33declare (strict_types=1 );
44namespace Rector \NodeManipulator ;
55
6+ use PhpParser \Node \Arg ;
67use PhpParser \Node \Expr \Assign ;
78use PhpParser \Node \Expr \StaticCall ;
9+ use PhpParser \Node \Expr \Variable ;
810use PhpParser \Node \Name ;
911use PhpParser \Node \Stmt ;
1012use PhpParser \Node \Stmt \Class_ ;
13+ use PhpParser \Node \Stmt \ClassLike ;
1114use PhpParser \Node \Stmt \ClassMethod ;
1215use PhpParser \Node \Stmt \Expression ;
1316use PhpParser \Node \Stmt \Property ;
1720use Rector \NodeAnalyzer \PropertyPresenceChecker ;
1821use Rector \NodeNameResolver \NodeNameResolver ;
1922use Rector \Php \PhpVersionProvider ;
23+ use Rector \PhpParser \AstResolver ;
2024use Rector \PhpParser \Node \NodeFactory ;
2125use Rector \PostRector \ValueObject \PropertyMetadata ;
2226use Rector \Reflection \ReflectionResolver ;
@@ -64,7 +68,11 @@ final class ClassDependencyManipulator
6468 * @readonly
6569 */
6670 private ReflectionResolver $ reflectionResolver ;
67- public function __construct (\Rector \NodeManipulator \ClassInsertManipulator $ classInsertManipulator , \Rector \NodeManipulator \ClassMethodAssignManipulator $ classMethodAssignManipulator , NodeFactory $ nodeFactory , \Rector \NodeManipulator \StmtsManipulator $ stmtsManipulator , PhpVersionProvider $ phpVersionProvider , PropertyPresenceChecker $ propertyPresenceChecker , NodeNameResolver $ nodeNameResolver , AutowiredClassMethodOrPropertyAnalyzer $ autowiredClassMethodOrPropertyAnalyzer , ReflectionResolver $ reflectionResolver )
71+ /**
72+ * @readonly
73+ */
74+ private AstResolver $ astResolver ;
75+ public function __construct (\Rector \NodeManipulator \ClassInsertManipulator $ classInsertManipulator , \Rector \NodeManipulator \ClassMethodAssignManipulator $ classMethodAssignManipulator , NodeFactory $ nodeFactory , \Rector \NodeManipulator \StmtsManipulator $ stmtsManipulator , PhpVersionProvider $ phpVersionProvider , PropertyPresenceChecker $ propertyPresenceChecker , NodeNameResolver $ nodeNameResolver , AutowiredClassMethodOrPropertyAnalyzer $ autowiredClassMethodOrPropertyAnalyzer , ReflectionResolver $ reflectionResolver , AstResolver $ astResolver )
6876 {
6977 $ this ->classInsertManipulator = $ classInsertManipulator ;
7078 $ this ->classMethodAssignManipulator = $ classMethodAssignManipulator ;
@@ -75,6 +83,44 @@ public function __construct(\Rector\NodeManipulator\ClassInsertManipulator $clas
7583 $ this ->nodeNameResolver = $ nodeNameResolver ;
7684 $ this ->autowiredClassMethodOrPropertyAnalyzer = $ autowiredClassMethodOrPropertyAnalyzer ;
7785 $ this ->reflectionResolver = $ reflectionResolver ;
86+ $ this ->astResolver = $ astResolver ;
87+ }
88+ private function resolveConstruct (Class_ $ class ) : ?ClassMethod
89+ {
90+ /** @var ClassMethod|null $constructorMethod */
91+ $ constructorMethod = $ class ->getMethod (MethodName::CONSTRUCT );
92+ // exists in current class
93+ if ($ constructorMethod instanceof ClassMethod) {
94+ return $ constructorMethod ;
95+ }
96+ // lookup parent, found first found (nearest parent constructor to follow)
97+ $ classReflection = $ this ->reflectionResolver ->resolveClassReflection ($ class );
98+ if (!$ classReflection instanceof ClassReflection) {
99+ return null ;
100+ }
101+ $ ancestors = \array_filter ($ classReflection ->getAncestors (), static fn (ClassReflection $ ancestor ): bool => $ ancestor ->getName () !== $ classReflection ->getName ());
102+ foreach ($ ancestors as $ ancestor ) {
103+ if (!$ ancestor ->hasNativeMethod (MethodName::CONSTRUCT )) {
104+ continue ;
105+ }
106+ $ parentClass = $ this ->astResolver ->resolveClassFromClassReflection ($ ancestor );
107+ if (!$ parentClass instanceof ClassLike) {
108+ continue ;
109+ }
110+ $ parentConstructorMethod = $ parentClass ->getMethod (MethodName::CONSTRUCT );
111+ if (!$ parentConstructorMethod instanceof ClassMethod) {
112+ continue ;
113+ }
114+ // only if it has parameters to check
115+ // early returns as nearest parent construct
116+ if ($ parentConstructorMethod ->params === []) {
117+ return null ;
118+ }
119+ // reprint parent method node to avoid invalid tokens
120+ $ this ->nodeFactory ->createReprintedNode ($ parentConstructorMethod );
121+ return $ parentConstructorMethod ;
122+ }
123+ return null ;
78124 }
79125 public function addConstructorDependency (Class_ $ class , PropertyMetadata $ propertyMetadata ) : void
80126 {
@@ -93,9 +139,10 @@ public function addConstructorDependency(Class_ $class, PropertyMetadata $proper
93139 $ this ->classMethodAssignManipulator ->addParameterAndAssignToMethod ($ autowireClassMethod , $ propertyMetadata ->getName (), $ propertyMetadata ->getType (), $ assign );
94140 return ;
95141 }
142+ $ constructClassMethod = $ this ->resolveConstruct ($ class );
96143 // add PHP 8.0 promoted property
97144 if ($ this ->shouldAddPromotedProperty ($ class , $ propertyMetadata )) {
98- $ this ->addPromotedProperty ($ class , $ propertyMetadata );
145+ $ this ->addPromotedProperty ($ class , $ propertyMetadata, $ constructClassMethod );
99146 return ;
100147 }
101148 $ assign = $ this ->nodeFactory ->createPropertyAssignment ($ propertyMetadata ->getName ());
@@ -106,15 +153,25 @@ public function addConstructorDependency(Class_ $class, PropertyMetadata $proper
106153 */
107154 public function addConstructorDependencyWithCustomAssign (Class_ $ class , string $ name , ?Type $ type , Assign $ assign ) : void
108155 {
109- /** @var ClassMethod|null $constructorMethod */
110- $ constructorMethod = $ class ->getMethod (MethodName::CONSTRUCT );
111- if ($ constructorMethod instanceof ClassMethod) {
112- $ this ->classMethodAssignManipulator ->addParameterAndAssignToMethod ($ constructorMethod , $ name , $ type , $ assign );
156+ /** @var ClassMethod|null $constructClassMethod */
157+ $ constructClassMethod = $ this ->resolveConstruct ($ class );
158+ if ($ constructClassMethod instanceof ClassMethod) {
159+ if (!$ class ->getMethod (MethodName::CONSTRUCT ) instanceof ClassMethod) {
160+ $ parentArgs = [];
161+ foreach ($ constructClassMethod ->params as $ originalParam ) {
162+ $ parentArgs [] = new Arg (new Variable ((string ) $ this ->nodeNameResolver ->getName ($ originalParam ->var )));
163+ }
164+ $ constructClassMethod ->stmts = [new Expression (new StaticCall (new Name (ObjectReference::PARENT ), MethodName::CONSTRUCT , $ parentArgs ))];
165+ $ this ->classInsertManipulator ->addAsFirstMethod ($ class , $ constructClassMethod );
166+ $ this ->classMethodAssignManipulator ->addParameterAndAssignToMethod ($ constructClassMethod , $ name , $ type , $ assign );
167+ } else {
168+ $ this ->classMethodAssignManipulator ->addParameterAndAssignToMethod ($ constructClassMethod , $ name , $ type , $ assign );
169+ }
113170 return ;
114171 }
115- $ constructorMethod = $ this ->nodeFactory ->createPublicMethod (MethodName::CONSTRUCT );
116- $ this ->classMethodAssignManipulator ->addParameterAndAssignToMethod ($ constructorMethod , $ name , $ type , $ assign );
117- $ this ->classInsertManipulator ->addAsFirstMethod ($ class , $ constructorMethod );
172+ $ constructClassMethod = $ this ->nodeFactory ->createPublicMethod (MethodName::CONSTRUCT );
173+ $ this ->classMethodAssignManipulator ->addParameterAndAssignToMethod ($ constructClassMethod , $ name , $ type , $ assign );
174+ $ this ->classInsertManipulator ->addAsFirstMethod ($ class , $ constructClassMethod );
118175 }
119176 /**
120177 * @api doctrine
@@ -140,16 +197,26 @@ public function addStmtsToConstructorIfNotThereYet(Class_ $class, array $stmts)
140197 }
141198 $ classMethod ->stmts = \array_merge ($ stmts , (array ) $ classMethod ->stmts );
142199 }
143- private function addPromotedProperty (Class_ $ class , PropertyMetadata $ propertyMetadata ) : void
200+ private function addPromotedProperty (Class_ $ class , PropertyMetadata $ propertyMetadata, ? ClassMethod $ constructClassMethod ) : void
144201 {
145- $ constructClassMethod = $ class ->getMethod (MethodName::CONSTRUCT );
146202 $ param = $ this ->nodeFactory ->createPromotedPropertyParam ($ propertyMetadata );
147203 if ($ constructClassMethod instanceof ClassMethod) {
148204 // parameter is already added
149205 if ($ this ->hasMethodParameter ($ constructClassMethod , $ propertyMetadata ->getName ())) {
150206 return ;
151207 }
152- $ constructClassMethod ->params [] = $ param ;
208+ // found construct, but only on parent, add to current class
209+ if (!$ class ->getMethod (MethodName::CONSTRUCT ) instanceof ClassMethod) {
210+ $ parentArgs = [];
211+ foreach ($ constructClassMethod ->params as $ originalParam ) {
212+ $ parentArgs [] = new Arg (new Variable ((string ) $ this ->nodeNameResolver ->getName ($ originalParam ->var )));
213+ }
214+ $ constructClassMethod ->params [] = $ param ;
215+ $ constructClassMethod ->stmts = [new Expression (new StaticCall (new Name (ObjectReference::PARENT ), MethodName::CONSTRUCT , $ parentArgs ))];
216+ $ this ->classInsertManipulator ->addAsFirstMethod ($ class , $ constructClassMethod );
217+ } else {
218+ $ constructClassMethod ->params [] = $ param ;
219+ }
153220 } else {
154221 $ constructClassMethod = $ this ->nodeFactory ->createPublicMethod (MethodName::CONSTRUCT );
155222 $ constructClassMethod ->params [] = $ param ;
0 commit comments