diff --git a/src/Enum/DoctrineClass.php b/src/Enum/DoctrineClass.php index b9c74ae90..4ff533366 100644 --- a/src/Enum/DoctrineClass.php +++ b/src/Enum/DoctrineClass.php @@ -35,4 +35,9 @@ final class DoctrineClass * @var string */ public const CONNECTION = 'Doctrine\DBAL\Connection'; + + /** + * @var string + */ + public const DOCUMENT_REPOSITORY = 'Doctrine\ODM\MongoDB\Repository\DocumentRepository'; } diff --git a/src/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule.php b/src/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule.php index 3306838cc..2e8d2234f 100644 --- a/src/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule.php +++ b/src/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule.php @@ -10,12 +10,15 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ObjectType; +use PHPStan\Type\Type; +use PHPStan\Type\UnionType; use Symplify\PHPStanRules\Enum\DoctrineClass; use Symplify\PHPStanRules\Enum\RuleIdentifier\DoctrineRuleIdentifier; use Symplify\PHPStanRules\Helper\NamingHelper; /** * @implements Rule + * @see \Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule\RequireQueryBuilderOnRepositoryRuleTest */ final class RequireQueryBuilderOnRepositoryRule implements Rule { @@ -39,16 +42,7 @@ public function processNode(Node $node, Scope $scope): array } $callerType = $scope->getType($node->var); - if (! $callerType instanceof ObjectType) { - return []; - } - - // we safe as both select() + from() calls are made on the repository - if ($callerType->isInstanceOf(DoctrineClass::ENTITY_REPOSITORY)->yes()) { - return []; - } - - if ($callerType->isInstanceOf(DoctrineClass::CONNECTION)->yes()) { + if ($this->isValidRepositoryObjectType($callerType)) { return []; } @@ -58,4 +52,30 @@ public function processNode(Node $node, Scope $scope): array return [$identifierRuleError]; } + + private function isValidRepositoryObjectType(Type $type): bool + { + if ($type instanceof UnionType) { + foreach ($type->getTypes() as $unionType) { + if ($this->isValidRepositoryObjectType($unionType)) { + return true; + } + } + } + + if (! $type instanceof ObjectType) { + return true; + } + + // we safe as both select() + from() calls are made on the repository + if ($type->isInstanceOf(DoctrineClass::ENTITY_REPOSITORY)->yes()) { + return true; + } + + if ($type->isInstanceOf(DoctrineClass::DOCUMENT_REPOSITORY)->yes()) { + return true; + } + + return $type->isInstanceOf(DoctrineClass::CONNECTION)->yes(); + } } diff --git a/stubs/Doctrine/DBAL/Connection.php b/stubs/Doctrine/DBAL/Connection.php new file mode 100644 index 000000000..f9d562611 --- /dev/null +++ b/stubs/Doctrine/DBAL/Connection.php @@ -0,0 +1,10 @@ + $className + * + * @return DocumentRepository|GridFSRepository|ViewRepository The repository. + * @psalm-return DocumentRepository|GridFSRepository|ViewRepository + * + * @template T of object + */ public function getRepository(string $class) { } diff --git a/stubs/Doctrine/ODM/MongoDB/Repository/DocumentRepository.php b/stubs/Doctrine/ODM/MongoDB/Repository/DocumentRepository.php index f516fbe69..4f961ce3f 100644 --- a/stubs/Doctrine/ODM/MongoDB/Repository/DocumentRepository.php +++ b/stubs/Doctrine/ODM/MongoDB/Repository/DocumentRepository.php @@ -8,4 +8,7 @@ abstract class DocumentRepository { + public function getQueryBuilder() + { + } } diff --git a/stubs/Doctrine/ODM/MongoDB/Repository/GridFSRepository.php b/stubs/Doctrine/ODM/MongoDB/Repository/GridFSRepository.php new file mode 100644 index 000000000..f398a2f0d --- /dev/null +++ b/stubs/Doctrine/ODM/MongoDB/Repository/GridFSRepository.php @@ -0,0 +1,8 @@ +createQueryBuilder(); + } +} diff --git a/tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/Fixture/SkipConnection.php b/tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/Fixture/SkipConnection.php new file mode 100644 index 000000000..314b9214d --- /dev/null +++ b/tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/Fixture/SkipConnection.php @@ -0,0 +1,15 @@ +createQueryBuilder(); + } +} diff --git a/tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/Fixture/SkipDocumentRepository.php b/tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/Fixture/SkipDocumentRepository.php new file mode 100644 index 000000000..e11db6947 --- /dev/null +++ b/tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/Fixture/SkipDocumentRepository.php @@ -0,0 +1,17 @@ +getRepository(RandomEntity::class) + ->createQueryBuilder(); + } +} diff --git a/tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/RequireQueryBuilderOnRepositoryRuleTest.php b/tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/RequireQueryBuilderOnRepositoryRuleTest.php index 05e8c6a33..72d0e8de6 100644 --- a/tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/RequireQueryBuilderOnRepositoryRuleTest.php +++ b/tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/RequireQueryBuilderOnRepositoryRuleTest.php @@ -21,10 +21,16 @@ public function testRule(string $filePath, array $expectedErrorsWithLines): void public static function provideData(): Iterator { yield [__DIR__ . '/Fixture/SkipCreateQueryBuilderOnRepository.php', []]; + yield [__DIR__ . '/Fixture/SkipDocumentRepository.php', []]; + yield [__DIR__ . '/Fixture/SkipConnection.php', []]; yield [__DIR__ . '/Fixture/ReportOnEntityManager.php', [ [RequireQueryBuilderOnRepositoryRule::ERROR_MESSAGE, 14], ]]; + + yield [__DIR__ . '/Fixture/ReportOnDocumentManager.php', [ + [RequireQueryBuilderOnRepositoryRule::ERROR_MESSAGE, 13], + ]]; } protected function getRule(): Rule