Skip to content

Commit a0e97e6

Browse files
mrceperkahrach
authored andcommitted
repository: correctly resolve union of multiple repositories [closes #19][closes #20]
1 parent 2d41436 commit a0e97e6

File tree

2 files changed

+50
-6
lines changed

2 files changed

+50
-6
lines changed

src/Types/RepositoryReturnTypeExtension.php

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
use PHPStan\Type\IntegerType;
1818
use PHPStan\Type\IntersectionType;
1919
use PHPStan\Type\IterableType;
20+
use PHPStan\Type\MixedType;
2021
use PHPStan\Type\ObjectType;
2122
use PHPStan\Type\Type;
2223
use PHPStan\Type\TypeCombinator;
24+
use PHPStan\Type\TypeTraverser;
2325
use PHPStan\Type\TypeWithClassName;
26+
use PHPStan\Type\UnionType;
2427

2528

2629
class RepositoryReturnTypeExtension implements DynamicMethodReturnTypeExtension
@@ -32,7 +35,10 @@ class RepositoryReturnTypeExtension implements DynamicMethodReturnTypeExtension
3235
private $reflectionProvider;
3336

3437

35-
public function __construct(RepositoryEntityTypeHelper $repositoryEntityTypeHelper, ReflectionProvider $reflectionProvider)
38+
public function __construct(
39+
RepositoryEntityTypeHelper $repositoryEntityTypeHelper,
40+
ReflectionProvider $reflectionProvider
41+
)
3642
{
3743
$this->repositoryEntityTypeHelper = $repositoryEntityTypeHelper;
3844
$this->reflectionProvider = $reflectionProvider;
@@ -71,15 +77,38 @@ public function getTypeFromMethodCall(
7177
): Type
7278
{
7379
$repository = $scope->getType($methodCall->var);
74-
\assert($repository instanceof TypeWithClassName);
80+
$repositoryType = new ObjectType(IRepository::class);
81+
return TypeTraverser::map(
82+
$repository,
83+
function ($type, $traverse) use ($repositoryType, $methodReflection, $scope): Type {
84+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
85+
return $traverse($type);
86+
}
87+
if (!$type instanceof TypeWithClassName || $repositoryType->isSuperTypeOf($type)->no()) {
88+
return new MixedType();
89+
}
90+
91+
/** @var class-string<Repository> $repositoryClassName */
92+
$repositoryClassName = $type->getClassName();
93+
return $this->getEntityTypeFromMethodClass($repositoryClassName, $methodReflection, $scope);
94+
}
95+
);
96+
}
7597

76-
if ($repository->getClassName() === Repository::class) {
98+
99+
/**
100+
* @param class-string<Repository> $repositoryClassName
101+
*/
102+
private function getEntityTypeFromMethodClass(
103+
string $repositoryClassName,
104+
MethodReflection $methodReflection,
105+
Scope $scope
106+
): Type
107+
{
108+
if ($repositoryClassName === Repository::class) {
77109
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
78110
}
79111

80-
/** @phpstan-var class-string<Repository> $repositoryClassName */
81-
$repositoryClassName = $repository->getClassName();
82-
83112
try {
84113
$repositoryReflection = $this->reflectionProvider->getClass($repositoryClassName);
85114
} catch (ClassNotFoundException $e) {

tests/testbox/Types/RepositoryTypesTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ public function testOk(AuthorsRepository $repository): void
3232
/** @var IEntity $a */
3333
$a = $repository->getById(1);
3434
$this->takeAuthor($repository->persist($a));
35+
36+
/** @var AuthorsRepository|BooksRepository $someRepo */
37+
$someRepo = $repository;
38+
foreach ($someRepo->findAll() as $entity) {
39+
if ($entity instanceof Author) {
40+
$this->takeAuthor($entity);
41+
} else {
42+
$this->takeBook($entity);
43+
}
44+
}
3545
}
3646

3747

@@ -51,4 +61,9 @@ private function takeAuthorNullable(?Author $author): void
5161
private function takeAuthorArray($authors): void
5262
{
5363
}
64+
65+
66+
private function takeBook(Book $book): void
67+
{
68+
}
5469
}

0 commit comments

Comments
 (0)