66
77use Doctrine \Common \Collections \Collection ;
88use PhpParser \Node ;
9+ use PhpParser \Node \Expr ;
910use PhpParser \Node \Name ;
11+ use PhpParser \Node \Name \FullyQualified ;
12+ use PhpParser \Node \Stmt \Class_ ;
1013use PhpParser \Node \Stmt \Property ;
14+ use PhpParser \Node \UnionType ;
1115use PHPStan \PhpDocParser \Ast \PhpDoc \VarTagValueNode ;
12- use PHPStan \ Reflection \ ClassReflection ;
16+ use Rector \ BetterPhpDocParser \ PhpDocInfo \ PhpDocInfo ;
1317use Rector \BetterPhpDocParser \PhpDocInfo \PhpDocInfoFactory ;
1418use Rector \Comments \NodeDocBlock \DocBlockUpdater ;
19+ use Rector \Doctrine \Enum \DoctrineClass ;
1520use Rector \Doctrine \TypedCollections \DocBlockProcessor \UnionCollectionTagValueNodeNarrower ;
16- use Rector \PHPStan \ScopeFetcher ;
1721use Rector \Rector \AbstractRector ;
1822use Symplify \RuleDocGenerator \ValueObject \CodeSample \CodeSample ;
1923use Symplify \RuleDocGenerator \ValueObject \RuleDefinition ;
2024
2125/**
22- * @see \Rector\Doctrine\Tests\TypedCollections\Rector\ClassMethod\NarrowParamUnionToCollectionRector\NarrowParamUnionToCollectionRectorTest
26+ * @see \Rector\Doctrine\Tests\TypedCollections\Rector\Property\NarrowPropertyUnionToCollectionRector\NarrowPropertyUnionToCollectionRectorTest
2327 */
2428final class NarrowPropertyUnionToCollectionRector extends AbstractRector
2529{
@@ -33,7 +37,7 @@ public function __construct(
3337 public function getRuleDefinition (): RuleDefinition
3438 {
3539 return new RuleDefinition (
36- 'Narrow union type to Collection type in property docblock ' ,
40+ 'Narrow union type to Collection type in property docblock and native type declaration ' ,
3741 [
3842 new CodeSample (
3943 <<<'CODE_SAMPLE'
@@ -66,48 +70,99 @@ class SomeClass
6670
6771 public function getNodeTypes (): array
6872 {
69- return [Property ::class];
73+ return [Class_ ::class];
7074 }
7175
7276 /**
73- * @param Property $node
77+ * @param Class_ $node
7478 */
75- public function refactor (Node $ node ): ?Property
79+ public function refactor (Node $ node ): ?Class_
7680 {
77- if ($ node ->isAbstract ()) {
78- return null ;
81+ $ hasChanged = false ;
82+ foreach ($ node ->getProperties () as $ property ) {
83+ if ($ property ->isAbstract ()) {
84+ continue ;
85+ }
86+
87+ if ($ this ->refactorPropertyDocBlock ($ property )) {
88+ $ hasChanged = true ;
89+ }
90+
91+ if ($ this ->refactorNativePropertyType ($ property )) {
92+ $ hasChanged = true ;
93+ }
7994 }
8095
81- $ scope = ScopeFetcher::fetch ($ node );
82- $ classReflection = $ scope ->getClassReflection ();
83- if (! $ classReflection instanceof ClassReflection) {
84- return null ;
96+ if ($ hasChanged ) {
97+ return $ node ;
8598 }
8699
87- if ($ classReflection ->isInterface ()) {
88- return null ;
100+ return null ;
101+ }
102+
103+ private function hasNativeTypeCollection (Property $ property ): bool
104+ {
105+ if (! $ property ->type instanceof Name) {
106+ return false ;
89107 }
90108
91- $ propertyPhpDocInfo = $ this ->phpDocInfoFactory ->createFromNodeOrEmpty ($ node );
109+ return $ this ->isName ($ property ->type , Collection::class);
110+ }
92111
93- $ varTagValueNode = $ propertyPhpDocInfo ->getVarTagValueNode ();
94- if (! $ varTagValueNode instanceof VarTagValueNode) {
95- return null ;
112+ private function isCollectionName (Node $ node ): bool
113+ {
114+ if (! $ node instanceof Name) {
115+ return false ;
116+ }
117+
118+ return $ this ->isName ($ node , DoctrineClass::COLLECTION );
119+ }
120+
121+ private function refactorNativePropertyType (Property $ property ): bool
122+ {
123+ if (! $ property ->type instanceof UnionType) {
124+ return false ;
96125 }
97126
98- $ hasNativeCollectionType = false ;
99- if ($ node ->type instanceof Name && $ this ->isName ($ node ->type , Collection::class)) {
100- $ hasNativeCollectionType = true ;
127+ foreach ($ property ->type ->types as $ uniontedType ) {
128+ if (! $ this ->isCollectionName ($ uniontedType )) {
129+ continue ;
130+ }
131+
132+ // narrow to pure collection
133+ $ property ->type = new FullyQualified (DoctrineClass::COLLECTION );
134+
135+ // remove default, as will be defined in constructor by another rule
136+ if ($ property ->props [0 ]->default instanceof Expr) {
137+ $ property ->props [0 ]->default = null ;
138+ }
139+
140+ return true ;
141+ }
142+
143+ return false ;
144+ }
145+
146+ private function refactorPropertyDocBlock (Property $ property ): bool
147+ {
148+ $ propertyPhpDocInfo = $ this ->phpDocInfoFactory ->createFromNode ($ property );
149+ if (! $ propertyPhpDocInfo instanceof PhpDocInfo) {
150+ return false ;
101151 }
102152
103- $ hasChanged = $ this -> unionCollectionTagValueNodeNarrower -> narrow ( $ varTagValueNode , $ hasNativeCollectionType );
153+ $ varTagValueNode = $ propertyPhpDocInfo -> getVarTagValueNode ( );
104154
105- if ($ hasChanged === false ) {
106- return null ;
155+ if (! $ varTagValueNode instanceof VarTagValueNode ) {
156+ return false ;
107157 }
108158
109- $ this ->docBlockUpdater ->updateRefactoredNodeWithPhpDocInfo ($ node );
159+ $ hasNativeCollectionType = $ this ->hasNativeTypeCollection ($ property );
160+
161+ if ($ this ->unionCollectionTagValueNodeNarrower ->narrow ($ varTagValueNode , $ hasNativeCollectionType )) {
162+ $ this ->docBlockUpdater ->updateRefactoredNodeWithPhpDocInfo ($ property );
163+ return true ;
164+ }
110165
111- return $ node ;
166+ return false ;
112167 }
113168}
0 commit comments