From bca9d315310dab49407607f401837e07e8a411e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20M=C3=BCller?= Date: Mon, 11 Aug 2014 16:53:18 +0200 Subject: [PATCH 1/2] add support for nesting embeddables --- .../ORM/Mapping/ClassMetadataFactory.php | 38 +++- .../ORM/Mapping/ClassMetadataInfo.php | 45 +++-- lib/Doctrine/ORM/Mapping/MappingException.php | 20 ++- .../Tests/ORM/Functional/ValueObjectsTest.php | 162 ++++++++++++++---- .../ORM/Mapping/XmlMappingDriverTest.php | 4 +- 5 files changed, 212 insertions(+), 57 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 62f81939e..cbb8c0cc9 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -142,14 +142,22 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory } if (!$class->isMappedSuperclass) { - foreach ($class->embeddedClasses as $property => $embeddableClass) { if (isset($embeddableClass['inherited'])) { continue; } + if ($embeddableClass['class'] === $class->name) { + throw MappingException::infiniteEmbeddableNesting($class->name, $property); + } + $embeddableMetadata = $this->getMetadataFor($embeddableClass['class']); + + if ($embeddableMetadata->isEmbeddedClass) { + $this->addNestedEmbeddedClasses($embeddableMetadata, $class, $property); + } + $class->inlineEmbeddable($property, $embeddableMetadata); } } @@ -370,6 +378,34 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory } } + /** + * Adds nested embedded classes metadata to a parent class. + * + * @param ClassMetadata $subClass Sub embedded class metadata to add nested embedded classes metadata from. + * @param ClassMetadata $parentClass Parent class to add nested embedded classes metadata to. + * @param string $prefix Embedded classes' prefix to use for nested embedded classes field names. + */ + private function addNestedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass, $prefix) + { + foreach ($subClass->embeddedClasses as $property => $embeddableClass) { + if (isset($embeddableClass['inherited'])) { + continue; + } + + $embeddableMetadata = $this->getMetadataFor($embeddableClass['class']); + + $parentClass->mapEmbedded(array( + 'fieldName' => $prefix . '.' . $property, + 'class' => $embeddableMetadata->name, + 'columnPrefix' => $embeddableClass['columnPrefix'], + 'declaredField' => $embeddableClass['declaredField'] + ? $prefix . '.' . $embeddableClass['declaredField'] + : $prefix, + 'originalField' => $embeddableClass['originalField'] ?: $property, + )); + } + } + /** * Adds inherited named queries to the subclass mapping. * diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 96a71de18..8f9f8cbd5 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -929,15 +929,31 @@ class ClassMetadataInfo implements ClassMetadata // Restore ReflectionClass and properties $this->reflClass = $reflService->getClass($this->name); + $parentReflFields = array(); + + foreach ($this->embeddedClasses as $property => $embeddedClass) { + if (isset($embeddedClass['declaredField'])) { + $parentReflFields[$property] = new ReflectionEmbeddedProperty( + $parentReflFields[$embeddedClass['declaredField']], + $reflService->getAccessibleProperty( + $this->embeddedClasses[$embeddedClass['declaredField']]['class'], + $embeddedClass['originalField'] + ), + $embeddedClass['class'] + ); + + continue; + } + + $parentReflFields[$property] = $reflService->getAccessibleProperty($this->name, $property); + } + foreach ($this->fieldMappings as $field => $mapping) { if (isset($mapping['declaredField'])) { - $declaringClass = isset($this->embeddedClasses[$mapping['declaredField']]['declared']) - ? $this->embeddedClasses[$mapping['declaredField']]['declared'] : $this->name; - $this->reflFields[$field] = new ReflectionEmbeddedProperty( - $reflService->getAccessibleProperty($declaringClass, $mapping['declaredField']), - $reflService->getAccessibleProperty($this->embeddedClasses[$mapping['declaredField']]['class'], $mapping['originalField']), - $this->embeddedClasses[$mapping['declaredField']]['class'] + $parentReflFields[$mapping['declaredField']], + $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']), + $mapping['originalClass'] ); continue; } @@ -3171,15 +3187,13 @@ class ClassMetadataInfo implements ClassMetadata */ public function mapEmbedded(array $mapping) { - if ($this->isEmbeddedClass) { - throw MappingException::noEmbeddablesInEmbeddable($this->name); - } - $this->assertFieldNotMapped($mapping['fieldName']); $this->embeddedClasses[$mapping['fieldName']] = array( 'class' => $this->fullyQualifiedClassName($mapping['class']), 'columnPrefix' => $mapping['columnPrefix'], + 'declaredField' => isset($mapping['declaredField']) ? $mapping['declaredField'] : null, + 'originalField' => isset($mapping['originalField']) ? $mapping['originalField'] : null, ); } @@ -3192,8 +3206,15 @@ class ClassMetadataInfo implements ClassMetadata public function inlineEmbeddable($property, ClassMetadataInfo $embeddable) { foreach ($embeddable->fieldMappings as $fieldMapping) { - $fieldMapping['declaredField'] = $property; - $fieldMapping['originalField'] = $fieldMapping['fieldName']; + $fieldMapping['originalClass'] = isset($fieldMapping['originalClass']) + ? $fieldMapping['originalClass'] + : $embeddable->name; + $fieldMapping['declaredField'] = isset($fieldMapping['declaredField']) + ? $property . '.' . $fieldMapping['declaredField'] + : $property; + $fieldMapping['originalField'] = isset($fieldMapping['originalField']) + ? $fieldMapping['originalField'] + : $fieldMapping['fieldName']; $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName']; if (! empty($this->embeddedClasses[$property]['columnPrefix'])) { diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index 82d5a4585..7a8eaada6 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -782,11 +782,21 @@ class MappingException extends \Doctrine\ORM\ORMException ); } - public static function noEmbeddablesInEmbeddable($className) + /** + * @param string $className + * @param string $propertyName + * + * @return MappingException + */ + public static function infiniteEmbeddableNesting($className, $propertyName) { - return new self(sprintf( - "You embedded one or more embeddables in embeddable '%s', but this behavior is currently unsupported.", - $className - )); + return new self( + sprintf( + 'Infinite nesting detected for embedded property %s::%s. ' . + 'You cannot embed an embeddable from the same type inside an embeddable.', + $className, + $propertyName + ) + ); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index 4e34004b8..98aea6e47 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -32,6 +32,7 @@ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase $person->address->street = "United States of Tara Street"; $person->address->zip = "12345"; $person->address->city = "funkytown"; + $person->address->country = new DDC93Country('Germany'); // 1. check saving value objects works $this->_em->persist($person); @@ -46,11 +47,14 @@ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('United States of Tara Street', $person->address->street); $this->assertEquals('12345', $person->address->zip); $this->assertEquals('funkytown', $person->address->city); + $this->assertInstanceOf(DDC93Country::CLASSNAME, $person->address->country); + $this->assertEquals('Germany', $person->address->country->name); // 3. check changing value objects works $person->address->street = "Street"; $person->address->zip = "54321"; $person->address->city = "another town"; + $person->address->country->name = "United States of America"; $this->_em->flush(); $this->_em->clear(); @@ -60,6 +64,7 @@ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('Street', $person->address->street); $this->assertEquals('54321', $person->address->zip); $this->assertEquals('another town', $person->address->city); + $this->assertEquals('United States of America', $person->address->country->name); // 4. check deleting works $personId = $person->id;; @@ -78,6 +83,7 @@ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase $person->address->street = "Tree"; $person->address->zip = "12345"; $person->address->city = "funkytown"; + $person->address->country = new DDC93Country('United States of America'); $this->_em->persist($person); } @@ -94,6 +100,8 @@ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('Tree', $person->address->street); $this->assertEquals('12345', $person->address->zip); $this->assertEquals('funkytown', $person->address->city); + $this->assertInstanceOf(DDC93Country::CLASSNAME, $person->address->country); + $this->assertEquals('United States of America', $person->address->country->name); } $dql = "SELECT p FROM " . __NAMESPACE__ . "\DDC93Person p"; @@ -103,6 +111,7 @@ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('Tree', $person['address.street']); $this->assertEquals('12345', $person['address.zip']); $this->assertEquals('funkytown', $person['address.city']); + $this->assertEquals('United States of America', $person['address.country.name']); } } @@ -115,32 +124,41 @@ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->markTestSkipped('SLC does not work with UPDATE/DELETE queries through EM.'); } - $person = new DDC93Person('Johannes', new DDC93Address('Moo', '12345', 'Karlsruhe')); + $person = new DDC93Person('Johannes', new DDC93Address('Moo', '12345', 'Karlsruhe', new DDC93Country('Germany'))); $this->_em->persist($person); $this->_em->flush($person); // SELECT - $selectDql = "SELECT p FROM " . __NAMESPACE__ ."\\DDC93Person p WHERE p.address.city = :city"; + $selectDql = "SELECT p FROM " . __NAMESPACE__ ."\\DDC93Person p WHERE p.address.city = :city AND p.address.country.name = :country"; $loadedPerson = $this->_em->createQuery($selectDql) ->setParameter('city', 'Karlsruhe') + ->setParameter('country', 'Germany') ->getSingleResult(); $this->assertEquals($person, $loadedPerson); - $this->assertNull($this->_em->createQuery($selectDql)->setParameter('city', 'asdf')->getOneOrNullResult()); + $this->assertNull( + $this->_em->createQuery($selectDql) + ->setParameter('city', 'asdf') + ->setParameter('country', 'Germany') + ->getOneOrNullResult() + ); // UPDATE - $updateDql = "UPDATE " . __NAMESPACE__ . "\\DDC93Person p SET p.address.street = :street WHERE p.address.city = :city"; + $updateDql = "UPDATE " . __NAMESPACE__ . "\\DDC93Person p SET p.address.street = :street, p.address.country.name = :country WHERE p.address.city = :city"; $this->_em->createQuery($updateDql) ->setParameter('street', 'Boo') + ->setParameter('country', 'DE') ->setParameter('city', 'Karlsruhe') ->execute(); $this->_em->refresh($person); $this->assertEquals('Boo', $person->address->street); + $this->assertEquals('DE', $person->address->country->name); // DELETE - $this->_em->createQuery("DELETE " . __NAMESPACE__ . "\\DDC93Person p WHERE p.address.city = :city") + $this->_em->createQuery("DELETE " . __NAMESPACE__ . "\\DDC93Person p WHERE p.address.city = :city AND p.address.country.name = :country") ->setParameter('city', 'Karlsruhe') + ->setParameter('country', 'DE') ->execute(); $this->_em->clear(); @@ -165,43 +183,24 @@ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($car, $reloadedCar); } - public function testEmbeddableWithinEmbeddable() - { - $this->setExpectedException( - 'Doctrine\ORM\Mapping\MappingException', - sprintf( - "You embedded one or more embeddables in embeddable '%s', but this behavior is currently unsupported.", - __NAMESPACE__ . '\DDC93ContactInfo' - ) - ); - - $this->_schemaTool->createSchema(array( - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Customer'), - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93ContactInfo'), - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93PhoneNumber') - )); - } - public function testInlineEmbeddableWithPrefix() { - $expectedColumnName = 'foobar_id'; + $metadata = $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC3028PersonWithPrefix'); - $actualColumnName = $this->_em - ->getClassMetadata(__NAMESPACE__ . '\DDC3028PersonWithPrefix') - ->getColumnName('id.id'); - - $this->assertEquals($expectedColumnName, $actualColumnName); + $this->assertEquals('foobar_id', $metadata->getColumnName('id.id')); + $this->assertEquals('bloo_foo_id', $metadata->getColumnName('nested.nestedWithPrefix.id')); + $this->assertEquals('bloo_nestedWithEmptyPrefix_id', $metadata->getColumnName('nested.nestedWithEmptyPrefix.id')); + $this->assertEquals('bloo_id', $metadata->getColumnName('nested.nestedWithPrefixFalse.id')); } public function testInlineEmbeddableEmptyPrefix() { - $expectedColumnName = 'id_id'; + $metadata = $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC3028PersonEmptyPrefix'); - $actualColumnName = $this->_em - ->getClassMetadata(__NAMESPACE__ . '\DDC3028PersonEmptyPrefix') - ->getColumnName('id.id'); - - $this->assertEquals($expectedColumnName, $actualColumnName); + $this->assertEquals('id_id', $metadata->getColumnName('id.id')); + $this->assertEquals('nested_foo_id', $metadata->getColumnName('nested.nestedWithPrefix.id')); + $this->assertEquals('nested_nestedWithEmptyPrefix_id', $metadata->getColumnName('nested.nestedWithEmptyPrefix.id')); + $this->assertEquals('nested_id', $metadata->getColumnName('nested.nestedWithPrefixFalse.id')); } public function testInlineEmbeddablePrefixFalse() @@ -223,6 +222,22 @@ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($isFieldMapped); } + + public function testThrowsExceptionOnInfiniteEmbeddableNesting() + { + $this->setExpectedException( + 'Doctrine\ORM\Mapping\MappingException', + sprintf( + 'Infinite nesting detected for embedded property %s::nested. ' . + 'You cannot embed an embeddable from the same type inside an embeddable.', + __NAMESPACE__ . '\DDCInfiniteNestingEmbeddable' + ) + ); + + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDCInfiniteNestingEmbeddable'), + )); + } } @@ -297,6 +312,24 @@ class DDC93Car extends DDC93Vehicle { } +/** + * @Embeddable + */ +class DDC93Country +{ + const CLASSNAME = __CLASS__; + + /** + * @Column(type="string", nullable=true) + */ + public $name; + + public function __construct($name = null) + { + $this->name = $name; + } +} + /** * @Embeddable */ @@ -316,12 +349,15 @@ class DDC93Address * @Column(type="string") */ public $city; + /** @Embedded(class = "DDC93Country") */ + public $country; - public function __construct($street = null, $zip = null, $city = null) + public function __construct($street = null, $zip = null, $city = null, DDC93Country $country = null) { $this->street = $street; $this->zip = $zip; $this->city = $city; + $this->country = $country; } } @@ -338,8 +374,14 @@ class DDC93Customer /** @Embeddable */ class DDC93ContactInfo { + const CLASSNAME = __CLASS__; + + /** + * @Column(type="string") + */ + public $email; /** @Embedded(class = "DDC93Address") */ - private $address; + public $address; } /** @@ -352,9 +394,13 @@ class DDC3028PersonWithPrefix /** @Embedded(class="DDC3028Id", columnPrefix = "foobar_") */ public $id; - public function __construct(DDC3028Id $id = null) + /** @Embedded(class="DDC3028NestedEmbeddable", columnPrefix = "bloo_") */ + public $nested; + + public function __construct(DDC3028Id $id = null, DDC3028NestedEmbeddable $nested = null) { $this->id = $id; + $this->nested = $nested; } } @@ -368,9 +414,13 @@ class DDC3028PersonEmptyPrefix /** @Embedded(class="DDC3028Id", columnPrefix = "") */ public $id; - public function __construct(DDC3028Id $id = null) + /** @Embedded(class="DDC3028NestedEmbeddable", columnPrefix = "") */ + public $nested; + + public function __construct(DDC3028Id $id = null, DDC3028NestedEmbeddable $nested = null) { $this->id = $id; + $this->nested = $nested; } } @@ -408,6 +458,33 @@ class DDC3028Id } } +/** + * @Embeddable + */ +class DDC3028NestedEmbeddable +{ + const CLASSNAME = __CLASS__; + + /** @Embedded(class="DDC3028Id", columnPrefix = "foo_") */ + public $nestedWithPrefix; + + /** @Embedded(class="DDC3028Id", columnPrefix = "") */ + public $nestedWithEmptyPrefix; + + /** @Embedded(class="DDC3028Id", columnPrefix = false) */ + public $nestedWithPrefixFalse; + + public function __construct( + DDC3028Id $nestedWithPrefix = null, + DDC3028Id $nestedWithEmptyPrefix = null, + DDC3028Id $nestedWithPrefixFalse = null + ) { + $this->nestedWithPrefix = $nestedWithPrefix; + $this->nestedWithEmptyPrefix = $nestedWithEmptyPrefix; + $this->nestedWithPrefixFalse = $nestedWithPrefixFalse; + } +} + /** * @MappedSuperclass */ @@ -426,3 +503,12 @@ abstract class DDC3027Animal class DDC3027Dog extends DDC3027Animal { } + +/** + * @Embeddable + */ +class DDCInfiniteNestingEmbeddable +{ + /** @Embedded(class="DDCInfiniteNestingEmbeddable") */ + public $nested; +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php index f810e367d..3827c4173 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php @@ -65,7 +65,9 @@ class XmlMappingDriverTest extends AbstractMappingDriverTest array( 'name' => array( 'class' => 'Doctrine\Tests\Models\ValueObjects\Name', - 'columnPrefix' => 'nm_' + 'columnPrefix' => 'nm_', + 'declaredField' => null, + 'originalField' => null, ) ), $class->embeddedClasses From 0768916a0648fe548ab039d557abdc9da3f44bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20M=C3=BCller?= Date: Tue, 12 Aug 2014 07:56:39 +0200 Subject: [PATCH 2/2] fix handling infinite nesting of embeddables --- .../ORM/Mapping/ClassMetadataFactory.php | 11 ++- .../Tests/ORM/Functional/ValueObjectsTest.php | 77 ++++++++++++++++++- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index cbb8c0cc9..876f1ce57 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -64,6 +64,11 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory */ private $evm; + /** + * @var array + */ + private $embeddablesActiveNesting = array(); + /** * @param EntityManager $em */ @@ -148,10 +153,12 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory continue; } - if ($embeddableClass['class'] === $class->name) { + if (isset($this->embeddablesActiveNesting[$embeddableClass['class']])) { throw MappingException::infiniteEmbeddableNesting($class->name, $property); } + $this->embeddablesActiveNesting[$class->name] = true; + $embeddableMetadata = $this->getMetadataFor($embeddableClass['class']); if ($embeddableMetadata->isEmbeddedClass) { @@ -159,6 +166,8 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory } $class->inlineEmbeddable($property, $embeddableMetadata); + + unset($this->embeddablesActiveNesting[$class->name]); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index 98aea6e47..28a7e2664 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -223,21 +223,32 @@ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($isFieldMapped); } - public function testThrowsExceptionOnInfiniteEmbeddableNesting() + /** + * @dataProvider getInfiniteEmbeddableNestingData + */ + public function testThrowsExceptionOnInfiniteEmbeddableNesting($embeddableClassName, $declaredEmbeddableClassName) { $this->setExpectedException( 'Doctrine\ORM\Mapping\MappingException', sprintf( 'Infinite nesting detected for embedded property %s::nested. ' . 'You cannot embed an embeddable from the same type inside an embeddable.', - __NAMESPACE__ . '\DDCInfiniteNestingEmbeddable' + __NAMESPACE__ . '\\' . $declaredEmbeddableClassName ) ); $this->_schemaTool->createSchema(array( - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDCInfiniteNestingEmbeddable'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\\' . $embeddableClassName), )); } + + public function getInfiniteEmbeddableNestingData() + { + return array( + array('DDCInfiniteNestingEmbeddable', 'DDCInfiniteNestingEmbeddable'), + array('DDCNestingEmbeddable1', 'DDCNestingEmbeddable4'), + ); + } } @@ -512,3 +523,63 @@ class DDCInfiniteNestingEmbeddable /** @Embedded(class="DDCInfiniteNestingEmbeddable") */ public $nested; } + +/** + * @Embeddable + */ +class DDCNestingEmbeddable1 +{ + /** @Embedded(class="DDC3028Id") */ + public $id1; + + /** @Embedded(class="DDC3028Id") */ + public $id2; + + /** @Embedded(class="DDCNestingEmbeddable2") */ + public $nested; +} + +/** + * @Embeddable + */ +class DDCNestingEmbeddable2 +{ + /** @Embedded(class="DDC3028Id") */ + public $id1; + + /** @Embedded(class="DDC3028Id") */ + public $id2; + + /** @Embedded(class="DDCNestingEmbeddable3") */ + public $nested; +} + +/** + * @Embeddable + */ +class DDCNestingEmbeddable3 +{ + /** @Embedded(class="DDC3028Id") */ + public $id1; + + /** @Embedded(class="DDC3028Id") */ + public $id2; + + /** @Embedded(class="DDCNestingEmbeddable4") */ + public $nested; +} + +/** + * @Embeddable + */ +class DDCNestingEmbeddable4 +{ + /** @Embedded(class="DDC3028Id") */ + public $id1; + + /** @Embedded(class="DDC3028Id") */ + public $id2; + + /** @Embedded(class="DDCNestingEmbeddable1") */ + public $nested; +}