diff --git a/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Fixture/own_property_with_parent_construct.php.inc b/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Fixture/own_property_with_parent_construct.php.inc new file mode 100644 index 00000000000..132c83d4d1a --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Fixture/own_property_with_parent_construct.php.inc @@ -0,0 +1,37 @@ +ownTypes = ['refresh_token']; + } +} + +?> +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Fixture/skip_property_filled_in_parent_constructor.php.inc b/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Fixture/skip_property_filled_in_parent_constructor.php.inc new file mode 100644 index 00000000000..d9d3ca5b9b3 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Fixture/skip_property_filled_in_parent_constructor.php.inc @@ -0,0 +1,17 @@ +allowedGrantTypes = ['refresh_token']; + } +} diff --git a/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Source/AbstractParentWithFilledProperty.php b/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Source/AbstractParentWithFilledProperty.php new file mode 100644 index 00000000000..3fd1f368fc6 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector/Source/AbstractParentWithFilledProperty.php @@ -0,0 +1,15 @@ +allowedGrantTypes[] = 'authorization_code'; + } +} diff --git a/rules/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector.php b/rules/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector.php index e952fb1806f..c1e1358fb27 100644 --- a/rules/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector.php +++ b/rules/CodeQuality/Rector/Class_/InlineConstructorDefaultToPropertyRector.php @@ -8,16 +8,19 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\If_; use PhpParser\Node\Stmt\Property; +use PHPStan\Reflection\ClassReflection; use Rector\NodeAnalyzer\ExprAnalyzer; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PhpParser\NodeFinder\PropertyFetchFinder; use Rector\Rector\AbstractRector; +use Rector\Reflection\ReflectionResolver; use Rector\ValueObject\MethodName; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -30,7 +33,8 @@ final class InlineConstructorDefaultToPropertyRector extends AbstractRector public function __construct( private readonly ExprAnalyzer $exprAnalyzer, private readonly BetterNodeFinder $betterNodeFinder, - private readonly PropertyFetchFinder $propertyFetchFinder + private readonly PropertyFetchFinder $propertyFetchFinder, + private readonly ReflectionResolver $reflectionResolver ) { } @@ -89,6 +93,8 @@ public function refactor(Node $node): ?Node return null; } + $hasParentConstructCall = false; + foreach ($constructClassMethod->stmts as $key => $stmt) { // code that is possibly breaking flow if ($stmt instanceof If_) { @@ -99,6 +105,11 @@ public function refactor(Node $node): ?Node continue; } + if ($this->isParentConstructCall($stmt->expr)) { + $hasParentConstructCall = true; + continue; + } + if (! $stmt->expr instanceof Assign) { continue; } @@ -115,6 +126,11 @@ public function refactor(Node $node): ?Node continue; } + // parent constructor may set the very same property, keep assign order as is + if ($hasParentConstructCall && $this->isPropertyDefinedInParentClass($node, $propertyName)) { + continue; + } + $hasPropertyChanged = $this->refactorProperty( $node, $propertyName, @@ -135,6 +151,30 @@ public function refactor(Node $node): ?Node return $node; } + private function isParentConstructCall(Expr $expr): bool + { + if (! $expr instanceof StaticCall) { + return false; + } + + if (! $this->isName($expr->class, 'parent')) { + return false; + } + + return $this->isName($expr->name, MethodName::CONSTRUCT); + } + + private function isPropertyDefinedInParentClass(Class_ $class, string $propertyName): bool + { + $classReflection = $this->reflectionResolver->resolveClassReflection($class); + if (! $classReflection instanceof ClassReflection) { + // unknown parent, keep it safe + return true; + } + + return array_any($classReflection->getParents(), fn(ClassReflection $parentClassReflection) => $parentClassReflection->getNativeReflection()->hasProperty($propertyName)); + } + private function matchAssignedLocalPropertyName(Assign $assign): ?string { if (! $assign->var instanceof PropertyFetch) {