Skip to content

Commit 48d4b77

Browse files
authored
Skip parent type with required ctor (#165)
1 parent 588c6c6 commit 48d4b77

File tree

4 files changed

+73
-6
lines changed

4 files changed

+73
-6
lines changed

src/Rules/Symfony/RequiredOnlyInAbstractRule.php

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use PhpParser\Node;
88
use PhpParser\Node\Stmt\Class_;
99
use PHPStan\Analyser\Scope;
10+
use PHPStan\Node\InClassNode;
11+
use PHPStan\Reflection\ClassReflection;
1012
use PHPStan\Rules\Rule;
1113
use PHPStan\Rules\RuleErrorBuilder;
1214
use Symplify\PHPStanRules\Enum\SymfonyRuleIdentifier;
@@ -15,31 +17,51 @@
1517
/**
1618
* @see \Symplify\PHPStanRules\Tests\Rules\Symfony\RequiredOnlyInAbstractRule\RequiredOnlyInAbstractRuleTest
1719
*
18-
* @implements Rule<Class_>
20+
* @implements Rule<InClassNode>
1921
*/
2022
final class RequiredOnlyInAbstractRule implements Rule
2123
{
2224
/**
2325
* @var string
2426
*/
25-
public const ERROR_MESSAGE = '#@required is reserved exclusively for abstract classes. For the rest of classes, use clean constructor injection';
27+
public const ERROR_MESSAGE = '#Symfony @required or #[Required] is reserved exclusively for abstract classes. For the rest of classes, use clean constructor injection';
28+
29+
/**
30+
* Magic parent types that require constructor internally,
31+
* so @required on final class is allowed
32+
*
33+
* @var string[]
34+
*/
35+
private const SKIPPED_PARENT_TYPES = [
36+
'Doctrine\ODM\MongoDB\Repository\DocumentRepository',
37+
];
2638

2739
public function getNodeType(): string
2840
{
29-
return Class_::class;
41+
return InClassNode::class;
3042
}
3143

3244
/**
33-
* @param Class_ $node
45+
* @param InClassNode $node
3446
*/
3547
public function processNode(Node $node, Scope $scope): array
3648
{
37-
foreach ($node->getMethods() as $classMethod) {
49+
$originalNode = $node->getOriginalNode();
50+
if (! $originalNode instanceof Class_) {
51+
return [];
52+
}
53+
54+
if ($this->shouldSkipClass($scope)) {
55+
return [];
56+
}
57+
58+
$class = $originalNode;
59+
foreach ($class->getMethods() as $classMethod) {
3860
if (! SymfonyRequiredMethodAnalyzer::detect($classMethod)) {
3961
continue;
4062
}
4163

42-
if ($node->isAbstract()) {
64+
if ($class->isAbstract()) {
4365
continue;
4466
}
4567

@@ -53,4 +75,24 @@ public function processNode(Node $node, Scope $scope): array
5375

5476
return [];
5577
}
78+
79+
private function shouldSkipClass(Scope $scope): bool
80+
{
81+
$classReflection = $scope->getClassReflection();
82+
if (! $classReflection instanceof ClassReflection) {
83+
return false;
84+
}
85+
86+
if ($classReflection->isAbstract()) {
87+
return true;
88+
}
89+
90+
foreach (self::SKIPPED_PARENT_TYPES as $skippedParentType) {
91+
if ($classReflection->isSubclassOf($skippedParentType)) {
92+
return true;
93+
}
94+
}
95+
96+
return false;
97+
}
5698
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Doctrine\ODM\MongoDB\Repository;
4+
5+
if (class_exists('Doctrine\ODM\MongoDB\Repository\DocumentRepository')) {
6+
return;
7+
}
8+
9+
abstract class DocumentRepository
10+
{
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Symplify\PHPStanRules\Tests\Rules\Symfony\RequiredOnlyInAbstractRule\Fixture;
4+
5+
final class SkipParentDocumentRepository extends \Doctrine\ODM\MongoDB\Repository\DocumentRepository
6+
{
7+
/**
8+
* @required
9+
*/
10+
public function autowire()
11+
{
12+
}
13+
}

tests/Rules/Symfony/RequiredOnlyInAbstractRule/RequiredOnlyInAbstractRuleTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static function provideData(): Iterator
2626
]]];
2727

2828
yield [__DIR__ . '/Fixture/SkipAbstractClass.php', []];
29+
yield [__DIR__ . '/Fixture/SkipParentDocumentRepository.php', []];
2930
}
3031

3132
protected function getRule(): Rule

0 commit comments

Comments
 (0)