Skip to content

Commit 31df930

Browse files
committed
initialize default array even for non-entity-property collections
1 parent 6eb6285 commit 31df930

File tree

5 files changed

+171
-14
lines changed

5 files changed

+171
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Rector\Doctrine\Tests\TypedCollections\Rector\Class_\InitializeCollectionInConstructorRector\Fixture;
4+
5+
use Doctrine\Common\Collections\Collection;
6+
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
7+
use Rector\Doctrine\Tests\TypedCollections\Rector\Class_\InitializeCollectionInConstructorRector\Source\RelatedEntity;
8+
9+
/**
10+
* @MongoDB\Document()
11+
*/
12+
final class IncludeBareCollection
13+
{
14+
/**
15+
* @var Collection<int, RelatedEntity>
16+
*/
17+
protected Collection $items;
18+
}
19+
20+
?>
21+
-----
22+
<?php
23+
24+
namespace Rector\Doctrine\Tests\TypedCollections\Rector\Class_\InitializeCollectionInConstructorRector\Fixture;
25+
26+
use Doctrine\Common\Collections\Collection;
27+
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
28+
use Rector\Doctrine\Tests\TypedCollections\Rector\Class_\InitializeCollectionInConstructorRector\Source\RelatedEntity;
29+
30+
/**
31+
* @MongoDB\Document()
32+
*/
33+
final class IncludeBareCollection
34+
{
35+
/**
36+
* @var Collection<int, RelatedEntity>
37+
*/
38+
protected Collection $items;
39+
public function __construct()
40+
{
41+
$this->items = new \Doctrine\Common\Collections\ArrayCollection();
42+
}
43+
}
44+
45+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Rector\Doctrine\Tests\TypedCollections\Rector\Class_\InitializeCollectionInConstructorRector\Fixture;
4+
5+
use Doctrine\Common\Collections\Collection;
6+
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
7+
use Rector\Doctrine\Tests\TypedCollections\Rector\Class_\InitializeCollectionInConstructorRector\Source\RelatedEntity;
8+
9+
/**
10+
* @MongoDB\Document()
11+
*/
12+
final class IncludeBareNullableCollection
13+
{
14+
/**
15+
* @var Collection<int, RelatedEntity>
16+
*/
17+
protected ?Collection $items;
18+
}
19+
20+
?>
21+
-----
22+
<?php
23+
24+
namespace Rector\Doctrine\Tests\TypedCollections\Rector\Class_\InitializeCollectionInConstructorRector\Fixture;
25+
26+
use Doctrine\Common\Collections\Collection;
27+
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
28+
use Rector\Doctrine\Tests\TypedCollections\Rector\Class_\InitializeCollectionInConstructorRector\Source\RelatedEntity;
29+
30+
/**
31+
* @MongoDB\Document()
32+
*/
33+
final class IncludeBareNullableCollection
34+
{
35+
/**
36+
* @var Collection<int, RelatedEntity>
37+
*/
38+
protected ?Collection $items;
39+
public function __construct()
40+
{
41+
$this->items = new \Doctrine\Common\Collections\ArrayCollection();
42+
}
43+
}
44+
45+
?>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Doctrine\TypedCollections\NodeAnalyzer;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\NullableType;
9+
use PhpParser\Node\Stmt\Property;
10+
use PhpParser\Node\UnionType;
11+
use Rector\Doctrine\Enum\DoctrineClass;
12+
use Rector\NodeNameResolver\NodeNameResolver;
13+
14+
final readonly class CollectionPropertyDetector
15+
{
16+
public function __construct(
17+
private NodeNameResolver $nodeNameResolver
18+
) {
19+
20+
}
21+
22+
public function detect(Property $property): bool
23+
{
24+
if (! $property->type instanceof Node) {
25+
return false;
26+
}
27+
28+
// 1. direct type
29+
if ($this->nodeNameResolver->isName($property->type, DoctrineClass::COLLECTION)) {
30+
return true;
31+
}
32+
33+
// 2. union type
34+
if ($property->type instanceof UnionType) {
35+
$unionType = $property->type;
36+
foreach ($unionType->types as $unionedType) {
37+
if ($this->nodeNameResolver->isName($unionedType, DoctrineClass::COLLECTION)) {
38+
return true;
39+
}
40+
}
41+
}
42+
43+
// 3. nullable type
44+
if ($property->type instanceof NullableType) {
45+
$directType = $property->type->type;
46+
if ($this->nodeNameResolver->isName($directType, DoctrineClass::COLLECTION)) {
47+
return true;
48+
}
49+
}
50+
51+
return false;
52+
}
53+
}

rules/TypedCollections/Rector/Class_/InitializeCollectionInConstructorRector.php

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PhpParser\Node;
88
use PhpParser\Node\Stmt\Class_;
99
use Rector\Doctrine\NodeFactory\ArrayCollectionAssignFactory;
10+
use Rector\Doctrine\TypedCollections\NodeAnalyzer\CollectionPropertyDetector;
1011
use Rector\Doctrine\TypedCollections\NodeAnalyzer\EntityLikeClassDetector;
1112
use Rector\Doctrine\TypedCollections\NodeModifier\PropertyDefaultNullRemover;
1213
use Rector\NodeManipulator\ClassDependencyManipulator;
@@ -29,7 +30,8 @@ public function __construct(
2930
private readonly ArrayCollectionAssignFactory $arrayCollectionAssignFactory,
3031
private readonly ClassDependencyManipulator $classDependencyManipulator,
3132
private readonly TestsNodeAnalyzer $testsNodeAnalyzer,
32-
private readonly PropertyDefaultNullRemover $propertyDefaultNullRemover
33+
private readonly PropertyDefaultNullRemover $propertyDefaultNullRemover,
34+
private readonly CollectionPropertyDetector $collectionPropertyDetector
3335
) {
3436
}
3537

@@ -89,22 +91,13 @@ public function getNodeTypes(): array
8991
*/
9092
public function refactor(Node $node): ?Node
9193
{
92-
if (! $this->entityLikeClassDetector->detect($node)) {
93-
return null;
94-
}
95-
96-
if ($this->testsNodeAnalyzer->isInTestClass($node)) {
97-
return null;
98-
}
99-
100-
if ($node->isAbstract()) {
94+
if ($this->shouldSkipClass($node)) {
10195
return null;
10296
}
10397

10498
$arrayCollectionAssigns = [];
105-
10699
foreach ($node->getProperties() as $property) {
107-
if (! $this->entityLikeClassDetector->isToMany($property)) {
100+
if (! $this->isDefaultArrayCollectionPropertyCandidate($property)) {
108101
continue;
109102
}
110103

@@ -128,4 +121,25 @@ public function refactor(Node $node): ?Node
128121

129122
return $node;
130123
}
124+
125+
private function shouldSkipClass(Class_ $class): bool
126+
{
127+
if (! $this->entityLikeClassDetector->detect($class)) {
128+
return true;
129+
}
130+
131+
if ($this->testsNodeAnalyzer->isInTestClass($class)) {
132+
return true;
133+
}
134+
135+
return $class->isAbstract();
136+
}
137+
138+
private function isDefaultArrayCollectionPropertyCandidate(mixed $property): bool
139+
{
140+
if ($this->entityLikeClassDetector->isToMany($property)) {
141+
return true;
142+
}
143+
return $this->collectionPropertyDetector->detect($property);
144+
}
131145
}

rules/TypedCollections/Rector/Property/NarrowPropertyUnionToCollectionRector.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function refactor(Node $node): ?Class_
8989
$hasChanged = true;
9090
}
9191

92-
if ($this->refactorNativePropertyType($property)) {
92+
if ($this->refactorNativeUnionPropertyType($property)) {
9393
$hasChanged = true;
9494
}
9595
}
@@ -119,7 +119,7 @@ private function isCollectionName(Node $node): bool
119119
return $this->isName($node, DoctrineClass::COLLECTION);
120120
}
121121

122-
private function refactorNativePropertyType(Property $property): bool
122+
private function refactorNativeUnionPropertyType(Property $property): bool
123123
{
124124
if (! $property->type instanceof UnionType) {
125125
return false;

0 commit comments

Comments
 (0)