Skip to content

Commit f9180bf

Browse files
authored
improve RequireQueryBuilderOnRepositoryRule to report document manager and skip document repository (#222)
1 parent 3d11c04 commit f9180bf

File tree

11 files changed

+132
-10
lines changed

11 files changed

+132
-10
lines changed

src/Enum/DoctrineClass.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,9 @@ final class DoctrineClass
3535
* @var string
3636
*/
3737
public const CONNECTION = 'Doctrine\DBAL\Connection';
38+
39+
/**
40+
* @var string
41+
*/
42+
public const DOCUMENT_REPOSITORY = 'Doctrine\ODM\MongoDB\Repository\DocumentRepository';
3843
}

src/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule.php

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
use PHPStan\Rules\Rule;
1111
use PHPStan\Rules\RuleErrorBuilder;
1212
use PHPStan\Type\ObjectType;
13+
use PHPStan\Type\Type;
14+
use PHPStan\Type\UnionType;
1315
use Symplify\PHPStanRules\Enum\DoctrineClass;
1416
use Symplify\PHPStanRules\Enum\RuleIdentifier\DoctrineRuleIdentifier;
1517
use Symplify\PHPStanRules\Helper\NamingHelper;
1618

1719
/**
1820
* @implements Rule<MethodCall>
21+
* @see \Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule\RequireQueryBuilderOnRepositoryRuleTest
1922
*/
2023
final class RequireQueryBuilderOnRepositoryRule implements Rule
2124
{
@@ -39,16 +42,7 @@ public function processNode(Node $node, Scope $scope): array
3942
}
4043

4144
$callerType = $scope->getType($node->var);
42-
if (! $callerType instanceof ObjectType) {
43-
return [];
44-
}
45-
46-
// we safe as both select() + from() calls are made on the repository
47-
if ($callerType->isInstanceOf(DoctrineClass::ENTITY_REPOSITORY)->yes()) {
48-
return [];
49-
}
50-
51-
if ($callerType->isInstanceOf(DoctrineClass::CONNECTION)->yes()) {
45+
if ($this->isValidRepositoryObjectType($callerType)) {
5246
return [];
5347
}
5448

@@ -58,4 +52,30 @@ public function processNode(Node $node, Scope $scope): array
5852

5953
return [$identifierRuleError];
6054
}
55+
56+
private function isValidRepositoryObjectType(Type $type): bool
57+
{
58+
if ($type instanceof UnionType) {
59+
foreach ($type->getTypes() as $unionType) {
60+
if ($this->isValidRepositoryObjectType($unionType)) {
61+
return true;
62+
}
63+
}
64+
}
65+
66+
if (! $type instanceof ObjectType) {
67+
return true;
68+
}
69+
70+
// we safe as both select() + from() calls are made on the repository
71+
if ($type->isInstanceOf(DoctrineClass::ENTITY_REPOSITORY)->yes()) {
72+
return true;
73+
}
74+
75+
if ($type->isInstanceOf(DoctrineClass::DOCUMENT_REPOSITORY)->yes()) {
76+
return true;
77+
}
78+
79+
return $type->isInstanceOf(DoctrineClass::CONNECTION)->yes();
80+
}
6181
}

stubs/Doctrine/DBAL/Connection.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Doctrine\DBAL;
4+
5+
class Connection
6+
{
7+
public function createQueryBuilder()
8+
{
9+
}
10+
}

stubs/Doctrine/ODM/MongoDB/DocumentManager.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,27 @@
22

33
namespace Doctrine\ODM\MongoDB;
44

5+
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
6+
use Doctrine\ODM\MongoDB\Repository\GridFSRepository;
7+
use Doctrine\ODM\MongoDB\Repository\ViewRepository;
8+
59
if (class_exists('Doctrine\ODM\MongoDB\DocumentManager')) {
610
return;
711
}
812

913
abstract class DocumentManager
1014
{
15+
/**
16+
* Gets the repository for a document class.
17+
*
18+
* @param string $className The name of the Document.
19+
* @psalm-param class-string<T> $className
20+
*
21+
* @return DocumentRepository|GridFSRepository|ViewRepository The repository.
22+
* @psalm-return DocumentRepository<T>|GridFSRepository<T>|ViewRepository<T>
23+
*
24+
* @template T of object
25+
*/
1126
public function getRepository(string $class)
1227
{
1328
}

stubs/Doctrine/ODM/MongoDB/Repository/DocumentRepository.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@
88

99
abstract class DocumentRepository
1010
{
11+
public function getQueryBuilder()
12+
{
13+
}
1114
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Doctrine\ODM\MongoDB\Repository;
4+
5+
class GridFSRepository
6+
{
7+
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Doctrine\ODM\MongoDB\Repository;
4+
5+
class ViewRepository
6+
{
7+
8+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule\Fixture;
6+
7+
use Doctrine\ODM\MongoDB\DocumentManager;
8+
9+
final class ReportOnDocumentManager
10+
{
11+
public function process(DocumentManager $documentManager)
12+
{
13+
$someRepository = $documentManager->createQueryBuilder();
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule\Fixture;
6+
7+
use Doctrine\DBAL\Connection;
8+
9+
final class SkipConnection
10+
{
11+
public function process(Connection $connection)
12+
{
13+
$someQueryBuilder = $connection->createQueryBuilder();
14+
}
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule\Fixture;
6+
7+
use Doctrine\ODM\MongoDB\DocumentManager;
8+
use Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule\Source\RandomEntity;
9+
10+
final class SkipDocumentRepository
11+
{
12+
public function process(DocumentManager $documentManager)
13+
{
14+
$someRepository = $documentManager->getRepository(RandomEntity::class)
15+
->createQueryBuilder();
16+
}
17+
}

tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/RequireQueryBuilderOnRepositoryRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,16 @@ public function testRule(string $filePath, array $expectedErrorsWithLines): void
2121
public static function provideData(): Iterator
2222
{
2323
yield [__DIR__ . '/Fixture/SkipCreateQueryBuilderOnRepository.php', []];
24+
yield [__DIR__ . '/Fixture/SkipDocumentRepository.php', []];
25+
yield [__DIR__ . '/Fixture/SkipConnection.php', []];
2426

2527
yield [__DIR__ . '/Fixture/ReportOnEntityManager.php', [
2628
[RequireQueryBuilderOnRepositoryRule::ERROR_MESSAGE, 14],
2729
]];
30+
31+
yield [__DIR__ . '/Fixture/ReportOnDocumentManager.php', [
32+
[RequireQueryBuilderOnRepositoryRule::ERROR_MESSAGE, 13],
33+
]];
2834
}
2935

3036
protected function getRule(): Rule

0 commit comments

Comments
 (0)