Skip to content

Commit 77d3d8e

Browse files
committed
Check if embedded class matches property's type
1 parent 5762a21 commit 77d3d8e

File tree

5 files changed

+115
-0
lines changed

5 files changed

+115
-0
lines changed

src/Rules/Doctrine/ORM/EntityColumnRule.php

+27
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace PHPStan\Rules\Doctrine\ORM;
44

5+
use Doctrine\ORM\Mapping\ClassMetadata;
56
use PhpParser\Node;
67
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\ClassReflection;
79
use PHPStan\Reflection\MissingPropertyFromReflectionException;
10+
use PHPStan\Reflection\Php\PhpPropertyReflection;
811
use PHPStan\Rules\Rule;
912
use PHPStan\Type\ArrayType;
1013
use PHPStan\Type\Doctrine\DescriptorNotRegisteredException;
@@ -13,6 +16,7 @@
1316
use PHPStan\Type\ErrorType;
1417
use PHPStan\Type\MixedType;
1518
use PHPStan\Type\NeverType;
19+
use PHPStan\Type\ObjectType;
1620
use PHPStan\Type\Type;
1721
use PHPStan\Type\TypeCombinator;
1822
use PHPStan\Type\TypeTraverser;
@@ -86,6 +90,10 @@ public function processNode(Node $node, Scope $scope): array
8690
return [];
8791
}
8892

93+
if (isset($metadata->embeddedClasses[$propertyName])) {
94+
return $this->checkEmbeddedObject($class, $property, $metadata, $propertyName);
95+
}
96+
8997
if (!isset($metadata->fieldMappings[$propertyName])) {
9098
return [];
9199
}
@@ -160,4 +168,23 @@ public function processNode(Node $node, Scope $scope): array
160168
return $errors;
161169
}
162170

171+
private function checkEmbeddedObject(ClassReflection $class, PhpPropertyReflection $property, ClassMetadata $metadata, string $propertyName): array
172+
{
173+
$errors = [];
174+
$embeddedClass = $metadata->embeddedClasses[$propertyName];
175+
$propertyWritableType = $property->getWritableType();
176+
$accordingToMapping = new ObjectType($embeddedClass['class']);
177+
if (!TypeCombinator::removeNull($propertyWritableType)->equals($accordingToMapping)) {
178+
$errors[] = sprintf(
179+
'Property %s::$%s type mapping mismatch: mapping specifies %s but property expects %s.',
180+
$class->getName(),
181+
$propertyName,
182+
$accordingToMapping->describe(VerbosityLevel::typeOnly()),
183+
$propertyWritableType->describe(VerbosityLevel::typeOnly())
184+
);
185+
}
186+
187+
return $errors;
188+
}
189+
163190
}

tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php

+15
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,21 @@ public function testCustomType(): void
153153
]);
154154
}
155155

156+
public function testEmbedded(): void
157+
{
158+
$this->analyse([__DIR__ . '/data/EntityWithEmbeddable.php'], []);
159+
}
160+
161+
public function testEmbeddedWithWrongTypeHint(): void
162+
{
163+
$this->analyse([__DIR__ . '/data/EntityWithBrokenEmbeddable.php'], [
164+
[
165+
'Property PHPStan\Rules\Doctrine\ORM\EntityWithBrokenEmbeddable::$embedded type mapping mismatch: mapping specifies PHPStan\Rules\Doctrine\ORM\Embeddable but property expects int.',
166+
24,
167+
],
168+
]);
169+
}
170+
156171
public function testUnknownType(): void
157172
{
158173
$this->analyse([__DIR__ . '/data/EntityWithUnknownType.php'], [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Doctrine\ORM;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
/**
8+
* @ORM\Embeddable()
9+
*/
10+
class Embeddable
11+
{
12+
/**
13+
* @ORM\Column(type="string")
14+
* @var string
15+
*/
16+
private $one;
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Doctrine\ORM;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
/**
8+
* @ORM\Entity()
9+
*/
10+
class EntityWithBrokenEmbeddable
11+
{
12+
13+
/**
14+
* @ORM\Id()
15+
* @ORM\Column(type="integer")
16+
* @var int
17+
*/
18+
private $id;
19+
20+
/**
21+
* @ORM\Embedded(class=Embeddable::class)
22+
* @var int
23+
*/
24+
private $embedded;
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Doctrine\ORM;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
/**
8+
* @ORM\Entity()
9+
*/
10+
class EntityWithEmbeddable
11+
{
12+
13+
/**
14+
* @ORM\Id()
15+
* @ORM\Column(type="integer")
16+
* @var int
17+
*/
18+
private $id;
19+
20+
/**
21+
* @ORM\Embedded(class=Embeddable::class)
22+
* @var Embeddable
23+
*/
24+
private $embedded;
25+
26+
/**
27+
* @ORM\Embedded(class=Embeddable::class)
28+
* @var ?Embeddable
29+
*/
30+
private $nullable;
31+
}

0 commit comments

Comments
 (0)