Skip to content

Commit

Permalink
[DDC-93] Implement first working version of value objects using a Ref…
Browse files Browse the repository at this point in the history
…lectionProxy object, bypassing changes to UnitOfWork, Persisters and Hydrators.
  • Loading branch information
beberlei committed Mar 26, 2013
1 parent 32988b3 commit 0204a8b
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 6 deletions.
5 changes: 5 additions & 0 deletions lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
Expand Up @@ -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);
}
Expand Down
70 changes: 64 additions & 6 deletions lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
}
}
3 changes: 3 additions & 0 deletions lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php
Expand Up @@ -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);
}
}

Expand Down
64 changes: 64 additions & 0 deletions lib/Doctrine/ORM/Mapping/ReflectionProxy.php
@@ -0,0 +1,64 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/

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);
}
}
4 changes: 4 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/ValueObjectsTest.php
Expand Up @@ -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);
}
}

Expand Down

0 comments on commit 0204a8b

Please sign in to comment.