diff --git a/rules-tests/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector/Fixture/include_bare_collection.php.inc b/rules-tests/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector/Fixture/include_bare_collection.php.inc new file mode 100644 index 00000000..32debfa0 --- /dev/null +++ b/rules-tests/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector/Fixture/include_bare_collection.php.inc @@ -0,0 +1,45 @@ + + */ + protected Collection $items; +} + +?> +----- + + */ + protected Collection $items; + public function __construct() + { + $this->items = new \Doctrine\Common\Collections\ArrayCollection(); + } +} + +?> diff --git a/rules-tests/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector/Fixture/include_bare_nullable_collection.php.inc b/rules-tests/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector/Fixture/include_bare_nullable_collection.php.inc new file mode 100644 index 00000000..30ee0d76 --- /dev/null +++ b/rules-tests/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector/Fixture/include_bare_nullable_collection.php.inc @@ -0,0 +1,45 @@ + + */ + protected ?Collection $items; +} + +?> +----- + + */ + protected ?Collection $items; + public function __construct() + { + $this->items = new \Doctrine\Common\Collections\ArrayCollection(); + } +} + +?> diff --git a/rules/TypedCollections/NodeAnalyzer/CollectionPropertyDetector.php b/rules/TypedCollections/NodeAnalyzer/CollectionPropertyDetector.php new file mode 100644 index 00000000..97f7f37f --- /dev/null +++ b/rules/TypedCollections/NodeAnalyzer/CollectionPropertyDetector.php @@ -0,0 +1,53 @@ +type instanceof Node) { + return false; + } + + // 1. direct type + if ($this->nodeNameResolver->isName($property->type, DoctrineClass::COLLECTION)) { + return true; + } + + // 2. union type + if ($property->type instanceof UnionType) { + $unionType = $property->type; + foreach ($unionType->types as $unionedType) { + if ($this->nodeNameResolver->isName($unionedType, DoctrineClass::COLLECTION)) { + return true; + } + } + } + + // 3. nullable type + if ($property->type instanceof NullableType) { + $directType = $property->type->type; + if ($this->nodeNameResolver->isName($directType, DoctrineClass::COLLECTION)) { + return true; + } + } + + return false; + } +} diff --git a/rules/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector.php b/rules/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector.php index e5d6b5f5..e3b656ad 100644 --- a/rules/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector.php +++ b/rules/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use Rector\Doctrine\NodeFactory\ArrayCollectionAssignFactory; +use Rector\Doctrine\TypedCollections\NodeAnalyzer\CollectionPropertyDetector; use Rector\Doctrine\TypedCollections\NodeAnalyzer\EntityLikeClassDetector; use Rector\Doctrine\TypedCollections\NodeModifier\PropertyDefaultNullRemover; use Rector\NodeManipulator\ClassDependencyManipulator; @@ -29,7 +30,8 @@ public function __construct( private readonly ArrayCollectionAssignFactory $arrayCollectionAssignFactory, private readonly ClassDependencyManipulator $classDependencyManipulator, private readonly TestsNodeAnalyzer $testsNodeAnalyzer, - private readonly PropertyDefaultNullRemover $propertyDefaultNullRemover + private readonly PropertyDefaultNullRemover $propertyDefaultNullRemover, + private readonly CollectionPropertyDetector $collectionPropertyDetector ) { } @@ -89,22 +91,13 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->entityLikeClassDetector->detect($node)) { - return null; - } - - if ($this->testsNodeAnalyzer->isInTestClass($node)) { - return null; - } - - if ($node->isAbstract()) { + if ($this->shouldSkipClass($node)) { return null; } $arrayCollectionAssigns = []; - foreach ($node->getProperties() as $property) { - if (! $this->entityLikeClassDetector->isToMany($property)) { + if (! $this->isDefaultArrayCollectionPropertyCandidate($property)) { continue; } @@ -128,4 +121,26 @@ public function refactor(Node $node): ?Node return $node; } + + private function shouldSkipClass(Class_ $class): bool + { + if (! $this->entityLikeClassDetector->detect($class)) { + return true; + } + + if ($this->testsNodeAnalyzer->isInTestClass($class)) { + return true; + } + + return $class->isAbstract(); + } + + private function isDefaultArrayCollectionPropertyCandidate(mixed $property): bool + { + if ($this->entityLikeClassDetector->isToMany($property)) { + return true; + } + + return $this->collectionPropertyDetector->detect($property); + } } diff --git a/rules/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector.php b/rules/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector.php index adffb723..38ca2e3f 100644 --- a/rules/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector.php +++ b/rules/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector.php @@ -89,7 +89,7 @@ public function refactor(Node $node): ?Class_ $hasChanged = true; } - if ($this->refactorNativePropertyType($property)) { + if ($this->refactorNativeUnionPropertyType($property)) { $hasChanged = true; } } @@ -119,7 +119,7 @@ private function isCollectionName(Node $node): bool return $this->isName($node, DoctrineClass::COLLECTION); } - private function refactorNativePropertyType(Property $property): bool + private function refactorNativeUnionPropertyType(Property $property): bool { if (! $property->type instanceof UnionType) { return false;