Skip to content

Commit 8151e1a

Browse files
committed
Fix issues with Collection<foo, bar>
1 parent bd17a80 commit 8151e1a

File tree

3 files changed

+48
-7
lines changed

3 files changed

+48
-7
lines changed

src/Rules/Doctrine/ORM/EntityRelationRule.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public function processNode(Node $node, Scope $scope): array
8585
}
8686

8787
$columnType = null;
88+
$toMany = false;
8889
if ((bool) ($associationMapping['type'] & 3)) { // ClassMetadataInfo::TO_ONE
8990
$columnType = new ObjectType($associationMapping['targetEntity']);
9091
if ($identifier !== null && $identifier === $propertyName) {
@@ -96,6 +97,7 @@ public function processNode(Node $node, Scope $scope): array
9697
$columnType = TypeCombinator::addNull($columnType);
9798
}
9899
} elseif ((bool) ($associationMapping['type'] & 12)) { // ClassMetadataInfo::TO_MANY
100+
$toMany = true;
99101
$columnType = TypeCombinator::intersect(
100102
new ObjectType('Doctrine\Common\Collections\Collection'),
101103
new IterableType(new MixedType(), new ObjectType($associationMapping['targetEntity']))
@@ -108,13 +110,26 @@ public function processNode(Node $node, Scope $scope): array
108110
if (get_class($propertyWritableType) === MixedType::class || $propertyWritableType instanceof ErrorType || $propertyWritableType instanceof NeverType) {
109111
return [];
110112
}
111-
if (!$propertyWritableType->isSuperTypeOf($columnType)->yes()) {
113+
114+
$collectionObjectType = new ObjectType('Doctrine\Common\Collections\Collection');
115+
$propertyWritableTypeToCheckAgainst = $propertyWritableType;
116+
if (
117+
$toMany
118+
&& $collectionObjectType->isSuperTypeOf($propertyWritableType)->yes()
119+
&& $propertyWritableType->isIterable()->yes()
120+
) {
121+
$propertyWritableTypeToCheckAgainst = TypeCombinator::intersect(
122+
$collectionObjectType,
123+
new IterableType(new MixedType(true), $propertyWritableType->getIterableValueType())
124+
);
125+
}
126+
if (!$propertyWritableTypeToCheckAgainst->isSuperTypeOf($columnType)->yes()) {
112127
$errors[] = sprintf(
113128
'Property %s::$%s type mapping mismatch: database can contain %s but property expects %s.',
114129
$className,
115130
$propertyName,
116131
$columnType->describe(VerbosityLevel::typeOnly()),
117-
$property->getWritableType()->describe(VerbosityLevel::typeOnly())
132+
$propertyWritableType->describe(VerbosityLevel::typeOnly())
118133
);
119134
}
120135
if (!$columnType->isSuperTypeOf($property->getReadableType())->yes()) {

tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,31 @@ public function testRule(string $file, array $expectedErrors): void
3131
}
3232

3333
/**
34-
* @return \Iterator<string, mixed[]>
34+
* @return \Iterator<mixed[]>
3535
*/
3636
public function ruleProvider(): Iterator
3737
{
38-
yield 'nice entity' => [__DIR__ . '/data/EntityWithRelations.php', []];
38+
yield [
39+
__DIR__ . '/data/EntityWithRelations.php',
40+
[
41+
[
42+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithRelations::$genericCollection type mapping mismatch: property can contain Doctrine\Common\Collections\Collection<PHPStan\Rules\Doctrine\ORM\AnotherEntity> but database expects Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
43+
59,
44+
],
45+
[
46+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithRelations::$genericCollection4 type mapping mismatch: property can contain Doctrine\Common\Collections\Collection but database expects Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
47+
77,
48+
],
49+
[
50+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithRelations::$genericCollection5 type mapping mismatch: database can contain Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity> but property expects Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\MyEntity>.',
51+
83,
52+
],
53+
[
54+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithRelations::$genericCollection5 type mapping mismatch: property can contain Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\MyEntity> but database expects Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
55+
83,
56+
],
57+
],
58+
];
3959

4060
yield 'one to one' => [__DIR__ . '/data/EntityWithBrokenOneToOneRelations.php',
4161
[

tests/Rules/Doctrine/ORM/data/EntityWithRelations.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,15 @@ class EntityWithRelations
7171
private $genericCollection3;
7272

7373
/**
74-
* @ORM\OneToMany(targetEntity="PHPStan\Rules\Doctrine\ORM\AnotherEntity")
75-
* @var \Doctrine\Common\Collections\Collection<int, AnotherEntity>
74+
* @ORM\OneToMany(targetEntity="PHPStan\Rules\Doctrine\ORM\AnotherEntity", mappedBy="manyToOne")
75+
* @var \Doctrine\Common\Collections\Collection
76+
*/
77+
private $genericCollection4;
78+
79+
/**
80+
* @ORM\OneToMany(targetEntity="PHPStan\Rules\Doctrine\ORM\AnotherEntity", mappedBy="manyToOne")
81+
* @var \Doctrine\Common\Collections\Collection&iterable<MyEntity>
7682
*/
77-
private $brokenCollectionAnnotation;
83+
private $genericCollection5;
7884

7985
}

0 commit comments

Comments
 (0)