diff --git a/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php b/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php index eb2e8a51..d2f7d4f6 100644 --- a/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php +++ b/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php @@ -60,6 +60,10 @@ public function processNode(Node $node, Scope $scope): array } $entityClass = $entityClassType->getClassName(); + if (interface_exists($entityClass)) { + return []; + } + $methodNameIdentifier = $node->name; if (!$methodNameIdentifier instanceof Node\Identifier) { return []; diff --git a/tests/Rules/Doctrine/ORM/EntityImplementingInterfaceTest.php b/tests/Rules/Doctrine/ORM/EntityImplementingInterfaceTest.php new file mode 100644 index 00000000..ba0afad1 --- /dev/null +++ b/tests/Rules/Doctrine/ORM/EntityImplementingInterfaceTest.php @@ -0,0 +1,38 @@ + + */ +class EntityImplementingInterfaceTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new RepositoryMethodCallRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', null)); + } + + /** + * @return string[] + */ + public static function getAdditionalConfigFiles(): array + { + return [__DIR__ . '/magic-repository.neon']; + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/repository-findBy-interface.php'], [ + [ + 'Call to method Doctrine\ORM\EntityRepository::findBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntityImplementingInterface does not have a field named $nonexistent.', + 22, + ], + ]); + } + +} diff --git a/tests/Rules/Doctrine/ORM/data/MyEntityImplementingInterface.php b/tests/Rules/Doctrine/ORM/data/MyEntityImplementingInterface.php new file mode 100644 index 00000000..ad3c5bb2 --- /dev/null +++ b/tests/Rules/Doctrine/ORM/data/MyEntityImplementingInterface.php @@ -0,0 +1,31 @@ +name; + } +} diff --git a/tests/Rules/Doctrine/ORM/data/MyInterface.php b/tests/Rules/Doctrine/ORM/data/MyInterface.php new file mode 100644 index 00000000..077fd4a5 --- /dev/null +++ b/tests/Rules/Doctrine/ORM/data/MyInterface.php @@ -0,0 +1,8 @@ +entityManager = $entityManager; + } + + public function doFindByWithConcreteClass(): void + { + $entityRepository = $this->entityManager->getRepository(MyEntityImplementingInterface::class); + $entityRepository->findBy(['id' => 1]); + $entityRepository->findBy(['nonexistent' => 'test']); + } + + public function doFindByWithInterface(MyInterface $entity): void + { + $entityRepository = $this->entityManager->getRepository(get_class($entity)); + // This is likely to work, but cannot be inferred from the interface + // alone + $repo->findBy(['name' => $entity->getName()]); + // This is NOT likely to work, but can't be proven without examining + // all implementations of the interface + $entityRepository->findBy(['nonexistent' => 'test']); + } + +}