Commit cfae81e1 authored by romanb's avatar romanb

[2.0] Fixed hydration for bi-directional many-many associations.

parent 00c44b7c
...@@ -144,8 +144,8 @@ class ObjectHydrator extends AbstractHydrator ...@@ -144,8 +144,8 @@ class ObjectHydrator extends AbstractHydrator
/** /**
* *
* @param <type> $component * @param string $component
* @return <type> * @return PersistentCollection
* @todo Consider inlining this method. * @todo Consider inlining this method.
*/ */
private function getCollection($component) private function getCollection($component)
...@@ -157,24 +157,25 @@ class ObjectHydrator extends AbstractHydrator ...@@ -157,24 +157,25 @@ class ObjectHydrator extends AbstractHydrator
/** /**
* *
* @param <type> $entity * @param object $entity
* @param <type> $name * @param string $name
* @todo Consider inlining this method. * @todo Consider inlining this method.
*/ */
private function initRelatedCollection($entity, $name) private function initRelatedCollection($entity, $name)
{ {
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$classMetadata = $this->_classMetadatas[get_class($entity)]; $classMetadata = $this->_classMetadatas[get_class($entity)];
if ( ! isset($this->_initializedRelations[$oid][$name])) {
$relation = $classMetadata->getAssociationMapping($name); $relation = $classMetadata->getAssociationMapping($name);
$relatedClass = $this->_em->getClassMetadata($relation->getTargetEntityName()); $relatedClass = $this->_em->getClassMetadata($relation->getTargetEntityName());
$coll = $this->getCollection($relatedClass->getClassName()); $coll = $this->getCollection($relatedClass->getClassName());
$coll->setOwner($entity, $relation); $coll->setOwner($entity, $relation);
$coll->setHydrationFlag(true);
$classMetadata->getReflectionProperty($name)->setValue($entity, $coll); $classMetadata->getReflectionProperty($name)->setValue($entity, $coll);
$this->_initializedRelations[$oid][$name] = true;
$this->_uow->setOriginalEntityProperty($oid, $name, $coll); $this->_uow->setOriginalEntityProperty($oid, $name, $coll);
} $this->_initializedRelations[$oid][$name] = true;
return $coll;
} }
private function isIndexKeyInUse($entity, $assocField, $indexField) private function isIndexKeyInUse($entity, $assocField, $indexField)
...@@ -208,39 +209,6 @@ class ObjectHydrator extends AbstractHydrator ...@@ -208,39 +209,6 @@ class ObjectHydrator extends AbstractHydrator
return $entity; return $entity;
} }
/**
* Adds an element to an indexed collection-valued property.
*
* @param <type> $entity1
* @param <type> $property
* @param <type> $entity2
* @param <type> $indexField
* @todo Consider inlining this method. It's called only once and inlining can
* remove the need for the 2 get_class calls.
*/
private function addRelatedIndexedEntity($entity1, $property, $entity2, $indexField)
{
$classMetadata1 = $this->_classMetadatas[get_class($entity1)];
$classMetadata2 = $this->_classMetadatas[get_class($entity2)];
$indexValue = $classMetadata2->getReflectionProperty($indexField)->getValue($entity2);
$classMetadata1->getReflectionProperty($property)->getValue($entity1)->set($indexValue, $entity2);
}
/**
* Adds an element to a collection-valued property.
*
* @param <type> $entity1
* @param <type> $property
* @param <type> $entity2
*/
private function addRelatedEntity($entity1, $property, $entity2)
{
$this->_classMetadatas[get_class($entity1)]
->getReflectionProperty($property)
->getValue($entity1)
->add($entity2);
}
/** /**
* Checks whether a field on an entity has a non-null value. * Checks whether a field on an entity has a non-null value.
* *
...@@ -258,9 +226,9 @@ class ObjectHydrator extends AbstractHydrator ...@@ -258,9 +226,9 @@ class ObjectHydrator extends AbstractHydrator
/** /**
* Sets a related element. * Sets a related element.
* *
* @param <type> $entity1 * @param object $entity1
* @param <type> $property * @param string $property
* @param <type> $entity2 * @param object $entity2
*/ */
private function setRelatedElement($entity1, $property, $entity2) private function setRelatedElement($entity1, $property, $entity2)
{ {
...@@ -270,7 +238,6 @@ class ObjectHydrator extends AbstractHydrator ...@@ -270,7 +238,6 @@ class ObjectHydrator extends AbstractHydrator
$this->_uow->setOriginalEntityProperty($oid, $property, $entity2); $this->_uow->setOriginalEntityProperty($oid, $property, $entity2);
$relation = $classMetadata1->getAssociationMapping($property); $relation = $classMetadata1->getAssociationMapping($property);
if ($relation->isOneToOne()) { if ($relation->isOneToOne()) {
//$targetClass = $this->_em->getClassMetadata($relation->getTargetEntityName());
$targetClass = $this->_classMetadatas[$relation->getTargetEntityName()]; $targetClass = $this->_classMetadatas[$relation->getTargetEntityName()];
if ($relation->isOwningSide()) { if ($relation->isOwningSide()) {
// If there is an inverse mapping on the target class its bidirectional // If there is an inverse mapping on the target class its bidirectional
...@@ -305,7 +272,7 @@ class ObjectHydrator extends AbstractHydrator ...@@ -305,7 +272,7 @@ class ObjectHydrator extends AbstractHydrator
unset($rowData['scalars']); unset($rowData['scalars']);
} }
// Now hydrate the entity data found in the current row. // Hydrate the entity data found in the current row.
foreach ($rowData as $dqlAlias => $data) { foreach ($rowData as $dqlAlias => $data) {
$index = false; $index = false;
$entityName = $this->_resultSetMapping->getClass($dqlAlias)->getClassName(); $entityName = $this->_resultSetMapping->getClass($dqlAlias)->getClassName();
...@@ -335,18 +302,43 @@ class ObjectHydrator extends AbstractHydrator ...@@ -335,18 +302,43 @@ class ObjectHydrator extends AbstractHydrator
// Check the type of the relation (many or single-valued) // Check the type of the relation (many or single-valued)
if ( ! $relation->isOneToOne()) { if ( ! $relation->isOneToOne()) {
//$oneToOne = false;
if (isset($nonemptyComponents[$dqlAlias])) { if (isset($nonemptyComponents[$dqlAlias])) {
$this->initRelatedCollection($baseElement, $relationAlias); if ( ! isset($this->_initializedRelations[spl_object_hash($baseElement)][$relationAlias])) {
$this->initRelatedCollection($baseElement, $relationAlias)
->setHydrationFlag(true);
}
$indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); $indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; $index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? $this->isIndexKeyInUse($baseElement, $relationAlias, $index) : false; $indexIsValid = $index !== false ? $this->isIndexKeyInUse($baseElement, $relationAlias, $index) : false;
if ( ! $indexExists || ! $indexIsValid) { if ( ! $indexExists || ! $indexIsValid) {
$element = $this->getEntity($data, $entityName); $element = $this->getEntity($data, $entityName);
// If it's a bi-directional many-to-many, also initialize the reverse collection.
if ($relation->isManyToMany()) {
if ($relation->isOwningSide()) {
$reverseFieldName = $this->_classMetadatas[get_class($element)]
->getInverseAssociationMapping($relationAlias)
->getSourceFieldName();
$this->initRelatedCollection($element, $reverseFieldName);
} else if ($mappedByField = $relation->getMappedByFieldName()) {
$this->initRelatedCollection($element, $mappedByField);
}
}
if ($field = $this->_getCustomIndexField($dqlAlias)) { if ($field = $this->_getCustomIndexField($dqlAlias)) {
$this->addRelatedIndexedEntity($baseElement, $relationAlias, $element, $field); $indexValue = $this->_classMetadatas[get_class($element)]
->getReflectionProperty($field)
->getValue($element);
$this->_classMetadatas[$parentClass]
->getReflectionProperty($relationAlias)
->getValue($baseElement)
->set($indexValue, $element);
} else { } else {
$this->addRelatedEntity($baseElement, $relationAlias, $element); $this->_classMetadatas[$parentClass]
->getReflectionProperty($relationAlias)
->getValue($baseElement)
->add($element);
} }
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $this->getLastKey( $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $this->getLastKey(
$this->_classMetadatas[$parentClass] $this->_classMetadatas[$parentClass]
...@@ -360,7 +352,6 @@ class ObjectHydrator extends AbstractHydrator ...@@ -360,7 +352,6 @@ class ObjectHydrator extends AbstractHydrator
$this->setRelatedElement($baseElement, $relationAlias, $coll); $this->setRelatedElement($baseElement, $relationAlias, $coll);
} }
} else { } else {
//$oneToOne = true;
if ( ! isset($nonemptyComponents[$dqlAlias]) && if ( ! isset($nonemptyComponents[$dqlAlias]) &&
! $this->isFieldSet($baseElement, $relationAlias)) { ! $this->isFieldSet($baseElement, $relationAlias)) {
$this->setRelatedElement($baseElement, $relationAlias, null); $this->setRelatedElement($baseElement, $relationAlias, null);
...@@ -375,7 +366,7 @@ class ObjectHydrator extends AbstractHydrator ...@@ -375,7 +366,7 @@ class ObjectHydrator extends AbstractHydrator
->getValue($baseElement); ->getValue($baseElement);
if ($coll !== null) { if ($coll !== null) {
$this->updateResultPointer($coll, $index, $dqlAlias/*, $oneToOne*/); $this->updateResultPointer($coll, $index, $dqlAlias);
} }
} else { } else {
// Its a root result element // Its a root result element
...@@ -409,7 +400,7 @@ class ObjectHydrator extends AbstractHydrator ...@@ -409,7 +400,7 @@ class ObjectHydrator extends AbstractHydrator
} else { } else {
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]]; $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
} }
$this->updateResultPointer($result, $index, $dqlAlias/*, false*/); $this->updateResultPointer($result, $index, $dqlAlias);
//unset($rowData[$dqlAlias]); //unset($rowData[$dqlAlias]);
} }
} }
......
...@@ -310,6 +310,8 @@ abstract class AssociationMapping ...@@ -310,6 +310,8 @@ abstract class AssociationMapping
/** /**
* Gets the field name of the owning side in a bi-directional association. * Gets the field name of the owning side in a bi-directional association.
* This is only set on the inverse side. When invoked on the owning side,
* NULL is returned.
* *
* @return string * @return string
*/ */
......
...@@ -129,7 +129,7 @@ class ManyToManyMapping extends AssociationMapping ...@@ -129,7 +129,7 @@ class ManyToManyMapping extends AssociationMapping
public function lazyLoadFor($entity, $entityManager) public function lazyLoadFor($entity, $entityManager)
{ {
//TODO
} }
/** /**
......
...@@ -251,9 +251,15 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection ...@@ -251,9 +251,15 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection
if ($this->_hydrationFlag) { if ($this->_hydrationFlag) {
if ($this->_backRefFieldName) { if ($this->_backRefFieldName) {
// set back reference to owner // Set back reference to owner
if ($this->_association->isOneToMany()) {
$this->_ownerClass->getReflectionProperty($this->_backRefFieldName) $this->_ownerClass->getReflectionProperty($this->_backRefFieldName)
->setValue($value, $this->_owner); ->setValue($value, $this->_owner);
} else {
// ManyToMany
$this->_ownerClass->getReflectionProperty($this->_backRefFieldName)
->getValue($value)->add($this->_owner);
}
} }
} else { } else {
$this->_changed(); $this->_changed();
...@@ -388,6 +394,7 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection ...@@ -388,6 +394,7 @@ final class PersistentCollection extends \Doctrine\Common\Collections\Collection
} }
}*/ }*/
parent::clear(); parent::clear();
$this->_changed();
} }
private function _changed() private function _changed()
......
...@@ -520,7 +520,8 @@ class UnitOfWork implements PropertyChangedListener ...@@ -520,7 +520,8 @@ class UnitOfWork implements PropertyChangedListener
$this->_entityChangeSets[$oid] = $changeSet; $this->_entityChangeSets[$oid] = $changeSet;
$this->_originalEntityData[$oid] = $data; $this->_originalEntityData[$oid] = $data;
} else if ($state == self::STATE_DELETED) { } else if ($state == self::STATE_DELETED) {
throw DoctrineException::updateMe("Deleted entity in collection detected during flush."); throw DoctrineException::updateMe("Deleted entity in collection detected during flush."
. " Make sure you properly remove deleted entities from collections.");
} }
// MANAGED associated entities are already taken into account // MANAGED associated entities are already taken into account
// during changeset calculation anyway, since they are in the identity map. // during changeset calculation anyway, since they are in the identity map.
...@@ -1234,6 +1235,7 @@ class UnitOfWork implements PropertyChangedListener ...@@ -1234,6 +1235,7 @@ class UnitOfWork implements PropertyChangedListener
}*/ }*/
$this->_mergeData($entity, $data, $class, true); $this->_mergeData($entity, $data, $class, true);
$this->_entityIdentifiers[$oid] = $id; $this->_entityIdentifiers[$oid] = $id;
$this->_entityStates[$oid] = self::STATE_MANAGED;
$this->addToIdentityMap($entity); $this->addToIdentityMap($entity);
} }
......
...@@ -41,5 +41,9 @@ class CmsGroup ...@@ -41,5 +41,9 @@ class CmsGroup
public function addUser(CmsUser $user) { public function addUser(CmsUser $user) {
$this->users[] = $user; $this->users[] = $user;
} }
public function getUsers() {
return $this->users;
}
} }
...@@ -291,5 +291,20 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase ...@@ -291,5 +291,20 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(1, $result[0]->getGroups()->count()); $this->assertEquals(1, $result[0]->getGroups()->count());
$groups = $result[0]->getGroups(); $groups = $result[0]->getGroups();
$this->assertEquals('Doctrine Developers', $groups[0]->getName()); $this->assertEquals('Doctrine Developers', $groups[0]->getName());
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($result[0]));
$this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($groups[0]));
$this->assertTrue($groups instanceof \Doctrine\ORM\PersistentCollection);
$this->assertTrue($groups[0]->getUsers() instanceof \Doctrine\ORM\PersistentCollection);
$groups[0]->getUsers()->clear();
$groups->clear();
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u inner join u.groups g");
$this->assertEquals(0, count($query->getResultList()));
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment