From 2f2127551edc1763b3b5867f53f2a0f2c0c93cde Mon Sep 17 00:00:00 2001 From: Florian Pfitzer Date: Fri, 11 Sep 2020 06:52:09 +0200 Subject: [PATCH] allow EntityRepository usage by interfaces --- ...tityRepositoryClassReflectionExtension.php | 15 ++++++++ .../ORM/data/customRepositoryUsage-2.json | 25 ++++++++++--- .../ORM/data/customRepositoryUsage-6.json | 4 +-- .../data/customRepositoryUsage-8-missing.json | 12 ++++++- .../ORM/data/customRepositoryUsage-8.json | 19 ++++++++-- .../ORM/data/customRepositoryUsage.php | 35 +++++++++++++++++-- tests/Rules/Doctrine/ORM/data/MyEntity.php | 2 +- .../Doctrine/ORM/data/MyEntityInterface.php | 7 ++++ .../data/RepositoryInterfaceFindByCalls.php | 7 ++++ 9 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 tests/Rules/Doctrine/ORM/data/MyEntityInterface.php create mode 100644 tests/Rules/Doctrine/ORM/data/RepositoryInterfaceFindByCalls.php diff --git a/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php b/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php index 4a6ac47d..74a25e1c 100644 --- a/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php +++ b/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php @@ -66,6 +66,21 @@ public function hasMethod(\PHPStan\Reflection\ClassReflection $classReflection, } $fieldName = $this->classify($methodFieldName); + // ensure all entities implementing the interface have the field + if (interface_exists($entityClassType->getClassName())) { + foreach ($objectManager->getMetadataFactory()->getAllMetadata() as $metadata) { + $implements = class_implements($metadata->getName()); + if (!isset($implements[$entityClassType->getClassName()])) { + continue; + } + if (!$metadata->hasField($fieldName) && $metadata->hasAssociation($fieldName)) { + return false; + } + } + + return true; + } + $classMetadata = $objectManager->getClassMetadata($entityClassType->getClassName()); return $classMetadata->hasField($fieldName) || $classMetadata->hasAssociation($fieldName); diff --git a/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-2.json b/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-2.json index 0e6a517a..a73b21b3 100644 --- a/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-2.json +++ b/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-2.json @@ -1,27 +1,42 @@ [ { "message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntity::nonexistent().", - "line": 35, + "line": 43, "ignorable": true }, { "message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyRepository::nonexistant().", - "line": 40, + "line": 48, "ignorable": true }, { "message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntity::nonexistent().", - "line": 54, + "line": 62, + "ignorable": true + }, + { + "message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntityInterface::nonexistent().", + "line": 69, "ignorable": true }, { "message": "Cannot call method test() on int.", - "line": 59, + "line": 74, "ignorable": true }, { "message": "Cannot call method test() on int.", - "line": 60, + "line": 75, + "ignorable": true + }, + { + "message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntityInterface::test().", + "line": 76, + "ignorable": true + }, + { + "message": "Method Doctrine\\Persistence\\ObjectRepository::findOneByBlabla() invoked with 0 parameters, 1 required.", + "line": 76, "ignorable": true } ] \ No newline at end of file diff --git a/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-6.json b/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-6.json index 5e81e72b..d22a70ee 100644 --- a/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-6.json +++ b/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-6.json @@ -1,12 +1,12 @@ [ { "message": "Property PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\Example::$repository with generic class PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyRepository does not specify its types: T", - "line": 15, + "line": 16, "ignorable": true }, { "message": "Method PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\Example::__construct() has parameter $anotherRepository with generic class PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyRepository but does not specify its types: T", - "line": 22, + "line": 28, "ignorable": true } ] \ No newline at end of file diff --git a/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-8-missing.json b/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-8-missing.json index 2c77f40d..4dacad6f 100644 --- a/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-8-missing.json +++ b/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-8-missing.json @@ -1,7 +1,17 @@ [ { "message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntity::nonexistent().", - "line": 54, + "line": 62, + "ignorable": true + }, + { + "message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntityInterface::nonexistent().", + "line": 69, + "ignorable": true + }, + { + "message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntityInterface::test().", + "line": 76, "ignorable": true } ] \ No newline at end of file diff --git a/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-8.json b/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-8.json index 8a4b71da..fbc399ab 100644 --- a/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-8.json +++ b/tests/DoctrineIntegration/ORM/data/customRepositoryUsage-8.json @@ -1,12 +1,27 @@ [ { "message": "Cannot call method doSomethingElse() on PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntity|null.", - "line": 53, + "line": 61, "ignorable": true }, { "message": "Cannot call method nonexistent() on PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntity|null.", - "line": 54, + "line": 62, + "ignorable": true + }, + { + "message": "Cannot call method doSomethingElse() on PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntityInterface|null.", + "line": 68, + "ignorable": true + }, + { + "message": "Cannot call method nonexistent() on PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntityInterface|null.", + "line": 69, + "ignorable": true + }, + { + "message": "Cannot call method test() on PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntityInterface|null.", + "line": 76, "ignorable": true } ] \ No newline at end of file diff --git a/tests/DoctrineIntegration/ORM/data/customRepositoryUsage.php b/tests/DoctrineIntegration/ORM/data/customRepositoryUsage.php index 0f6ca4cb..6677b23d 100644 --- a/tests/DoctrineIntegration/ORM/data/customRepositoryUsage.php +++ b/tests/DoctrineIntegration/ORM/data/customRepositoryUsage.php @@ -2,6 +2,7 @@ namespace PHPStan\DoctrineIntegration\ORM\CustomRepositoryUsage; +use Doctrine\Common\Persistence\ObjectRepository; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Mapping as ORM; @@ -19,13 +20,20 @@ class Example */ private $anotherRepository; + /** + * @var MyRepositoryInterface + */ + private $interfaceRepository; + public function __construct( EntityManagerInterface $entityManager, - MyRepository $anotherRepository + MyRepository $anotherRepository, + MyRepositoryInterface $interfaceRepository ) { $this->repository = $entityManager->getRepository(MyEntity::class); $this->anotherRepository = $anotherRepository; + $this->interfaceRepository = $interfaceRepository; } public function get(): void @@ -54,17 +62,31 @@ public function genericRepository(): void $entity->nonexistent(); } + public function interfaceRepository(): void + { + $entity = $this->interfaceRepository->find(1); + $entity->doSomethingElse(); + $entity->nonexistent(); + } + public function callExistingMethodOnRepository(): void { $this->repository->findOneByBlabla()->test(); $this->anotherRepository->findOneByBlabla()->test(); + $this->interfaceRepository->findOneByBlabla()->test(); } } +interface MyEntityInterface +{ + public function doSomethingElse(): void; +} + + /** * @ORM\Entity(repositoryClass=MyRepository::class) */ -class MyEntity +class MyEntity implements MyEntityInterface { /** * @ORM\Id() @@ -102,3 +124,12 @@ public function findOneByBlabla(): int return 1; } } + +/** + * @extends ObjectRepository + */ +interface MyRepositoryInterface extends ObjectRepository +{ + +} + diff --git a/tests/Rules/Doctrine/ORM/data/MyEntity.php b/tests/Rules/Doctrine/ORM/data/MyEntity.php index a20c6fd5..a279e3a3 100644 --- a/tests/Rules/Doctrine/ORM/data/MyEntity.php +++ b/tests/Rules/Doctrine/ORM/data/MyEntity.php @@ -7,7 +7,7 @@ /** * @ORM\Entity() */ -class MyEntity +class MyEntity implements MyEntityInterface { /** * @ORM\Id() diff --git a/tests/Rules/Doctrine/ORM/data/MyEntityInterface.php b/tests/Rules/Doctrine/ORM/data/MyEntityInterface.php new file mode 100644 index 00000000..1aa21df0 --- /dev/null +++ b/tests/Rules/Doctrine/ORM/data/MyEntityInterface.php @@ -0,0 +1,7 @@ +