Skip to content

Commit 983c304

Browse files
Fix return type on EntityRepository::matching() generics (#743)
Co-authored-by: Eric Stern <eric@ericstern.com>
1 parent 81dac0e commit 983c304

7 files changed

Lines changed: 120 additions & 7 deletions

File tree

extension.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ parameters:
3232
- stubs/Persistence/ObjectManagerDecorator.stub
3333
- stubs/Persistence/ObjectRepository.stub
3434
- stubs/RepositoryFactory.stub
35+
- stubs/Collections/AbstractLazyCollection.stub
3536
- stubs/Collections/ArrayCollection.stub
3637
- stubs/Collections/Selectable.stub
3738
- stubs/ORM/AbstractQuery.stub
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Doctrine\Common\Collections;
4+
5+
/**
6+
* @phpstan-template TKey of array-key
7+
* @phpstan-template T
8+
* @template-implements Collection<TKey,T>
9+
* @template-implements Selectable<TKey,T>
10+
*/
11+
abstract class AbstractLazyCollection implements Collection, Selectable
12+
{
13+
}

stubs/EntityManagerInterface.stub

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
namespace Doctrine\ORM;
44

5-
use Doctrine\Persistence\ObjectManager;
6-
use Doctrine\Persistence\ObjectRepository;
5+
use Doctrine\ORM\EntityRepository;
76
use Doctrine\ORM\Exception\ORMException;
87
use Doctrine\ORM\Mapping\ClassMetadata;
8+
use Doctrine\Persistence\ObjectManager;
99

1010
interface EntityManagerInterface extends ObjectManager
1111
{
@@ -29,7 +29,7 @@ interface EntityManagerInterface extends ObjectManager
2929
/**
3030
* @template T of object
3131
* @phpstan-param class-string<T> $className
32-
* @phpstan-return ObjectRepository<T>
32+
* @phpstan-return EntityRepository<T>
3333
*/
3434
public function getRepository($className);
3535

stubs/EntityRepository.stub

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace Doctrine\ORM;
44

5+
use Doctrine\Common\Collections\AbstractLazyCollection;
56
use Doctrine\Common\Collections\Criteria;
7+
use Doctrine\Common\Collections\Selectable;
68
use Doctrine\Persistence\ObjectRepository;
79

810
/**
@@ -59,11 +61,9 @@ class EntityRepository implements ObjectRepository
5961
protected function getEntityName();
6062

6163
/**
62-
* @param \Doctrine\Common\Collections\Criteria $criteria
64+
* @param Criteria $criteria
6365
*
64-
* @return \Doctrine\Common\Collections\Collection
65-
*
66-
* @psalm-return \Doctrine\Common\Collections\Collection<int, TEntityClass>
66+
* @phpstan-return AbstractLazyCollection<int, TEntityClass>&Selectable<int, TEntityClass>
6767
*/
6868
public function matching(Criteria $criteria);
6969

tests/DoctrineIntegration/TypeInferenceTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
namespace PHPStan\DoctrineIntegration;
44

5+
use Doctrine\Common\Collections\AbstractLazyCollection;
6+
use Doctrine\Common\Collections\Selectable;
57
use PHPStan\Testing\TypeInferenceTestCase;
8+
use function is_a;
69

710
class TypeInferenceTest extends TypeInferenceTestCase
811
{
@@ -12,6 +15,12 @@ public function dataFileAsserts(): iterable
1215
yield from $this->gatherAssertTypes(__DIR__ . '/data/getRepository.php');
1316
yield from $this->gatherAssertTypes(__DIR__ . '/data/isEmpty.php');
1417
yield from $this->gatherAssertTypes(__DIR__ . '/data/Collection.php');
18+
19+
if (is_a(AbstractLazyCollection::class, Selectable::class, true)) { // @phpstan-ignore function.alreadyNarrowedType
20+
yield from $this->gatherAssertTypes(__DIR__ . '/data/repositoryMatching-collection26.php');
21+
} else {
22+
yield from $this->gatherAssertTypes(__DIR__ . '/data/repositoryMatching-collection25.php');
23+
}
1524
}
1625

1726
/**
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace RepositoryMatching25;
4+
5+
use Doctrine\Common\Collections\Criteria;
6+
use Doctrine\ORM\EntityManager;
7+
use Doctrine\ORM\EntityRepository;
8+
use function PHPStan\Testing\assertType;
9+
10+
class Foo
11+
{
12+
13+
/** @var EntityManager */
14+
private $entityManager;
15+
16+
public function doFoo(): void
17+
{
18+
$repository = $this->entityManager->getRepository(MyEntity::class);
19+
$criteria = Criteria::create();
20+
$results = $repository->matching($criteria);
21+
assertType('Doctrine\Common\Collections\AbstractLazyCollection<int, RepositoryMatching25\MyEntity>&Doctrine\Common\Collections\Selectable<int, RepositoryMatching25\MyEntity>', $results);
22+
foreach ($results as $result) {
23+
assertType(MyEntity::class, $result);
24+
}
25+
}
26+
27+
/**
28+
* @param EntityRepository<MyEntity> $repository
29+
*/
30+
public function withTypedRepository(EntityRepository $repository): void
31+
{
32+
$criteria = Criteria::create();
33+
$results = $repository->matching($criteria);
34+
assertType('Doctrine\Common\Collections\AbstractLazyCollection<int, RepositoryMatching25\MyEntity>&Doctrine\Common\Collections\Selectable<int, RepositoryMatching25\MyEntity>', $results);
35+
foreach ($results as $result) {
36+
assertType(MyEntity::class, $result);
37+
}
38+
}
39+
40+
}
41+
42+
class MyEntity
43+
{
44+
45+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace RepositoryMatching26;
4+
5+
use Doctrine\Common\Collections\Criteria;
6+
use Doctrine\ORM\EntityManager;
7+
use Doctrine\ORM\EntityRepository;
8+
use function PHPStan\Testing\assertType;
9+
10+
class Foo
11+
{
12+
13+
/** @var EntityManager */
14+
private $entityManager;
15+
16+
public function doFoo(): void
17+
{
18+
$repository = $this->entityManager->getRepository(MyEntity::class);
19+
$criteria = Criteria::create();
20+
$results = $repository->matching($criteria);
21+
assertType('Doctrine\Common\Collections\AbstractLazyCollection<int, RepositoryMatching26\MyEntity>', $results);
22+
foreach ($results as $result) {
23+
assertType(MyEntity::class, $result);
24+
}
25+
}
26+
27+
/**
28+
* @param EntityRepository<MyEntity> $repository
29+
*/
30+
public function withTypedRepository(EntityRepository $repository): void
31+
{
32+
$criteria = Criteria::create();
33+
$results = $repository->matching($criteria);
34+
assertType('Doctrine\Common\Collections\AbstractLazyCollection<int, RepositoryMatching26\MyEntity>', $results);
35+
foreach ($results as $result) {
36+
assertType(MyEntity::class, $result);
37+
}
38+
}
39+
40+
}
41+
42+
class MyEntity
43+
{
44+
45+
}

0 commit comments

Comments
 (0)