Skip to content

Commit a9b6b72

Browse files
malarzmbeberlei
andauthored
Fix inherited embeddables and nesting after AnnotationDriver change doctrine#8006 (doctrine#8036)
* Add test case * Treat parent embeddables as mapped superclasses * [doctrineGH-8031] Bugfix: Get working again on nested embeddables in inherited embeddables. * Housekeeping: CS * Update note on limitations * [doctrineGH-8031] Verify assocations still do not work with Embeddables. * Housekeeping: CS Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
1 parent cd905ff commit a9b6b72

File tree

4 files changed

+174
-8
lines changed

4 files changed

+174
-8
lines changed

docs/en/tutorials/embeddables.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ or address are the primary use case for this feature.
88

99
.. note::
1010

11-
Embeddables can only contain properties with basic ``@Column`` mapping.
11+
Embeddables can not contain references to entities. They can however compose
12+
other embeddables in addition to holding properties with basic ``@Column``
13+
mapping.
1214

1315
For the purposes of this tutorial, we will assume that you have a ``User``
1416
class in your application and you would like to store an address in

lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php

+6-7
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Doctrine\ORM\Id\IdentityGenerator;
3232
use Doctrine\ORM\ORMException;
3333
use ReflectionException;
34+
use function assert;
3435

3536
/**
3637
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
@@ -401,10 +402,10 @@ private function getShortName($className)
401402
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
402403
{
403404
foreach ($parentClass->fieldMappings as $mapping) {
404-
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
405+
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass && ! $parentClass->isEmbeddedClass) {
405406
$mapping['inherited'] = $parentClass->name;
406407
}
407-
if ( ! isset($mapping['declared'])) {
408+
if (! isset($mapping['declared'])) {
408409
$mapping['declared'] = $parentClass->name;
409410
}
410411
$subClass->addInheritedFieldMapping($mapping);
@@ -469,10 +470,6 @@ private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetad
469470
private function addNestedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass, $prefix)
470471
{
471472
foreach ($subClass->embeddedClasses as $property => $embeddableClass) {
472-
if (isset($embeddableClass['inherited'])) {
473-
continue;
474-
}
475-
476473
$embeddableMetadata = $this->getMetadataFor($embeddableClass['class']);
477474

478475
$parentClass->mapEmbedded(
@@ -780,7 +777,9 @@ protected function getDriver()
780777
*/
781778
protected function isEntity(ClassMetadataInterface $class)
782779
{
783-
return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
780+
assert($class instanceof ClassMetadata);
781+
782+
return $class->isMappedSuperclass === false && $class->isEmbeddedClass === false;
784783
}
785784

786785
/**

lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php

+2
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
277277
/* @var $property \ReflectionProperty */
278278
foreach ($class->getProperties() as $property) {
279279
if ($metadata->isMappedSuperclass && ! $property->isPrivate()
280+
||
281+
$metadata->isEmbeddedClass && $property->getDeclaringClass()->getName() !== $class->getName()
280282
||
281283
$metadata->isInheritedField($property->name)
282284
||
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket;
6+
7+
use Doctrine\Tests\OrmFunctionalTestCase;
8+
9+
class GH8031Test extends OrmFunctionalTestCase
10+
{
11+
protected function setUp()
12+
{
13+
parent::setUp();
14+
15+
$this->setUpEntitySchema([
16+
GH8031Invoice::class,
17+
]);
18+
}
19+
20+
public function testEntityIsFetched()
21+
{
22+
$entity = new GH8031Invoice(new GH8031InvoiceCode(1, 2020, new GH8031Nested(10)));
23+
$this->_em->persist($entity);
24+
$this->_em->flush();
25+
$this->_em->clear();
26+
27+
/** @var GH8031Invoice $fetched */
28+
$fetched = $this->_em->find(GH8031Invoice::class, $entity->getId());
29+
$this->assertInstanceOf(GH8031Invoice::class, $fetched);
30+
$this->assertSame(1, $fetched->getCode()->getNumber());
31+
$this->assertSame(2020, $fetched->getCode()->getYear());
32+
33+
$this->_em->clear();
34+
$this->assertCount(
35+
1,
36+
$this->_em->getRepository(GH8031Invoice::class)->findBy([], ['code.number' => 'ASC'])
37+
);
38+
}
39+
40+
public function testEmbeddableWithAssociationNotAllowed()
41+
{
42+
$cm = $this->_em->getClassMetadata(GH8031EmbeddableWithAssociation::class);
43+
44+
$this->assertArrayHasKey('invoice', $cm->associationMappings);
45+
46+
$cm = $this->_em->getClassMetadata(GH8031Invoice::class);
47+
48+
$this->assertCount(0, $cm->associationMappings);
49+
}
50+
}
51+
52+
/**
53+
* @Embeddable
54+
*/
55+
class GH8031EmbeddableWithAssociation
56+
{
57+
/** @ManyToOne(targetEntity=GH8031Invoice::class) */
58+
public $invoice;
59+
}
60+
61+
/**
62+
* @Embeddable
63+
*/
64+
class GH8031Nested
65+
{
66+
/**
67+
* @Column(type="integer", name="number", length=6)
68+
* @var int
69+
*/
70+
protected $number;
71+
72+
public function __construct(int $number)
73+
{
74+
$this->number = $number;
75+
}
76+
77+
public function getNumber() : int
78+
{
79+
return $this->number;
80+
}
81+
}
82+
83+
/**
84+
* @Embeddable
85+
*/
86+
class GH8031InvoiceCode extends GH8031AbstractYearSequenceValue
87+
{
88+
}
89+
90+
/**
91+
* @Embeddable
92+
*/
93+
abstract class GH8031AbstractYearSequenceValue
94+
{
95+
/**
96+
* @Column(type="integer", name="number", length=6)
97+
* @var int
98+
*/
99+
protected $number;
100+
101+
/**
102+
* @Column(type="smallint", name="year", length=4)
103+
* @var int
104+
*/
105+
protected $year;
106+
107+
/** @Embedded(class=GH8031Nested::class) */
108+
protected $nested;
109+
110+
public function __construct(int $number, int $year, GH8031Nested $nested)
111+
{
112+
$this->number = $number;
113+
$this->year = $year;
114+
$this->nested = $nested;
115+
}
116+
117+
public function getNumber() : int
118+
{
119+
return $this->number;
120+
}
121+
122+
public function getYear() : int
123+
{
124+
return $this->year;
125+
}
126+
}
127+
128+
/**
129+
* @Entity
130+
*/
131+
class GH8031Invoice
132+
{
133+
/**
134+
* @Id
135+
* @GeneratedValue
136+
* @Column(type="integer")
137+
*/
138+
private $id;
139+
140+
/**
141+
* @Embedded(class=GH8031InvoiceCode::class)
142+
* @var GH8031InvoiceCode
143+
*/
144+
private $code;
145+
146+
/** @Embedded(class=GH8031EmbeddableWithAssociation::class) */
147+
private $embeddedAssoc;
148+
149+
public function __construct(GH8031InvoiceCode $code)
150+
{
151+
$this->code = $code;
152+
}
153+
154+
public function getId()
155+
{
156+
return $this->id;
157+
}
158+
159+
public function getCode() : GH8031InvoiceCode
160+
{
161+
return $this->code;
162+
}
163+
}

0 commit comments

Comments
 (0)