diff --git a/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php b/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php index 9f5231fe..fe3bbc9d 100644 --- a/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php +++ b/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php @@ -87,6 +87,37 @@ public function processNode(Node $node, Scope $scope): array } } + $orderByType = count($node->getArgs()) > 1 ? $scope->getType($node->getArgs()[1]->value) : null; + + if ( + in_array($methodName, [ + 'findBy', + 'findOneBy', + ], true) + && $orderByType !== null + ) { + foreach ($orderByType->getConstantArrays() as $constantArray) { + foreach ($constantArray->getKeyTypes() as $keyType) { + foreach ($keyType->getConstantStrings() as $fieldName) { + if ( + $classMetadata->hasField($fieldName->getValue()) + || $classMetadata->hasAssociation($fieldName->getValue()) + ) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Call to method %s::%s() - entity %s does not have a field named $%s.', + $calledOnType->describe(VerbosityLevel::typeOnly()), + $methodName, + $entityClassNames[0], + $fieldName->getValue(), + ))->identifier(sprintf('doctrine.%sArgument', $methodName))->build(); + } + } + } + } + return $messages; } diff --git a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php index da10cbf3..4204e35f 100644 --- a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php +++ b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php @@ -76,6 +76,38 @@ public function testRule(): void 'Call to method Doctrine\ORM\EntityRepository::count() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $transient.', 45, ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $transient.', + 54, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $nonexistent.', + 55, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $nonexistent.', + 56, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $transient.', + 56, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findOneBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $transient.', + 64, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findOneBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $nonexistent.', + 65, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findOneBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $nonexistent.', + 66, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findOneBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $transient.', + 66, + ], ]); } diff --git a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php index 849d3675..0cc4f38b 100644 --- a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php +++ b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php @@ -76,6 +76,38 @@ public function testRule(): void 'Call to method Doctrine\ORM\EntityRepository::count() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $transient.', 45, ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $transient.', + 54, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $nonexistent.', + 55, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $nonexistent.', + 56, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $transient.', + 56, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findOneBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $transient.', + 64, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findOneBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $nonexistent.', + 65, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findOneBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $nonexistent.', + 66, + ], + [ + 'Call to method Doctrine\ORM\EntityRepository::findOneBy() - entity PHPStan\Rules\Doctrine\ORM\MyEntity does not have a field named $transient.', + 66, + ], ]); } diff --git a/tests/Rules/Doctrine/ORM/data/repository-findBy-etc.php b/tests/Rules/Doctrine/ORM/data/repository-findBy-etc.php index ee5fa6a7..c4271b5c 100644 --- a/tests/Rules/Doctrine/ORM/data/repository-findBy-etc.php +++ b/tests/Rules/Doctrine/ORM/data/repository-findBy-etc.php @@ -45,4 +45,25 @@ public function doCountBy(): void $entityRepository->count(['nonexistent' => 'test', 'transient' => 'test']); } + + public function doFindByOrder(): void + { + $entityRepository = $this->entityManager->getRepository(MyEntity::class); + $entityRepository->findBy([], ['id' => 'ASC']); + $entityRepository->findBy([], ['title' => 'DESC']); + $entityRepository->findBy([], ['transient' => 'ASC']); + $entityRepository->findBy([], ['nonexistent' => 'DESC']); + $entityRepository->findBy([], ['nonexistent' => 'ASC', 'transient' => 'DESC']); + } + + public function doFindOneByOrder(): void + { + $entityRepository = $this->entityManager->getRepository(MyEntity::class); + $entityRepository->findOneBy([], ['id' => 'ASC']); + $entityRepository->findOneBy([], ['title' => 'DESC']); + $entityRepository->findOneBy([], ['transient' => 'ASC']); + $entityRepository->findOneBy([], ['nonexistent' => 'DESC']); + $entityRepository->findOneBy([], ['nonexistent' => 'ASC', 'transient' => 'DESC']); + } + }