diff --git a/rules-tests/CodeQuality/Rector/Class_/CompleteDynamicPropertiesRector/Fixture/skip_property_from_not_autoloaded_grandparent.php.inc b/rules-tests/CodeQuality/Rector/Class_/CompleteDynamicPropertiesRector/Fixture/skip_property_from_not_autoloaded_grandparent.php.inc new file mode 100644 index 00000000000..8bffac21901 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/CompleteDynamicPropertiesRector/Fixture/skip_property_from_not_autoloaded_grandparent.php.inc @@ -0,0 +1,15 @@ +configFactory->get(''); + } +} + +class CancelableHandler extends \NotAutoloaded\EmailHandler +{ +} diff --git a/rules/CodeQuality/Rector/Class_/CompleteDynamicPropertiesRector.php b/rules/CodeQuality/Rector/Class_/CompleteDynamicPropertiesRector.php index 9def47c7b58..aa888569847 100644 --- a/rules/CodeQuality/Rector/Class_/CompleteDynamicPropertiesRector.php +++ b/rules/CodeQuality/Rector/Class_/CompleteDynamicPropertiesRector.php @@ -5,7 +5,6 @@ namespace Rector\CodeQuality\Rector\Class_; use PhpParser\Node; -use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Class_; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; @@ -15,6 +14,7 @@ use Rector\NodeAnalyzer\ClassAnalyzer; use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer; use Rector\Rector\AbstractRector; +use Rector\Reflection\ClassReflectionAnalyzer; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -30,6 +30,7 @@ public function __construct( private readonly ClassAnalyzer $classAnalyzer, private readonly PhpAttributeAnalyzer $phpAttributeAnalyzer, private readonly MissingPropertiesResolver $missingPropertiesResolver, + private readonly ClassReflectionAnalyzer $classReflectionAnalyzer, ) { } @@ -140,9 +141,25 @@ private function shouldSkipClass(Class_ $class): bool return true; } - return $class->extends instanceof FullyQualified && ! $this->reflectionProvider->hasClass( - $class->extends->toString() - ); + // any not autoloaded ancestor may already declare the property, so we cannot safely add it + return $this->hasNotAutoloadedAncestor($classReflection); + } + + private function hasNotAutoloadedAncestor(ClassReflection $classReflection): bool + { + $currentClassReflection = $classReflection; + + while ($currentClassReflection instanceof ClassReflection) { + $parentClassName = $this->classReflectionAnalyzer->resolveParentClassName($currentClassReflection); + + if ($parentClassName !== null && ! $this->reflectionProvider->hasClass($parentClassName)) { + return true; + } + + $currentClassReflection = $currentClassReflection->getParentClass(); + } + + return false; } private function matchClassReflection(Class_ $class): ?ClassReflection