From b4b9709090e2fb5919b025fa2e5a194e1013fe07 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Sun, 3 Mar 2013 11:01:43 +0100 Subject: [PATCH 01/29] adds a new output format --- .../Tools/Console/Command/RunDqlCommand.php | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php index 1189b8194c0..c8cc50e838f 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php @@ -25,6 +25,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Doctrine\Common\Util\Debug; +use JMS\Serializer\SerializerBuilder; /** * Command to execute DQL queries in a given EntityManager. @@ -64,6 +65,11 @@ protected function configure() new InputOption( 'depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of Entity graph.', 7 + ), + new InputOption( + 'format', null, InputOption::VALUE_REQUIRED, + 'The output format of the result. Available formats: doctrine-debug (default), jms-serializer-json.', + 'doctrine-debug' ) )) ->setHelp(<<execute(array(), constant($hydrationMode)); - Debug::dump($resultSet, $input->getOption('depth')); + switch ($input->getOption('format')) { + case 'doctrine-debug': + ob_start(); + Debug::dump($resultSet, $input->getOption('depth')); + $message = ob_get_clean(); + + $output->write($message); + break; + + case 'jms-serializer-json': + $serializer = SerializerBuilder::create()->build(); + $output->write($serializer->serialize($resultSet, 'json')); + break; + + default: + throw new \RuntimeException(sprintf('Unknown output format "%s"; available formats: doctrine-debug, jms-serializer-json', $input->getOption('format'))); + } } } From 02d34bbba6e3055b6b72918a3f08d43d4f29469e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 19 Feb 2013 19:04:13 +0100 Subject: [PATCH 02/29] [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 03/29] [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 04/29] [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 05/29] [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 06/29] [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 07/29] [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']); + } + } } /** From c67ac8a11b56fbc37eee7a475c011548c6c63487 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Fri, 1 Nov 2013 20:38:19 +0100 Subject: [PATCH 08/29] adds support for selecting based on embedded fields --- lib/Doctrine/ORM/Query/Parser.php | 8 ++++- .../Tests/ORM/Functional/ValueObjectsTest.php | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 5c721a1d5b1..912138208be 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -1049,7 +1049,7 @@ public function JoinAssociationPathExpression() * Parses an arbitrary path expression and defers semantical validation * based on expected types. * - * PathExpression ::= IdentificationVariable "." identifier + * PathExpression ::= IdentificationVariable "." identifier [ ("." identifier)* ] * * @param integer $expectedTypes * @@ -1065,6 +1065,12 @@ public function PathExpression($expectedTypes) $this->match(Lexer::T_IDENTIFIER); $field = $this->lexer->token['value']; + + while ($this->lexer->isNextToken(Lexer::T_DOT)) { + $this->match(Lexer::T_DOT); + $this->match(Lexer::T_IDENTIFIER); + $field .= '.'.$this->lexer->token['value']; + } } // Creating AST node diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index 89a51a40664..a309acdb17e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -101,6 +101,24 @@ public function testLoadDql() $this->assertEquals('funkytown', $person['address.city']); } } + + /** + * @group dql + */ + public function testDqlOnEmbeddedObjectsField() + { + $person = new DDC93Person('Johannes', new DDC93Address('Moo', '12345', 'Karlsruhe')); + $this->_em->persist($person); + $this->_em->flush($person); + + $dql = "SELECT p FROM " . __NAMESPACE__ ."\\DDC93Person p WHERE p.address.city = :city"; + $loadedPerson = $this->_em->createQuery($dql) + ->setParameter('city', 'Karlsruhe') + ->getSingleResult(); + $this->assertEquals($person, $loadedPerson); + + $this->assertNull($this->_em->createQuery($dql)->setParameter('city', 'asdf')->getOneOrNullResult()); + } } /** @@ -118,6 +136,12 @@ class DDC93Person /** @Embedded(class="DDC93Address") */ public $address; + + public function __construct($name = null, DDC93Address $address = null) + { + $this->name = $name; + $this->address = $address; + } } /** @@ -139,5 +163,12 @@ class DDC93Address * @Column(type="string") */ public $city; + + public function __construct($street = null, $zip = null, $city = null) + { + $this->street = $street; + $this->zip = $zip; + $this->city = $city; + } } From 30897c311593a9e51200d7d5bffc8aa6bdcb81b0 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Fri, 1 Nov 2013 20:46:08 +0100 Subject: [PATCH 09/29] adds tests for update/delete DQL queries --- .../Tests/ORM/Functional/ValueObjectsTest.php | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index a309acdb17e..9386d188510 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -111,13 +111,32 @@ public function testDqlOnEmbeddedObjectsField() $this->_em->persist($person); $this->_em->flush($person); - $dql = "SELECT p FROM " . __NAMESPACE__ ."\\DDC93Person p WHERE p.address.city = :city"; - $loadedPerson = $this->_em->createQuery($dql) + // SELECT + $selectDql = "SELECT p FROM " . __NAMESPACE__ ."\\DDC93Person p WHERE p.address.city = :city"; + $loadedPerson = $this->_em->createQuery($selectDql) ->setParameter('city', 'Karlsruhe') ->getSingleResult(); $this->assertEquals($person, $loadedPerson); - $this->assertNull($this->_em->createQuery($dql)->setParameter('city', 'asdf')->getOneOrNullResult()); + $this->assertNull($this->_em->createQuery($selectDql)->setParameter('city', 'asdf')->getOneOrNullResult()); + + // UPDATE + $updateDql = "UPDATE " . __NAMESPACE__ . "\\DDC93Person p SET p.address.street = :street WHERE p.address.city = :city"; + $this->_em->createQuery($updateDql) + ->setParameter('street', 'Boo') + ->setParameter('city', 'Karlsruhe') + ->execute(); + + $this->_em->refresh($person); + $this->assertEquals('Boo', $person->address->street); + + // DELETE + $this->_em->createQuery("DELETE " . __NAMESPACE__ . "\\DDC93Person p WHERE p.address.city = :city") + ->setParameter('city', 'Karlsruhe') + ->execute(); + + $this->_em->clear(); + $this->assertNull($this->_em->find(__NAMESPACE__.'\\DDC93Person', $person->id)); } } From 41c937b983027329a55bcfbeb6ce1fbc0a8e6edf Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Fri, 1 Nov 2013 20:54:45 +0100 Subject: [PATCH 10/29] adds test for non-existent field --- .../Doctrine/Tests/ORM/Functional/ValueObjectsTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index 9386d188510..3ccfd05bbe6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -138,6 +138,16 @@ public function testDqlOnEmbeddedObjectsField() $this->_em->clear(); $this->assertNull($this->_em->find(__NAMESPACE__.'\\DDC93Person', $person->id)); } + + /** + * @expectedException Doctrine\ORM\Query\QueryException + * @expectedExceptionMessage no field or association named address.asdfasdf + */ + public function testDqlWithNonExistentEmbeddableField() + { + $this->_em->createQuery("SELECT p FROM " . __NAMESPACE__ . "\\DDC93Person p WHERE p.address.asdfasdf IS NULL") + ->execute(); + } } /** From fd8b5bd045d3c8d8669893f1a652ddab2130db41 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Fri, 1 Nov 2013 21:16:02 +0100 Subject: [PATCH 11/29] removes outdated todos --- 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 510d466728c..03dc3cb1572 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -3093,8 +3093,8 @@ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable) foreach ($embeddable->fieldMappings as $fieldMapping) { $fieldMapping['declaredField'] = $property; $fieldMapping['originalField'] = $fieldMapping['fieldName']; - $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName']; // TODO: Change DQL parser to accept this dot notation - $fieldMapping['columnName'] = $property . "_" . $fieldMapping['columnName']; // TODO: Use naming strategy + $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName']; + $fieldMapping['columnName'] = $property . "_" . $fieldMapping['columnName']; $this->mapField($fieldMapping); } From 20fb8270dc3c84eceaf90979644fdfbe0b2e615e Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Fri, 1 Nov 2013 21:44:57 +0100 Subject: [PATCH 12/29] make use of NamingStrategy for columns of embedded fields --- UPGRADE.md | 8 ++++++++ lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 2 +- lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php | 8 ++++++++ lib/Doctrine/ORM/Mapping/NamingStrategy.php | 10 ++++++++++ lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php | 8 ++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/UPGRADE.md b/UPGRADE.md index b864d86145c..224967b87dc 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,11 @@ +# Upgrade to 2.5 + +## BC BREAK: NamingStrategy has a new method ``embeddedFieldToColumnName($propertyName, $embeddedColumnName)`` + +This method generates the column name for fields of embedded objects. If you implement your custom NamingStrategy, you +now also need to implement this new method. + + # Upgrade to 2.4 ## BC BREAK: Compatibility Bugfix in PersistentCollection#matching() diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 03dc3cb1572..c72f884e983 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -3094,7 +3094,7 @@ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable) $fieldMapping['declaredField'] = $property; $fieldMapping['originalField'] = $fieldMapping['fieldName']; $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName']; - $fieldMapping['columnName'] = $property . "_" . $fieldMapping['columnName']; + $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName($property, $fieldMapping['columnName']); $this->mapField($fieldMapping); } diff --git a/lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php b/lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php index 2433b4afa8a..22e51674ea6 100644 --- a/lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php +++ b/lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php @@ -50,6 +50,14 @@ public function propertyToColumnName($propertyName, $className = null) return $propertyName; } + /** + * {@inheritdoc} + */ + public function embeddedFieldToColumnName($propertyName, $embeddedColumnName) + { + return $propertyName.ucfirst($embeddedColumnName); + } + /** * {@inheritdoc} */ diff --git a/lib/Doctrine/ORM/Mapping/NamingStrategy.php b/lib/Doctrine/ORM/Mapping/NamingStrategy.php index fc66905c508..b48e950698a 100644 --- a/lib/Doctrine/ORM/Mapping/NamingStrategy.php +++ b/lib/Doctrine/ORM/Mapping/NamingStrategy.php @@ -49,6 +49,16 @@ function classToTableName($className); */ function propertyToColumnName($propertyName, $className = null); + /** + * Returns a column name for an embedded property. + * + * @param string $propertyName + * @param string $embeddedColumnName + * + * @return string + */ + function embeddedFieldToColumnName($propertyName, $embeddedColumnName); + /** * Returns the default reference column name. * diff --git a/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php b/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php index 5231aaafcd9..e301d958bbc 100644 --- a/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php +++ b/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php @@ -87,6 +87,14 @@ public function propertyToColumnName($propertyName, $className = null) return $this->underscore($propertyName); } + /** + * {@inheritdoc} + */ + public function embeddedFieldToColumnName($propertyName, $embeddedColumnName) + { + return $this->underscore($propertyName).'_'.$embeddedColumnName; + } + /** * {@inheritdoc} */ From 4f6c15099a6e47b275d409c4ce17f5a35e73dd4f Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Fri, 1 Nov 2013 21:47:56 +0100 Subject: [PATCH 13/29] fixes coding style --- tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index 3ccfd05bbe6..bc02268c02a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -139,12 +139,10 @@ public function testDqlOnEmbeddedObjectsField() $this->assertNull($this->_em->find(__NAMESPACE__.'\\DDC93Person', $person->id)); } - /** - * @expectedException Doctrine\ORM\Query\QueryException - * @expectedExceptionMessage no field or association named address.asdfasdf - */ public function testDqlWithNonExistentEmbeddableField() { + $this->setExpectedException('Doctrine\ORM\Query\QueryException', 'no field or association named address.asdfasdf'); + $this->_em->createQuery("SELECT p FROM " . __NAMESPACE__ . "\\DDC93Person p WHERE p.address.asdfasdf IS NULL") ->execute(); } From f86abd81dd6d4eef31b685c9ffa9942831359c92 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Fri, 1 Nov 2013 22:33:59 +0100 Subject: [PATCH 14/29] fixes annotation context --- lib/Doctrine/ORM/Mapping/Embeddable.php | 2 +- lib/Doctrine/ORM/Mapping/Embedded.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Embeddable.php b/lib/Doctrine/ORM/Mapping/Embeddable.php index 34cdcd1f193..f14bfac82a6 100644 --- a/lib/Doctrine/ORM/Mapping/Embeddable.php +++ b/lib/Doctrine/ORM/Mapping/Embeddable.php @@ -21,7 +21,7 @@ /** * @Annotation - * @Target("PROPERTY") + * @Target("CLASS") */ final class Embeddable implements Annotation { diff --git a/lib/Doctrine/ORM/Mapping/Embedded.php b/lib/Doctrine/ORM/Mapping/Embedded.php index c3dcb0837a4..aa4d89356d5 100644 --- a/lib/Doctrine/ORM/Mapping/Embedded.php +++ b/lib/Doctrine/ORM/Mapping/Embedded.php @@ -21,7 +21,7 @@ /** * @Annotation - * @Target("CLASS") + * @Target("PROPERTY") */ final class Embedded implements Annotation { From 97836ef8c6e8ef9c906699d2b20b603b6a4ddd77 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Fri, 1 Nov 2013 22:37:59 +0100 Subject: [PATCH 15/29] some consistency fixes --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 2 +- lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php | 4 ++-- lib/Doctrine/ORM/Mapping/NamingStrategy.php | 2 +- lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index c72f884e983..612574e9020 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -3094,7 +3094,7 @@ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable) $fieldMapping['declaredField'] = $property; $fieldMapping['originalField'] = $fieldMapping['fieldName']; $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName']; - $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName($property, $fieldMapping['columnName']); + $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName($property, $fieldMapping['columnName'], $this->reflClass->name, $embeddable->reflClass->name); $this->mapField($fieldMapping); } diff --git a/lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php b/lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php index 22e51674ea6..06bc593be23 100644 --- a/lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php +++ b/lib/Doctrine/ORM/Mapping/DefaultNamingStrategy.php @@ -53,9 +53,9 @@ public function propertyToColumnName($propertyName, $className = null) /** * {@inheritdoc} */ - public function embeddedFieldToColumnName($propertyName, $embeddedColumnName) + public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null) { - return $propertyName.ucfirst($embeddedColumnName); + return $propertyName.'_'.$embeddedColumnName; } /** diff --git a/lib/Doctrine/ORM/Mapping/NamingStrategy.php b/lib/Doctrine/ORM/Mapping/NamingStrategy.php index b48e950698a..94938ccf272 100644 --- a/lib/Doctrine/ORM/Mapping/NamingStrategy.php +++ b/lib/Doctrine/ORM/Mapping/NamingStrategy.php @@ -57,7 +57,7 @@ function propertyToColumnName($propertyName, $className = null); * * @return string */ - function embeddedFieldToColumnName($propertyName, $embeddedColumnName); + function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null); /** * Returns the default reference column name. diff --git a/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php b/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php index e301d958bbc..ec74373a8b5 100644 --- a/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php +++ b/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php @@ -90,7 +90,7 @@ public function propertyToColumnName($propertyName, $className = null) /** * {@inheritdoc} */ - public function embeddedFieldToColumnName($propertyName, $embeddedColumnName) + public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null) { return $this->underscore($propertyName).'_'.$embeddedColumnName; } From ece62d6ad70e5fd3b313d297e60de39580eb620e Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Sat, 2 Nov 2013 13:23:56 +0100 Subject: [PATCH 16/29] adds support & tests for embeddables in inheritance schemes --- .../ORM/Mapping/ClassMetadataFactory.php | 21 +++++++++- .../ORM/Mapping/ClassMetadataInfo.php | 17 ++++++-- .../ORM/Mapping/Driver/AnnotationDriver.php | 4 +- .../Tests/ORM/Functional/ValueObjectsTest.php | 42 +++++++++++++++++++ 4 files changed, 78 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 701aee5510f..ff09210be16 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -96,6 +96,7 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS $class->setIdGeneratorType($parent->generatorType); $this->addInheritedFields($class, $parent); $this->addInheritedRelations($class, $parent); + $this->addInheritedEmbeddedClasses($class, $parent); $class->setIdentifier($parent->identifier); $class->setVersioned($parent->isVersioned); $class->setVersionField($parent->versionField); @@ -141,7 +142,11 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS } foreach ($class->embeddedClasses as $property => $embeddableClass) { - $embeddableMetadata = $this->getMetadataFor($embeddableClass); + if (isset($embeddableClass['inherited'])) { + continue; + } + + $embeddableMetadata = $this->getMetadataFor($embeddableClass['class']); $class->inlineEmbeddable($property, $embeddableMetadata); } @@ -343,6 +348,20 @@ private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $p } } + private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass) + { + foreach ($parentClass->embeddedClasses as $field => $embeddedClass) { + if ( ! isset($embeddedClass['inherited']) && ! $parentClass->isMappedSuperclass) { + $embeddedClass['inherited'] = $parentClass->name; + } + if ( ! isset($embeddedClass['declared'])) { + $embeddedClass['declared'] = $parentClass->name; + } + + $subClass->embeddedClasses[$field] = $embeddedClass; + } + } + /** * 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 612574e9020..3a4d080ab25 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -899,10 +899,12 @@ public function wakeupReflection($reflService) foreach ($this->fieldMappings as $field => $mapping) { if (isset($mapping['declaredField'])) { + $declaringClass = isset($this->embeddedClasses[$field]['declared']) ? $this->embeddedClasses[$field]['declared'] : $this->name; + $this->reflFields[$field] = new ReflectionEmbeddedProperty( - $reflService->getAccessibleProperty($this->name, $mapping['declaredField']), - $reflService->getAccessibleProperty($this->embeddedClasses[$mapping['declaredField']], $mapping['originalField']), - $this->embeddedClasses[$mapping['declaredField']] + $reflService->getAccessibleProperty($declaringClass, $mapping['declaredField']), + $reflService->getAccessibleProperty($this->embeddedClasses[$mapping['declaredField']]['class'], $mapping['originalField']), + $this->embeddedClasses[$mapping['declaredField']]['class'] ); continue; } @@ -2110,6 +2112,11 @@ public function isInheritedAssociation($fieldName) return isset($this->associationMappings[$fieldName]['inherited']); } + public function isInheritedEmbeddedClass($fieldName) + { + return isset($this->embeddedClasses[$fieldName]['inherited']); + } + /** * Sets the name of the primary table the class is mapped to. * @@ -3079,7 +3086,9 @@ public function mapEmbedded(array $mapping) { $this->assertFieldNotMapped($mapping['fieldName']); - $this->embeddedClasses[$mapping['fieldName']] = $this->fullyQualifiedClassName($mapping['class']); + $this->embeddedClasses[$mapping['fieldName']] = array( + 'class' => $this->fullyQualifiedClassName($mapping['class']) + ); } /** diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index b2437de2376..e7893f33ad8 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -242,7 +242,9 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) || $metadata->isInheritedField($property->name) || - $metadata->isInheritedAssociation($property->name)) { + $metadata->isInheritedAssociation($property->name) + || + $metadata->isInheritedEmbeddedClass($property->name)) { continue; } diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index bc02268c02a..f82220a1f9a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -15,6 +15,8 @@ public function setUp() $this->_schemaTool->createSchema(array( $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Person'), $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Address'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Vehicle'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC93Car'), )); } catch(\Exception $e) { } @@ -146,6 +148,16 @@ public function testDqlWithNonExistentEmbeddableField() $this->_em->createQuery("SELECT p FROM " . __NAMESPACE__ . "\\DDC93Person p WHERE p.address.asdfasdf IS NULL") ->execute(); } + + public function testEmbeddableWithInheritance() + { + $car = new DDC93Car(new DDC93Address('Foo', '12345', 'Asdf')); + $this->_em->persist($car); + $this->_em->flush($car); + + $reloadedCar = $this->_em->find(__NAMESPACE__.'\\DDC93Car', $car->id); + $this->assertEquals($car, $reloadedCar); + } } /** @@ -171,6 +183,36 @@ public function __construct($name = null, DDC93Address $address = null) } } +/** + * @Entity + * + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorColumn(name = "t", type = "string", length = 10) + * @DiscriminatorMap({ + * "v" = "Doctrine\Tests\ORM\Functional\DDC93Car", + * }) + */ +abstract class DDC93Vehicle +{ + /** @Id @GeneratedValue(strategy = "AUTO") @Column(type = "integer") */ + public $id; + + /** @Embedded(class = "DDC93Address") */ + public $address; + + public function __construct(DDC93Address $address) + { + $this->address = $address; + } +} + +/** + * @Entity + */ +class DDC93Car extends DDC93Vehicle +{ +} + /** * @Embeddable */ From 5586ddd6b7cbd73659d93615d1a1db0bea8e7300 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Sat, 2 Nov 2013 13:27:17 +0100 Subject: [PATCH 17/29] removes restrictions on constructors of embedded objects --- .../ORM/Mapping/ReflectionEmbeddedProperty.php | 2 +- .../Tests/ORM/Functional/ValueObjectsTest.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php b/lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php index b68d7d81885..662c006900e 100644 --- a/lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php +++ b/lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php @@ -57,7 +57,7 @@ public function setValue($object, $value) $embeddedObject = $this->parentProperty->getValue($object); if ($embeddedObject === null) { - $embeddedObject = new $this->class; // TODO + $embeddedObject = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->class), $this->class)); $this->parentProperty->setValue($object, $embeddedObject); } diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index f82220a1f9a..03bab0843b7 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -176,10 +176,28 @@ class DDC93Person /** @Embedded(class="DDC93Address") */ public $address; + /** @Embedded(class = "DDC93Timestamps") */ + public $timestamps; + public function __construct($name = null, DDC93Address $address = null) { $this->name = $name; $this->address = $address; + $this->timestamps = new DDC93Timestamps(new \DateTime); + } +} + +/** + * @Embeddable + */ +class DDC93Timestamps +{ + /** @Column(type = "datetime") */ + public $createdAt; + + public function __construct(\DateTime $createdAt) + { + $this->createdAt = $createdAt; } } From 0cd6061fc28902e024d08ed72f5a23cabc6d5ef7 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Sat, 2 Nov 2013 13:35:41 +0100 Subject: [PATCH 18/29] fixes a bad merge --- .../Tools/Console/Command/RunDqlCommand.php | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php index c8cc50e838f..1189b8194c0 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php @@ -25,7 +25,6 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Doctrine\Common\Util\Debug; -use JMS\Serializer\SerializerBuilder; /** * Command to execute DQL queries in a given EntityManager. @@ -65,11 +64,6 @@ protected function configure() new InputOption( 'depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of Entity graph.', 7 - ), - new InputOption( - 'format', null, InputOption::VALUE_REQUIRED, - 'The output format of the result. Available formats: doctrine-debug (default), jms-serializer-json.', - 'doctrine-debug' ) )) ->setHelp(<<execute(array(), constant($hydrationMode)); - switch ($input->getOption('format')) { - case 'doctrine-debug': - ob_start(); - Debug::dump($resultSet, $input->getOption('depth')); - $message = ob_get_clean(); - - $output->write($message); - break; - - case 'jms-serializer-json': - $serializer = SerializerBuilder::create()->build(); - $output->write($serializer->serialize($resultSet, 'json')); - break; - - default: - throw new \RuntimeException(sprintf('Unknown output format "%s"; available formats: doctrine-debug, jms-serializer-json', $input->getOption('format'))); - } + Debug::dump($resultSet, $input->getOption('depth')); } } From 2b2f4894cb2b21b45d27f68229dae9229ad75b4c Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Sat, 2 Nov 2013 13:55:19 +0100 Subject: [PATCH 19/29] fixes declaring class --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 3a4d080ab25..5ddac59f7e3 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -899,7 +899,8 @@ public function wakeupReflection($reflService) foreach ($this->fieldMappings as $field => $mapping) { if (isset($mapping['declaredField'])) { - $declaringClass = isset($this->embeddedClasses[$field]['declared']) ? $this->embeddedClasses[$field]['declared'] : $this->name; + $declaringClass = isset($this->embeddedClasses[$mapping['declaredField']]['declared']) + ? $this->embeddedClasses[$mapping['declaredField']]['declared'] : $this->name; $this->reflFields[$field] = new ReflectionEmbeddedProperty( $reflService->getAccessibleProperty($declaringClass, $mapping['declaredField']), From 17e0a7b2f8c5d04b87069c414196cd10728fdb68 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Sat, 2 Nov 2013 14:07:14 +0100 Subject: [PATCH 20/29] makes column prefix configurable --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 8 ++++++-- lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php | 1 + lib/Doctrine/ORM/Mapping/Embedded.php | 6 ++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 5ddac59f7e3..c89634f5298 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -3088,7 +3088,8 @@ public function mapEmbedded(array $mapping) $this->assertFieldNotMapped($mapping['fieldName']); $this->embeddedClasses[$mapping['fieldName']] = array( - 'class' => $this->fullyQualifiedClassName($mapping['class']) + 'class' => $this->fullyQualifiedClassName($mapping['class']), + 'columnPrefix' => $mapping['columnPrefix'], ); } @@ -3104,7 +3105,10 @@ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable) $fieldMapping['declaredField'] = $property; $fieldMapping['originalField'] = $fieldMapping['fieldName']; $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName']; - $fieldMapping['columnName'] = $this->namingStrategy->embeddedFieldToColumnName($property, $fieldMapping['columnName'], $this->reflClass->name, $embeddable->reflClass->name); + + $fieldMapping['columnName'] = ! empty($this->embeddedClasses[$property]['columnPrefix']) + ? $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'] + : $this->namingStrategy->embeddedFieldToColumnName($property, $fieldMapping['columnName'], $this->reflClass->name, $embeddable->reflClass->name); $this->mapField($fieldMapping); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index e7893f33ad8..cfb83f0c3a5 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -370,6 +370,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) $metadata->mapManyToMany($mapping); } else if ($embeddedAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Embedded')) { $mapping['class'] = $embeddedAnnot->class; + $mapping['columnPrefix'] = $embeddedAnnot->columnPrefix; $metadata->mapEmbedded($mapping); } } diff --git a/lib/Doctrine/ORM/Mapping/Embedded.php b/lib/Doctrine/ORM/Mapping/Embedded.php index aa4d89356d5..b44bced841b 100644 --- a/lib/Doctrine/ORM/Mapping/Embedded.php +++ b/lib/Doctrine/ORM/Mapping/Embedded.php @@ -26,7 +26,13 @@ final class Embedded implements Annotation { /** + * @Required * @var string */ public $class; + + /** + * @var string + */ + public $columnPrefix; } From 9ad376c0064fae0c8ae6f6c3e15406936323727c Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Tue, 12 Nov 2013 23:49:25 +0100 Subject: [PATCH 21/29] adds docs --- docs/en/index.rst | 3 +- docs/en/toc.rst | 1 + docs/en/tutorials/embeddables.rst | 83 +++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 docs/en/tutorials/embeddables.rst diff --git a/docs/en/index.rst b/docs/en/index.rst index 58753eb2575..383c0693066 100644 --- a/docs/en/index.rst +++ b/docs/en/index.rst @@ -37,7 +37,7 @@ Mapping Objects onto a Database * **Mapping**: :doc:`Objects ` | :doc:`Associations ` | - :doc:`Inheritance ` + :doc:`Inheritance ` | * **Drivers**: :doc:`Docblock Annotations ` | @@ -88,6 +88,7 @@ Tutorials * :doc:`Ordered associations ` * :doc:`Pagination ` * :doc:`Override Field/Association Mappings In Subclasses ` +* :doc:`Embeddables ` Cookbook -------- diff --git a/docs/en/toc.rst b/docs/en/toc.rst index 1a331fa2384..75acd20718f 100644 --- a/docs/en/toc.rst +++ b/docs/en/toc.rst @@ -16,6 +16,7 @@ Tutorials tutorials/ordered-associations tutorials/override-field-association-mappings-in-subclasses tutorials/pagination.rst + tutorials/embeddables.rst Reference Guide --------------- diff --git a/docs/en/tutorials/embeddables.rst b/docs/en/tutorials/embeddables.rst new file mode 100644 index 00000000000..3db2b47b0fe --- /dev/null +++ b/docs/en/tutorials/embeddables.rst @@ -0,0 +1,83 @@ +Separating Concerns using Embeddables +------------------------------------- + +Embeddables are classes which are not entities themself, but are embedded +in entities and can also be queried in DQL. You'll mostly want to use them +to reduce duplication or separating concerns. + +For the purposes of this tutorial, we will assume that you have a ``User`` +class in your application and you would like to store an address in +the ``User`` class. We will model the ``Address`` class as an embeddable +instead of simply adding the respective columns to the ``User`` class. + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + + + .. code-block:: yaml + + User: + type: entity + embedded: + address: + class: Address + + Address: + type: embeddable + columns: + street: { type: string } + postalCode: { type: string } + city: { type: string } + country: { type: string } + +In terms of your database schema, Doctrine will automatically inline all +columns from the ``Address`` class into the table of the ``User`` class, +just as if you had declared them directly there. + +You can also use mapped fields of embedded classes in DQL queries, just +as if they were declared in the ``User`` class: + +.. code-block:: sql + + SELECT u FROM User u WHERE u.address.city = :myCity + From fb3a06b9e7b0e34c2de172f0e03b8c685bc12a6e Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Wed, 13 Nov 2013 00:03:21 +0100 Subject: [PATCH 22/29] adds support for XML/Yaml drivers --- docs/en/tutorials/embeddables.rst | 12 ++++++------ lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 13 +++++++++++++ lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php | 12 ++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/docs/en/tutorials/embeddables.rst b/docs/en/tutorials/embeddables.rst index 3db2b47b0fe..5ec207fa6a0 100644 --- a/docs/en/tutorials/embeddables.rst +++ b/docs/en/tutorials/embeddables.rst @@ -43,14 +43,14 @@ instead of simply adding the respective columns to the ``User`` class. - + - - - - + + + + @@ -64,7 +64,7 @@ instead of simply adding the respective columns to the ``User`` class. Address: type: embeddable - columns: + fields: street: { type: string } postalCode: { type: string } city: { type: string } diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 6e024d04165..3b8bf7e9921 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -69,6 +69,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) isset($xmlRoot['repository-class']) ? (string)$xmlRoot['repository-class'] : null ); $metadata->isMappedSuperclass = true; + } else if ($xmlRoot->getName() == 'embeddable') { + $metadata->isEmbeddedClass = true; } else { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } @@ -241,6 +243,17 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } } + if (isset($xmlRoot->embedded)) { + foreach ($xmlRoot->embedded as $embeddedMapping) { + $mapping = array( + 'fieldName' => (string) $embeddedMapping['name'], + 'class' => (string) $embeddedMapping['class'], + 'columnPrefix' => isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null, + ); + $metadata->mapEmbedded($mapping); + } + } + foreach ($mappings as $mapping) { if (isset($mapping['version'])) { $metadata->setVersionMapping($mapping); diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index 1e7aa33560d..9a1347649ef 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -66,6 +66,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) isset($element['repositoryClass']) ? $element['repositoryClass'] : null ); $metadata->isMappedSuperclass = true; + } else if ($element['type'] == 'embeddable') { + $metadata->isEmbeddedClass = true; } else { throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } @@ -311,6 +313,16 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } } + if (isset($element['embedded'])) { + foreach ($element['embedded'] as $name => $embeddedMapping) { + $mapping = array( + 'fieldName' => $name, + 'class' => $embeddedMapping['class'], + 'columnPrefix' => isset($embeddedMapping['columnPrefix']) ? $embeddedMapping['columnPrefix'] : null, + ); + $metadata->mapEmbedded($mapping); + } + } // Evaluate oneToOne relationships if (isset($element['oneToOne'])) { From 2a73a6f1f7978b0f4a43f5698464f5ef2ad4150b Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Wed, 13 Nov 2013 00:05:55 +0100 Subject: [PATCH 23/29] some cs fixes --- 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 c89634f5298..418ad891197 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -247,7 +247,7 @@ class ClassMetadataInfo implements ClassMetadata public $isMappedSuperclass = false; /** - * READ-ONLY: Wheather this class describes the mapping of an embeddable class. + * READ-ONLY: Whether this class describes the mapping of an embeddable class. * * @var boolean */ @@ -900,7 +900,7 @@ public function wakeupReflection($reflService) 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->embeddedClasses[$mapping['declaredField']]['declared'] : $this->name; $this->reflFields[$field] = new ReflectionEmbeddedProperty( $reflService->getAccessibleProperty($declaringClass, $mapping['declaredField']), From 0ee7b688c367d4593eba8f6f4c6472a42f0e7c51 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Wed, 13 Nov 2013 00:07:20 +0100 Subject: [PATCH 24/29] small fix --- docs/en/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/index.rst b/docs/en/index.rst index 383c0693066..7a348c47a3f 100644 --- a/docs/en/index.rst +++ b/docs/en/index.rst @@ -37,7 +37,7 @@ Mapping Objects onto a Database * **Mapping**: :doc:`Objects ` | :doc:`Associations ` | - :doc:`Inheritance ` | + :doc:`Inheritance ` * **Drivers**: :doc:`Docblock Annotations ` | From e5cab1db2d934d230f01781d8af5277531df8b91 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Thu, 28 Nov 2013 17:32:47 +0100 Subject: [PATCH 25/29] adds embedded classes to cache --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 418ad891197..d34b9656f4f 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -794,6 +794,7 @@ public function __sleep() 'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName'] 'fieldMappings', 'fieldNames', + 'embeddedClasses', 'identifier', 'isIdentifierComposite', // TODO: REMOVE 'name', From 928c32d61626255bf514eadfcdf10288d08a6e09 Mon Sep 17 00:00:00 2001 From: Jan Kramer Date: Sat, 7 Dec 2013 16:04:48 +0100 Subject: [PATCH 26/29] Update XML schema to reflect addition of embeddables --- doctrine-mapping.xsd | 18 ++++++++++++++++++ lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 2 +- .../Tests/Models/ValueObjects/Name.php | 9 +++++++++ .../Tests/Models/ValueObjects/Person.php | 9 +++++++++ ...rine.Tests.Models.ValueObjects.Name.dcm.xml | 10 ++++++++++ ...ne.Tests.Models.ValueObjects.Person.dcm.xml | 12 ++++++++++++ 6 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/Doctrine/Tests/Models/ValueObjects/Name.php create mode 100644 tests/Doctrine/Tests/Models/ValueObjects/Person.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.ValueObjects.Name.dcm.xml create mode 100644 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.ValueObjects.Person.dcm.xml diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index f9c774d5723..b66e027e9fc 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -17,6 +17,7 @@ + @@ -166,6 +167,7 @@ + @@ -212,6 +214,16 @@ + + + + + + + + + + @@ -274,6 +286,12 @@ + + + + + + diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 3b8bf7e9921..95d6ad817ec 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -248,7 +248,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) $mapping = array( 'fieldName' => (string) $embeddedMapping['name'], 'class' => (string) $embeddedMapping['class'], - 'columnPrefix' => isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null, + 'columnPrefix' => isset($embeddedMapping['column-prefix']) ? (string) $embeddedMapping['column-prefix'] : null, ); $metadata->mapEmbedded($mapping); } diff --git a/tests/Doctrine/Tests/Models/ValueObjects/Name.php b/tests/Doctrine/Tests/Models/ValueObjects/Name.php new file mode 100644 index 00000000000..1c836032417 --- /dev/null +++ b/tests/Doctrine/Tests/Models/ValueObjects/Name.php @@ -0,0 +1,9 @@ + + + + + + + diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.ValueObjects.Person.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.ValueObjects.Person.dcm.xml new file mode 100644 index 00000000000..c2480bca7d2 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.ValueObjects.Person.dcm.xml @@ -0,0 +1,12 @@ + + + + + + + + + From fbb7b5ad8e06766cbf1f99cc7f23055958d43da7 Mon Sep 17 00:00:00 2001 From: Jan Kramer Date: Sat, 7 Dec 2013 16:40:54 +0100 Subject: [PATCH 27/29] Fix XmlDriver to accept embeddables --- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 5 ++++ .../ORM/Mapping/XmlMappingDriverTest.php | 25 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 95d6ad817ec..7fb7804078e 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -754,6 +754,11 @@ protected function loadMappingFile($file) $className = (string)$mappedSuperClass['name']; $result[$className] = $mappedSuperClass; } + } else if (isset($xmlElement->embeddable)) { + foreach ($xmlElement->embeddable as $embeddableElement) { + $embeddableName = (string) $embeddableElement['name']; + $result[$embeddableName] = $embeddableElement; + } } return $result; diff --git a/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php index 42b871f1e3a..6d3ed77d167 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/XmlMappingDriverTest.php @@ -3,6 +3,7 @@ namespace Doctrine\Tests\ORM\Mapping; use Doctrine\ORM\Mapping\ClassMetadata, + Doctrine\ORM\Mapping\ClassMetadataFactory, Doctrine\ORM\Mapping\Driver\XmlDriver, Doctrine\ORM\Mapping\Driver\YamlDriver; @@ -38,7 +39,7 @@ public function testIdentifierWithAssociationKey() { $driver = $this->_loadDriver(); $em = $this->_getTestEntityManager(); - $factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory(); + $factory = new ClassMetadataFactory(); $em->getConfiguration()->setMetadataDriverImpl($driver); $factory->setEntityManager($em); @@ -52,6 +53,28 @@ public function testIdentifierWithAssociationKey() $this->assertTrue($class->associationMappings['article']['id']); } + public function testEmbeddableMapping() + { + $class = $this->createClassMetadata('Doctrine\Tests\Models\ValueObjects\Name'); + + $this->assertEquals(true, $class->isEmbeddedClass); + } + + public function testEmbeddedMapping() + { + $class = $this->createClassMetadata('Doctrine\Tests\Models\ValueObjects\Person'); + + $this->assertEquals( + array( + 'name' => array( + 'class' => 'Doctrine\Tests\Models\ValueObjects\Name', + 'columnPrefix' => 'nm_' + ) + ), + $class->embeddedClasses + ); + } + /** * @group DDC-1468 * From 946419459c4395caab084822590ee71080276d30 Mon Sep 17 00:00:00 2001 From: Johannes Schmitt Date: Sat, 4 Jan 2014 17:54:46 +0100 Subject: [PATCH 28/29] fixes bad merge --- lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 657891a4412..8f4a34c0ef4 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -66,7 +66,4 @@ require_once __DIR__.'/../AttributeOverride.php'; require_once __DIR__.'/../AttributeOverrides.php'; require_once __DIR__.'/../EntityListeners.php'; -<<<<<<< HEAD -======= require_once __DIR__.'/../Cache.php'; ->>>>>>> 69cad4079dcfbee273e7451ee6361a82c2432afb From 7020f4135f9ba6d92dad5437ef13a026ea92d93e Mon Sep 17 00:00:00 2001 From: Johannes Schmitt Date: Sat, 4 Jan 2014 18:32:06 +0100 Subject: [PATCH 29/29] skips DQL UPDATE/DELETE tests with SLC enabled --- tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php index 03bab0843b7..2baa72cd045 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php @@ -109,6 +109,10 @@ public function testLoadDql() */ public function testDqlOnEmbeddedObjectsField() { + if ($this->isSecondLevelCacheEnabled) { + $this->markTestSkipped('SLC does not work with UPDATE/DELETE queries through EM.'); + } + $person = new DDC93Person('Johannes', new DDC93Address('Moo', '12345', 'Karlsruhe')); $this->_em->persist($person); $this->_em->flush($person);