diff --git a/src/NodeManipulator/ClassDependencyManipulator.php b/src/NodeManipulator/ClassDependencyManipulator.php index 61e43fb1730..9125a347977 100644 --- a/src/NodeManipulator/ClassDependencyManipulator.php +++ b/src/NodeManipulator/ClassDependencyManipulator.php @@ -4,11 +4,13 @@ namespace Rector\NodeManipulator; +use PhpParser\Modifiers; use PhpParser\Node\Arg; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Name; +use PhpParser\Node\Param; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassLike; @@ -248,13 +250,31 @@ private function addPromotedProperty( $param = $this->nodeFactory->createPromotedPropertyParam($propertyMetadata); if ($constructClassMethod instanceof ClassMethod) { - // parameter is already added - if ($this->hasMethodParameter($constructClassMethod, $propertyMetadata->getName())) { + $hasOwnConstruct = $class->getMethod(MethodName::CONSTRUCT) instanceof ClassMethod; + $matchedParam = $this->matchMethodParameter($constructClassMethod, $propertyMetadata->getName()); + + if ($matchedParam instanceof Param) { + // own constructor already has this param → nothing to do + if ($hasOwnConstruct) { + return; + } + + // parent constructor has a same-named param; if it is a private promoted + // property, the child cannot access it via $this — add a fresh constructor + // on the child instead of trying to extend the parent signature + if (($matchedParam->flags & Modifiers::PRIVATE) !== 0) { + $childConstructClassMethod = $this->nodeFactory->createPublicMethod(MethodName::CONSTRUCT); + $childConstructClassMethod->params[] = $param; + $this->classInsertManipulator->addAsFirstMethod($class, $childConstructClassMethod); + return; + } + + // parent's matching param is protected/public — accessible from child, no add needed return; } // found construct, but only on parent, add to current class - if (! $class->getMethod(MethodName::CONSTRUCT) instanceof ClassMethod) { + if (! $hasOwnConstruct) { $parentArgs = []; foreach ($constructClassMethod->params as $originalParam) { @@ -334,15 +354,15 @@ private function hasClassPropertyAndDependency(Class_ $class, PropertyMetadata $ return $property instanceof Property; } - private function hasMethodParameter(ClassMethod $classMethod, string $name): bool + private function matchMethodParameter(ClassMethod $classMethod, string $name): ?Param { foreach ($classMethod->params as $param) { if ($this->nodeNameResolver->isName($param->var, $name)) { - return true; + return $param; } } - return false; + return null; } private function shouldAddPromotedProperty(Class_ $class, PropertyMetadata $propertyMetadata): bool