Skip to content

Commit 5fa20b8

Browse files
committed
Dynamic return type extension for EntityRepository methods via a generic type created by EM::getRepository()
1 parent a27368f commit 5fa20b8

File tree

4 files changed

+144
-0
lines changed

4 files changed

+144
-0
lines changed

extension.neon

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@ services:
1111
class: PHPStan\Type\Doctrine\EntityManagerFindDynamicReturnTypeExtension
1212
tags:
1313
- phpstan.broker.dynamicMethodReturnTypeExtension
14+
-
15+
class: PHPStan\Type\Doctrine\EntityManagerGetRepositoryDynamicReturnTypeExtension
16+
tags:
17+
- phpstan.broker.dynamicMethodReturnTypeExtension
18+
-
19+
class: PHPStan\Type\Doctrine\EntityRepositoryDynamicReturnTypeExtension
20+
tags:
21+
- phpstan.broker.dynamicMethodReturnTypeExtension
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Type\Type;
9+
10+
class EntityManagerGetRepositoryDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension
11+
{
12+
13+
public static function getClass(): string
14+
{
15+
return \Doctrine\ORM\EntityManager::class;
16+
}
17+
18+
public function isMethodSupported(MethodReflection $methodReflection): bool
19+
{
20+
return $methodReflection->getName() === 'getRepository';
21+
}
22+
23+
public function getTypeFromMethodCall(
24+
MethodReflection $methodReflection,
25+
MethodCall $methodCall,
26+
Scope $scope
27+
): Type
28+
{
29+
if (count($methodCall->args) === 0) {
30+
return $methodReflection->getReturnType();
31+
}
32+
$arg = $methodCall->args[0]->value;
33+
if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) {
34+
return $methodReflection->getReturnType();
35+
}
36+
37+
$class = $arg->class;
38+
if (!($class instanceof \PhpParser\Node\Name)) {
39+
return $methodReflection->getReturnType();
40+
}
41+
42+
$class = (string) $class;
43+
if ($class === 'static') {
44+
return $methodReflection->getReturnType();
45+
}
46+
47+
if ($class === 'self') {
48+
$class = $scope->getClassReflection()->getName();
49+
}
50+
51+
return new EntityRepositoryType($class);
52+
}
53+
54+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Type\ArrayType;
9+
use PHPStan\Type\MixedType;
10+
use PHPStan\Type\ObjectType;
11+
use PHPStan\Type\Type;
12+
use PHPStan\Type\TypeCombinator;
13+
14+
class EntityRepositoryDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension
15+
{
16+
17+
public static function getClass(): string
18+
{
19+
return \Doctrine\ORM\EntityRepository::class;
20+
}
21+
22+
public function isMethodSupported(MethodReflection $methodReflection): bool
23+
{
24+
$methodName = $methodReflection->getName();
25+
return strpos($methodName, 'findBy') === 0
26+
|| strpos($methodName, 'findOneBy') === 0
27+
|| $methodName === 'findAll'
28+
|| $methodName === 'find';
29+
}
30+
31+
public function getTypeFromMethodCall(
32+
MethodReflection $methodReflection,
33+
MethodCall $methodCall,
34+
Scope $scope
35+
): Type
36+
{
37+
$calledOnType = $scope->getType($methodCall->var);
38+
if (!$calledOnType instanceof EntityRepositoryType) {
39+
return new MixedType();
40+
}
41+
$methodName = $methodReflection->getName();
42+
$entityType = new ObjectType($calledOnType->getEntityClass());
43+
44+
if ($methodName === 'find' || strpos($methodName, 'findOneBy') === 0) {
45+
return TypeCombinator::addNull($entityType);
46+
}
47+
48+
return new ArrayType($entityType);
49+
}
50+
51+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine;
4+
5+
use PHPStan\Type\ObjectType;
6+
7+
class EntityRepositoryType extends ObjectType
8+
{
9+
10+
/**
11+
* @var string
12+
*/
13+
private $entityClass;
14+
15+
public function __construct(string $entityClass)
16+
{
17+
parent::__construct(\Doctrine\ORM\EntityRepository::class);
18+
$this->entityClass = $entityClass;
19+
}
20+
21+
public function getEntityClass(): string
22+
{
23+
return $this->entityClass;
24+
}
25+
26+
public function describe(): string
27+
{
28+
return sprintf('%s<%s>', parent::describe(), $this->entityClass);
29+
}
30+
31+
}

0 commit comments

Comments
 (0)