Skip to content

Commit d9f64ae

Browse files
VincentLangletondrejmirtes
authored andcommitted
Pass allowNullablePropertyForRequiredField to EntityRelationRule
1 parent 613f592 commit d9f64ae

File tree

3 files changed

+149
-5
lines changed

3 files changed

+149
-5
lines changed

rules.neon

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ parameters:
44
reportUnknownTypes: false
55
allowNullablePropertyForRequiredField: false
66

7-
87
parametersSchema:
98
doctrine: structure([
109
repositoryClass: schema(string(), nullable())
@@ -22,7 +21,6 @@ rules:
2221
- PHPStan\Rules\Doctrine\ORM\DqlRule
2322
- PHPStan\Rules\Doctrine\ORM\RepositoryMethodCallRule
2423
- PHPStan\Rules\Doctrine\ORM\EntityNotFinalRule
25-
- PHPStan\Rules\Doctrine\ORM\EntityRelationRule
2624

2725
services:
2826
-
@@ -40,3 +38,9 @@ services:
4038
- phpstan.rules.rule
4139
-
4240
class: PHPStan\Rules\Doctrine\ORM\EntityNotFinalRule
41+
-
42+
class: PHPStan\Rules\Doctrine\ORM\EntityRelationRule
43+
arguments:
44+
allowNullablePropertyForRequiredField: %doctrine.allowNullablePropertyForRequiredField%
45+
tags:
46+
- phpstan.rules.rule

src/Rules/Doctrine/ORM/EntityRelationRule.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,16 @@ class EntityRelationRule implements Rule
2525
/** @var \PHPStan\Type\Doctrine\ObjectMetadataResolver */
2626
private $objectMetadataResolver;
2727

28-
public function __construct(ObjectMetadataResolver $objectMetadataResolver)
28+
/** @var bool */
29+
private $allowNullablePropertyForRequiredField;
30+
31+
public function __construct(
32+
ObjectMetadataResolver $objectMetadataResolver,
33+
bool $allowNullablePropertyForRequiredField
34+
)
2935
{
3036
$this->objectMetadataResolver = $objectMetadataResolver;
37+
$this->allowNullablePropertyForRequiredField = $allowNullablePropertyForRequiredField;
3138
}
3239

3340
public function getNodeType(): string
@@ -136,7 +143,13 @@ public function processNode(Node $node, Scope $scope): array
136143
$propertyWritableType->describe(VerbosityLevel::typeOnly())
137144
);
138145
}
139-
if (!$columnType->isSuperTypeOf($property->getReadableType())->yes()) {
146+
if (
147+
!$columnType->isSuperTypeOf(
148+
$this->allowNullablePropertyForRequiredField
149+
? TypeCombinator::removeNull($property->getReadableType())
150+
: $property->getReadableType()
151+
)->yes()
152+
) {
140153
$errors[] = sprintf(
141154
'Property %s::$%s type mapping mismatch: property can contain %s but database expects %s.',
142155
$className,

tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@
1313
class EntityRelationRuleTest extends RuleTestCase
1414
{
1515

16+
/** @var bool */
17+
private $allowNullablePropertyForRequiredField;
18+
1619
protected function getRule(): Rule
1720
{
1821
return new EntityRelationRule(
19-
new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', null)
22+
new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', null),
23+
$this->allowNullablePropertyForRequiredField
2024
);
2125
}
2226

@@ -27,6 +31,7 @@ protected function getRule(): Rule
2731
*/
2832
public function testRule(string $file, array $expectedErrors): void
2933
{
34+
$this->allowNullablePropertyForRequiredField = false;
3035
$this->analyse([$file], $expectedErrors);
3136
}
3237

@@ -154,4 +159,126 @@ public function ruleProvider(): Iterator
154159
];
155160
}
156161

162+
/**
163+
* @dataProvider ruleWithAllowedNullablePropertyProvider
164+
* @param string $file
165+
* @param mixed[] $expectedErrors
166+
*/
167+
public function testRuleWithAllowedNullableProperty(string $file, array $expectedErrors): void
168+
{
169+
$this->allowNullablePropertyForRequiredField = true;
170+
$this->analyse([$file], $expectedErrors);
171+
}
172+
173+
/**
174+
* @return \Iterator<mixed[]>
175+
*/
176+
public function ruleWithAllowedNullablePropertyProvider(): Iterator
177+
{
178+
yield [
179+
__DIR__ . '/data/EntityWithRelations.php',
180+
[
181+
[
182+
'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>.',
183+
77,
184+
],
185+
[
186+
'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>.',
187+
83,
188+
],
189+
[
190+
'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>.',
191+
83,
192+
],
193+
],
194+
];
195+
196+
yield 'one to one' => [__DIR__ . '/data/EntityWithBrokenOneToOneRelations.php',
197+
[
198+
[
199+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenOneToOneRelations::$oneToOneNullableColumn type mapping mismatch: database can contain PHPStan\Rules\Doctrine\ORM\AnotherEntity|null but property expects PHPStan\Rules\Doctrine\ORM\AnotherEntity.',
200+
37,
201+
],
202+
[
203+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenOneToOneRelations::$oneToOneWrongClass type mapping mismatch: database can contain PHPStan\Rules\Doctrine\ORM\AnotherEntity|null but property expects PHPStan\Rules\Doctrine\ORM\MyEntity|null.',
204+
50,
205+
],
206+
[
207+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenOneToOneRelations::$oneToOneWrongClass type mapping mismatch: property can contain PHPStan\Rules\Doctrine\ORM\MyEntity|null but database expects PHPStan\Rules\Doctrine\ORM\AnotherEntity|null.',
208+
50,
209+
],
210+
]];
211+
212+
yield 'many to one' => [__DIR__ . '/data/EntityWithBrokenManyToOneRelations.php',
213+
[
214+
[
215+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenManyToOneRelations::$manyToOneNullableColumn type mapping mismatch: database can contain PHPStan\Rules\Doctrine\ORM\AnotherEntity|null but property expects PHPStan\Rules\Doctrine\ORM\AnotherEntity.',
216+
37,
217+
],
218+
[
219+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenManyToOneRelations::$manyToOneWrongClass type mapping mismatch: database can contain PHPStan\Rules\Doctrine\ORM\AnotherEntity|null but property expects PHPStan\Rules\Doctrine\ORM\MyEntity|null.',
220+
50,
221+
],
222+
[
223+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenManyToOneRelations::$manyToOneWrongClass type mapping mismatch: property can contain PHPStan\Rules\Doctrine\ORM\MyEntity|null but database expects PHPStan\Rules\Doctrine\ORM\AnotherEntity|null.',
224+
50,
225+
],
226+
]];
227+
228+
yield 'one to many' => [__DIR__ . '/data/EntityWithBrokenOneToManyRelations.php',
229+
[
230+
[
231+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenOneToManyRelations::$oneToManyWithIterableAnnotation type mapping mismatch: property can contain iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity> but database expects Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
232+
24,
233+
],
234+
[
235+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenOneToManyRelations::$oneToManyWithCollectionAnnotation type mapping mismatch: property can contain Doctrine\Common\Collections\Collection but database expects Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
236+
30,
237+
],
238+
[
239+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenOneToManyRelations::$oneToManyWithArrayAnnotation type mapping mismatch: database can contain Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity> but property expects array<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
240+
36,
241+
],
242+
[
243+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenOneToManyRelations::$oneToManyWithArrayAnnotation type mapping mismatch: property can contain array<PHPStan\Rules\Doctrine\ORM\AnotherEntity> but database expects Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
244+
36,
245+
],
246+
]];
247+
248+
yield 'many to many' => [__DIR__ . '/data/EntityWithBrokenManyToManyRelations.php',
249+
[
250+
[
251+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenManyToManyRelations::$manyToManyWithIterableAnnotation type mapping mismatch: property can contain iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity> but database expects Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
252+
24,
253+
],
254+
[
255+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenManyToManyRelations::$manyToManyWithCollectionAnnotation type mapping mismatch: property can contain Doctrine\Common\Collections\Collection but database expects Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
256+
30,
257+
],
258+
[
259+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenManyToManyRelations::$manyToManyWithArrayAnnotation type mapping mismatch: database can contain Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity> but property expects array<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
260+
36,
261+
],
262+
[
263+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenManyToManyRelations::$manyToManyWithArrayAnnotation type mapping mismatch: property can contain array<PHPStan\Rules\Doctrine\ORM\AnotherEntity> but database expects Doctrine\Common\Collections\Collection&iterable<PHPStan\Rules\Doctrine\ORM\AnotherEntity>.',
264+
36,
265+
],
266+
]];
267+
268+
yield 'primary key as relation' => [
269+
__DIR__ . '/data/MyEntityRelationPrimaryKey.php',
270+
[],
271+
];
272+
273+
yield 'primary key as nullable relation' => [
274+
__DIR__ . '/data/MyEntityRelationNullablePrimaryKey.php',
275+
[],
276+
];
277+
278+
yield 'composite primary key' => [
279+
__DIR__ . '/data/CompositePrimaryKeyEntity2.php',
280+
[],
281+
];
282+
}
283+
157284
}

0 commit comments

Comments
 (0)