Skip to content

Commit 0a960ad

Browse files
authored
[doctrine] Add RequiredDoctrineServiceRepositoryParentRule (#208)
1 parent 689e172 commit 0a960ad

File tree

9 files changed

+193
-2
lines changed

9 files changed

+193
-2
lines changed

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1782,6 +1782,46 @@ class SomeListener
17821782

17831783
<br>
17841784

1785+
### RequiredDoctrineServiceRepositoryParentRule
1786+
1787+
Repository must extend *, so it can be injected as a service
1788+
1789+
```yaml
1790+
rules:
1791+
- Symplify\PHPStanRules\Rules\Doctrine\RequiredDoctrineServiceRepositoryParentRule
1792+
```
1793+
1794+
```php
1795+
final class SomeRepository
1796+
{
1797+
public function __construct(EntityManagerInterface $entityManager)
1798+
{
1799+
// ...
1800+
}
1801+
}
1802+
```
1803+
1804+
:x:
1805+
1806+
<br>
1807+
1808+
```php
1809+
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
1810+
use Doctrine\Persistence\ManagerRegistry;
1811+
1812+
final class SomeRepository extends ServiceEntityRepository
1813+
{
1814+
public function __construct(ManagerRegistry $registry)
1815+
{
1816+
parent::__construct($registry, SomeEntity::class);
1817+
}
1818+
}
1819+
```
1820+
1821+
:+1:
1822+
1823+
<br>
1824+
17851825
### NoDoctrineListenerWithoutContractRule
17861826

17871827
There should be no Doctrine listeners modified in config. Implement "Document\Event\EventSubscriber" to provide events in the class itself

src/Enum/DoctrineClass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class DoctrineClass
1414
/**
1515
* @var string
1616
*/
17-
public const ODM_SERVICE_DOCUMENT_REPOSITORY_INTERFACE = 'Doctrine\Bundle\MongoDBBundle\Repository\ServiceDocumentRepositoryInterface';
17+
public const ODM_SERVICE_REPOSITORY_INTERFACE = 'Doctrine\Bundle\MongoDBBundle\Repository\ServiceDocumentRepositoryInterface';
1818

1919
/**
2020
* @var string

src/Enum/DoctrineRuleIdentifier.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ final class DoctrineRuleIdentifier
1919
public const INJECT_SERVICE_REPOSITORY = 'doctrine.injectServiceRepository';
2020

2121
public const NO_LISTENER_WITHOUT_CONTRACT = 'doctrine.noListenerWithoutContract';
22+
23+
public const REQUIRE_SERVICE_PARENT_REPOSITORY = 'doctrine.requireServiceParentRepository';
2224
}

src/Rules/Doctrine/NoGetRepositoryOnServiceRepositoryEntityRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ private function isServiceRepositoryClassReflection(string $repositoryClassName)
134134
return true;
135135
}
136136

137-
if ($repositoryClassReflection->is(DoctrineClass::ODM_SERVICE_DOCUMENT_REPOSITORY_INTERFACE)) {
137+
if ($repositoryClassReflection->is(DoctrineClass::ODM_SERVICE_REPOSITORY_INTERFACE)) {
138138
return true;
139139
}
140140

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Rules\Doctrine;
6+
7+
use PhpParser\Node;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Node\InClassNode;
10+
use PHPStan\Reflection\ClassReflection;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleError;
13+
use PHPStan\Rules\RuleErrorBuilder;
14+
use Symplify\PHPStanRules\Enum\DoctrineClass;
15+
use Symplify\PHPStanRules\Enum\DoctrineRuleIdentifier;
16+
17+
/**
18+
* @implements Rule<InClassNode>
19+
*
20+
* @see \Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireServiceRepositoryParentRuleTest\RequireServiceRepositoryParentRuleTest
21+
*/
22+
final class RequireServiceRepositoryParentRule implements Rule
23+
{
24+
/**
25+
* @var string
26+
*/
27+
public const ERROR_MESSAGE = 'Repository must extend "%s", "%s" or implement "%s", so it can be injected as a service';
28+
29+
public function getNodeType(): string
30+
{
31+
return InClassNode::class;
32+
}
33+
34+
/**
35+
* @param InClassNode $node
36+
* @return RuleError[]
37+
*/
38+
public function processNode(Node $node, Scope $scope): array
39+
{
40+
$classReflection = $node->getClassReflection();
41+
42+
// no parent? probably not a repository service yet
43+
if (! $this->isDoctrineRepositoryClass($classReflection)) {
44+
return [];
45+
}
46+
47+
if ($this->isExtendingServiceRepository($classReflection)) {
48+
return [];
49+
}
50+
51+
$errorMessage = sprintf(self::ERROR_MESSAGE, DoctrineClass::ODM_SERVICE_REPOSITORY, DoctrineClass::ORM_SERVICE_REPOSITORY, DoctrineClass::ODM_SERVICE_REPOSITORY_INTERFACE);
52+
53+
$identifierRuleError = RuleErrorBuilder::message($errorMessage)
54+
->identifier(DoctrineRuleIdentifier::REQUIRE_SERVICE_PARENT_REPOSITORY)
55+
->build();
56+
57+
return [$identifierRuleError];
58+
}
59+
60+
private function isExtendingServiceRepository(ClassReflection $classReflection): bool
61+
{
62+
if ($classReflection->is(DoctrineClass::ODM_SERVICE_REPOSITORY)) {
63+
return true;
64+
}
65+
66+
if ($classReflection->is(DoctrineClass::ORM_SERVICE_REPOSITORY)) {
67+
return true;
68+
}
69+
70+
return $classReflection->is(DoctrineClass::ODM_SERVICE_REPOSITORY_INTERFACE);
71+
}
72+
73+
private function isDoctrineRepositoryClass(ClassReflection $classReflection): bool
74+
{
75+
if (! $classReflection->isClass()) {
76+
return false;
77+
}
78+
79+
// simple check
80+
return str_ends_with($classReflection->getName(), 'Repository');
81+
}
82+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireServiceRepositoryParentRuleTest\Fixture;
6+
7+
use Doctrine\Bundle\MongoDBBundle\Repository\ServiceDocumentRepositoryInterface;
8+
9+
final class SkipContractImplementingRepository implements ServiceDocumentRepositoryInterface
10+
{
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireServiceRepositoryParentRuleTest\Fixture;
6+
7+
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
8+
9+
final class SkipServiceRepository extends ServiceEntityRepository
10+
{
11+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireServiceRepositoryParentRuleTest\Fixture;
6+
7+
final class SomeRepository
8+
{
9+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireServiceRepositoryParentRuleTest;
6+
7+
use Iterator;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use Symplify\PHPStanRules\Enum\DoctrineClass;
12+
use Symplify\PHPStanRules\Rules\Doctrine\RequireServiceRepositoryParentRule;
13+
14+
final class RequireServiceRepositoryParentRuleTest extends RuleTestCase
15+
{
16+
#[DataProvider('provideData')]
17+
public function testRule(string $filePath, array $expectedErrorsWithLines): void
18+
{
19+
$this->analyse([$filePath], $expectedErrorsWithLines);
20+
}
21+
22+
public static function provideData(): Iterator
23+
{
24+
$errorMessage = sprintf(RequireServiceRepositoryParentRule::ERROR_MESSAGE, DoctrineClass::ODM_SERVICE_REPOSITORY, DoctrineClass::ORM_SERVICE_REPOSITORY, DoctrineClass::ODM_SERVICE_REPOSITORY_INTERFACE);
25+
26+
yield [__DIR__ . '/Fixture/SomeRepository.php', [[$errorMessage, 7]]];
27+
28+
yield [__DIR__ . '/Fixture/SkipServiceRepository.php', []];
29+
yield [__DIR__ . '/Fixture/SkipContractImplementingRepository.php', []];
30+
}
31+
32+
protected function getRule(): Rule
33+
{
34+
return new RequireServiceRepositoryParentRule();
35+
}
36+
}

0 commit comments

Comments
 (0)