From 02d34bbba6e3055b6b72918a3f08d43d4f29469e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 19 Feb 2013 19:04:13 +0100 Subject: [PATCH 1/6] [DDC-93] Started ValueObjectsTest --- .../Tests/ORM/Functional/ValueObjectsTest.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php new file mode 100644 index 00000000000..916f06bf93d --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -0,0 +1,25 @@ + Date: Tue, 26 Mar 2013 21:45:15 +0100 Subject: [PATCH 2/6] [DDC-93] Parse @Embedded and @Embeddable during SchemaTool processing to make parsing work. --- .../ORM/Mapping/ClassMetadataInfo.php | 13 ++++- .../ORM/Mapping/Driver/AnnotationDriver.php | 2 + .../Mapping/Driver/DoctrineAnnotations.php | 4 +- lib/Doctrine/ORM/Mapping/Embeddable.php | 28 ++++++++++ lib/Doctrine/ORM/Mapping/Embedded.php | 32 +++++++++++ lib/Doctrine/ORM/Tools/SchemaTool.php | 1 + .../Tests/ORM/Functional/ValueObjectsTest.php | 53 ++++++++++++++++++- 7 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 lib/Doctrine/ORM/Mapping/Embeddable.php create mode 100644 lib/Doctrine/ORM/Mapping/Embedded.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 1e038d659c3..63c324a624e 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -246,6 +246,13 @@ class ClassMetadataInfo implements ClassMetadata */ public $isMappedSuperclass = false; + /** + * READ-ONLY: Wheather this class describes the mapping of an embeddable class. + * + * @var boolean + */ + public $isEmbeddedClass = false; + /** * READ-ONLY: The names of the parent classes (ancestors). * @@ -921,8 +928,12 @@ public function initializeReflection($reflService) */ public function validateIdentifier() { + if ($this->isMappedSuperclass || $this->isEmbeddedClass) { + return; + } + // Verify & complete identifier mapping - if ( ! $this->identifier && ! $this->isMappedSuperclass) { + if ( ! $this->identifier) { throw MappingException::identifierRequired($this->name); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 9e1c734cc22..56b693e678e 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -85,6 +85,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) $mappedSuperclassAnnot = $classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass']; $metadata->setCustomRepositoryClass($mappedSuperclassAnnot->repositoryClass); $metadata->isMappedSuperclass = true; + } else if (isset($classAnnotations['Doctrine\ORM\Mapping\Embeddable'])) { + $metadata->isEmbeddedClass = true; } else { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 14abadb9e4d..3ba65f772a7 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -19,6 +19,8 @@ require_once __DIR__.'/../Annotation.php'; require_once __DIR__.'/../Entity.php'; +require_once __DIR__.'/../Embeddable.php'; +require_once __DIR__.'/../Embedded.php'; require_once __DIR__.'/../MappedSuperclass.php'; require_once __DIR__.'/../InheritanceType.php'; require_once __DIR__.'/../DiscriminatorColumn.php'; @@ -64,4 +66,4 @@ require_once __DIR__.'/../AssociationOverrides.php'; require_once __DIR__.'/../AttributeOverride.php'; require_once __DIR__.'/../AttributeOverrides.php'; -require_once __DIR__.'/../EntityListeners.php'; \ No newline at end of file +require_once __DIR__.'/../EntityListeners.php'; diff --git a/lib/Doctrine/ORM/Mapping/Embeddable.php b/lib/Doctrine/ORM/Mapping/Embeddable.php new file mode 100644 index 00000000000..34cdcd1f193 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Embeddable.php @@ -0,0 +1,28 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class Embeddable implements Annotation +{ +} diff --git a/lib/Doctrine/ORM/Mapping/Embedded.php b/lib/Doctrine/ORM/Mapping/Embedded.php new file mode 100644 index 00000000000..c3dcb0837a4 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Embedded.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("CLASS") + */ +final class Embedded implements Annotation +{ + /** + * @var string + */ + public $class; +} diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index f66a49d152b..a8baa02aa88 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -126,6 +126,7 @@ private function processingNotRequired($class, array $processedClasses) return ( isset($processedClasses[$class->name]) || $class->isMappedSuperclass || + $class->isEmbeddedClass || ($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName) ); } diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index 916f06bf93d..ac0f332a1d3 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -2,10 +2,37 @@ namespace Doctrine\Tests\ORM\Functional; +/** + * @group DDC-93 + */ class ValueObjectsTest extends \Doctrine\Tests\OrmFunctionalTestCase { public function setUp() { + parent::setUp(); + + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Person'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Address'), + )); + } + + public function testMetadata() + { + $person = new DDC93Person(); + $person->name = "Tara"; + $person->address = new DDC93Address(); + $person->address->street = "United States of Tara Street"; + $person->address->zip = "12345"; + $person->address->city = "funkytown"; + + $this->_em->persist($person); + $this->_em->flush(); + + $this->_em->clear(); + + $person = $this->_em->find(DDC93Person::CLASSNAME, $person->id); + $this->assertInstanceOf(DDC93Address::CLASSNAME, $person->address); } } @@ -14,12 +41,36 @@ public function setUp() */ class DDC93Person { + const CLASSNAME = __CLASS__; + /** @Id @GeneratedValue @Column(type="integer") */ public $id; /** @Column(type="string") */ public $name; - /** @Embedded */ + /** @Embedded(class="DDC93Address") */ public $address; } + +/** + * @Embeddable + */ +class DDC93Address +{ + const CLASSNAME = __CLASS__; + + /** + * @Column(type="string") + */ + public $street; + /** + * @Column(type="string") + */ + public $zip; + /** + * @Column(type="string") + */ + public $city; +} + From 0204a8b69a33883c83a26be108b528fac320c729 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 27 Mar 2013 00:10:30 +0100 Subject: [PATCH 3/6] [DDC-93] Implement first working version of value objects using a ReflectionProxy object, bypassing changes to UnitOfWork, Persisters and Hydrators. --- .../ORM/Mapping/ClassMetadataFactory.php | 5 ++ .../ORM/Mapping/ClassMetadataInfo.php | 70 +++++++++++++++++-- .../ORM/Mapping/Driver/AnnotationDriver.php | 3 + lib/Doctrine/ORM/Mapping/ReflectionProxy.php | 64 +++++++++++++++++ .../Tests/ORM/Functional/ValueObjectsTest.php | 4 ++ 5 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 lib/Doctrine/ORM/Mapping/ReflectionProxy.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index a1da3ffcfd1..d661eb7a319 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -136,6 +136,11 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS $this->completeIdGeneratorMapping($class); } + foreach ($class->embeddedClasses as $property => $embeddableClass) { + $embeddableMetadata = $this->getMetadataFor($embeddableClass); + $class->inlineEmbeddable($property, $embeddableMetadata); + } + if ($parent && $parent->isInheritanceTypeSingleTable()) { $class->setPrimaryTable($parent->table); } diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 63c324a624e..9ab80a72c15 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -267,6 +267,13 @@ class ClassMetadataInfo implements ClassMetadata */ public $subClasses = array(); + /** + * READ-ONLY: The names of all embedded classes based on properties. + * + * @var array + */ + public $embeddedClasses = array(); + /** * READ-ONLY: The named queries allowed to be called directly from Repository. * @@ -887,6 +894,15 @@ public function wakeupReflection($reflService) $this->reflClass = $reflService->getClass($this->name); foreach ($this->fieldMappings as $field => $mapping) { + if (isset($mapping['declaredField'])) { + $this->reflFields[$field] = new ReflectionProxy( + $reflService->getAccessibleProperty($this->name, $mapping['declaredField']), + $reflService->getAccessibleProperty($this->embeddedClasses[$mapping['declaredField']], $mapping['originalField']), + $this->embeddedClasses[$mapping['declaredField']] + ); + continue; + } + $this->reflFields[$field] = isset($mapping['declared']) ? $reflService->getAccessibleProperty($mapping['declared'], $field) : $reflService->getAccessibleProperty($this->name, $field); @@ -2166,9 +2182,8 @@ private function _isInheritanceType($type) public function mapField(array $mapping) { $this->_validateAndCompleteFieldMapping($mapping); - if (isset($this->fieldMappings[$mapping['fieldName']]) || isset($this->associationMappings[$mapping['fieldName']])) { - throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']); - } + $this->assertFieldNotMapped($mapping['fieldName']); + $this->fieldMappings[$mapping['fieldName']] = $mapping; } @@ -2416,9 +2431,7 @@ protected function _storeAssociationMapping(array $assocMapping) { $sourceFieldName = $assocMapping['fieldName']; - if (isset($this->fieldMappings[$sourceFieldName]) || isset($this->associationMappings[$sourceFieldName])) { - throw MappingException::duplicateFieldMapping($this->name, $sourceFieldName); - } + $this->assertFieldNotMapped($sourceFieldName); $this->associationMappings[$sourceFieldName] = $assocMapping; } @@ -3030,4 +3043,49 @@ public function fullyQualifiedClassName($className) return $className; } + + /** + * Map Embedded Class + * + * @array $mapping + * @return void + */ + public function mapEmbedded(array $mapping) + { + $this->assertFieldNotMapped($mapping['fieldName']); + + $this->embeddedClasses[$mapping['fieldName']] = $this->fullyQualifiedClassName($mapping['class']); + } + + /** + * Inline the embeddable class + * + * @param string $property + * @param ClassMetadataInfo $embeddable + */ + public function inlineEmbeddable($property, ClassMetadataInfo $embeddable) + { + foreach ($embeddable->fieldMappings as $fieldMapping) { + $fieldMapping['declaredField'] = $property; + $fieldMapping['originalField'] = $fieldMapping['fieldName']; + $fieldMapping['fieldName'] = $property . $fieldMapping['fieldName']; + $fieldMapping['columnName'] = $property . "_" . $fieldMapping['columnName']; + + $this->mapField($fieldMapping); + } + } + + /** + * @param string $fieldName + * @throws MappingException + */ + private function assertFieldNotMapped($fieldName) + { + if (isset($this->fieldMappings[$fieldName]) || + isset($this->associationMappings[$fieldName]) || + isset($this->embeddedClasses[$fieldName])) { + + throw MappingException::duplicateFieldMapping($this->name, $fieldName); + } + } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 56b693e678e..2529edeedf5 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -366,6 +366,9 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } $metadata->mapManyToMany($mapping); + } else if ($embeddedAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Embedded')) { + $mapping['class'] = $embeddedAnnot->class; + $metadata->mapEmbedded($mapping); } } diff --git a/lib/Doctrine/ORM/Mapping/ReflectionProxy.php b/lib/Doctrine/ORM/Mapping/ReflectionProxy.php new file mode 100644 index 00000000000..9d391d9a873 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/ReflectionProxy.php @@ -0,0 +1,64 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * Acts as a proxy to a nested Property structure, making it look like + * just a single scalar property. + * + * This way value objects "just work" without UnitOfWork, Persisters or Hydrators + * needing any changes. + */ +class ReflectionProxy +{ + private $parentProperty; + private $childProperty; + private $class; + + public function __construct($parentProperty, $childProperty, $class) + { + $this->parentProperty = $parentProperty; + $this->childProperty = $childProperty; + $this->class = $class; + } + + public function getValue($object) + { + $embeddedObject = $this->parentProperty->getValue($object); + + if ($embeddedObject === null) { + return null; + } + + return $this->childProperty->getValue($embeddedObject); + } + + public function setValue($object, $value) + { + $embeddedObject = $this->parentProperty->getValue($object); + + if ($embeddedObject === null) { + $embeddedObject = new $this->class; // TODO + $this->parentProperty->setValue($object, $embeddedObject); + } + + $this->childProperty->setValue($embeddedObject, $value); + } +} diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index ac0f332a1d3..26f129c7158 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -32,7 +32,11 @@ public function testMetadata() $this->_em->clear(); $person = $this->_em->find(DDC93Person::CLASSNAME, $person->id); + $this->assertInstanceOf(DDC93Address::CLASSNAME, $person->address); + $this->assertEquals('United States of Tara Street', $person->address->street); + $this->assertEquals('12345', $person->address->zip); + $this->assertEquals('funkytown', $person->address->city); } } From 011776f02ffa279afc0016b2de76b662b87dfc95 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 27 Mar 2013 00:18:21 +0100 Subject: [PATCH 4/6] [DDC-93] Add some TODOs in code. --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 9ab80a72c15..8a444d9ae06 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -3068,8 +3068,8 @@ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable) foreach ($embeddable->fieldMappings as $fieldMapping) { $fieldMapping['declaredField'] = $property; $fieldMapping['originalField'] = $fieldMapping['fieldName']; - $fieldMapping['fieldName'] = $property . $fieldMapping['fieldName']; - $fieldMapping['columnName'] = $property . "_" . $fieldMapping['columnName']; + $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName']; // TODO: Change DQL parser to accept this dot notation + $fieldMapping['columnName'] = $property . "_" . $fieldMapping['columnName']; // TODO: Use naming strategy $this->mapField($fieldMapping); } From 879ab6e52b1d20565074b5abfeb995926505461a Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 27 Mar 2013 07:44:47 +0100 Subject: [PATCH 5/6] [DDC-93] Show CRUD with value objects with current change tracking assumptions. --- .../Tests/ORM/Functional/ValueObjectsTest.php | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index 26f129c7158..d1f81189c0f 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -17,7 +17,7 @@ public function setUp() )); } - public function testMetadata() + public function testCRUD() { $person = new DDC93Person(); $person->name = "Tara"; @@ -26,17 +26,40 @@ public function testMetadata() $person->address->zip = "12345"; $person->address->city = "funkytown"; + // 1. check saving value objects works $this->_em->persist($person); $this->_em->flush(); $this->_em->clear(); + // 2. check loading value objects works $person = $this->_em->find(DDC93Person::CLASSNAME, $person->id); $this->assertInstanceOf(DDC93Address::CLASSNAME, $person->address); $this->assertEquals('United States of Tara Street', $person->address->street); $this->assertEquals('12345', $person->address->zip); $this->assertEquals('funkytown', $person->address->city); + + // 3. check changing value objects works + $person->address->street = "Street"; + $person->address->zip = "54321"; + $person->address->city = "another town"; + $this->_em->flush(); + + $this->_em->clear(); + + $person = $this->_em->find(DDC93Person::CLASSNAME, $person->id); + + $this->assertEquals('Street', $person->address->street); + $this->assertEquals('54321', $person->address->zip); + $this->assertEquals('another town', $person->address->city); + + // 4. check deleting works + $personId = $person->id;; + $this->_em->remove($person); + $this->_em->flush(); + + $this->assertNull($this->_em->find(DDC93Person::CLASSNAME, $personId)); } } From 9613f1d8cb40c552ea5e57ad12b3adf0a654d167 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 27 Mar 2013 21:45:16 +0100 Subject: [PATCH 6/6] [DDC-93] Rename ReflectionProxy to ReflectionEmbeddedProperty, Add DQL test with Object and Array Hydration. --- .../ORM/Mapping/ClassMetadataInfo.php | 2 +- ...oxy.php => ReflectionEmbeddedProperty.php} | 4 +- .../Tests/ORM/Functional/ValueObjectsTest.php | 48 +++++++++++++++++-- 3 files changed, 48 insertions(+), 6 deletions(-) rename lib/Doctrine/ORM/Mapping/{ReflectionProxy.php => ReflectionEmbeddedProperty.php} (96%) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 8a444d9ae06..6a2931fe6e0 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -895,7 +895,7 @@ public function wakeupReflection($reflService) foreach ($this->fieldMappings as $field => $mapping) { if (isset($mapping['declaredField'])) { - $this->reflFields[$field] = new ReflectionProxy( + $this->reflFields[$field] = new ReflectionEmbeddedProperty( $reflService->getAccessibleProperty($this->name, $mapping['declaredField']), $reflService->getAccessibleProperty($this->embeddedClasses[$mapping['declaredField']], $mapping['originalField']), $this->embeddedClasses[$mapping['declaredField']] diff --git a/lib/Doctrine/ORM/Mapping/ReflectionProxy.php b/lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php similarity index 96% rename from lib/Doctrine/ORM/Mapping/ReflectionProxy.php rename to lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php index 9d391d9a873..b68d7d81885 100644 --- a/lib/Doctrine/ORM/Mapping/ReflectionProxy.php +++ b/lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php @@ -25,8 +25,10 @@ * * This way value objects "just work" without UnitOfWork, Persisters or Hydrators * needing any changes. + * + * TODO: Move this class into Common\Reflection */ -class ReflectionProxy +class ReflectionEmbeddedProperty { private $parentProperty; private $childProperty; diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index d1f81189c0f..89a51a40664 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -11,10 +11,13 @@ public function setUp() { parent::setUp(); - $this->_schemaTool->createSchema(array( - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Person'), - $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Address'), - )); + try { + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Person'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Address'), + )); + } catch(\Exception $e) { + } } public function testCRUD() @@ -61,6 +64,43 @@ public function testCRUD() $this->assertNull($this->_em->find(DDC93Person::CLASSNAME, $personId)); } + + public function testLoadDql() + { + for ($i = 0; $i < 3; $i++) { + $person = new DDC93Person(); + $person->name = "Donkey Kong$i"; + $person->address = new DDC93Address(); + $person->address->street = "Tree"; + $person->address->zip = "12345"; + $person->address->city = "funkytown"; + + $this->_em->persist($person); + } + + $this->_em->flush(); + $this->_em->clear(); + + $dql = "SELECT p FROM " . __NAMESPACE__ . "\DDC93Person p"; + $persons = $this->_em->createQuery($dql)->getResult(); + + $this->assertCount(3, $persons); + foreach ($persons as $person) { + $this->assertInstanceOf(DDC93Address::CLASSNAME, $person->address); + $this->assertEquals('Tree', $person->address->street); + $this->assertEquals('12345', $person->address->zip); + $this->assertEquals('funkytown', $person->address->city); + } + + $dql = "SELECT p FROM " . __NAMESPACE__ . "\DDC93Person p"; + $persons = $this->_em->createQuery($dql)->getArrayResult(); + + foreach ($persons as $person) { + $this->assertEquals('Tree', $person['address.street']); + $this->assertEquals('12345', $person['address.zip']); + $this->assertEquals('funkytown', $person['address.city']); + } + } } /**