diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index c56b6eb22..fc41f067f 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -94,13 +94,7 @@ class ObjectHydrator extends AbstractHydrator $sourceClass = $this->_getClassMetadata($sourceClassName); $assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]]; - $this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true; - - if ($sourceClass->subClasses) { - foreach ($sourceClass->subClasses as $sourceSubclassName) { - $this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true; - } - } + $this->_hints['fetched'][$this->_rsm->parentAliasMap[$dqlAlias]][$assoc['fieldName']] = true; if ($assoc['type'] === ClassMetadata::MANY_TO_MANY) { continue; @@ -108,11 +102,12 @@ class ObjectHydrator extends AbstractHydrator // Mark any non-collection opposite sides as fetched, too. if ($assoc['mappedBy']) { - $this->_hints['fetched'][$className][$assoc['mappedBy']] = true; + $this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true; continue; } + // handle fetch-joined owning side bi-directional one-to-one associations if ($assoc['inversedBy']) { $class = $this->_ce[$className]; $inverseAssoc = $class->associationMappings[$assoc['inversedBy']]; @@ -121,13 +116,7 @@ class ObjectHydrator extends AbstractHydrator continue; } - $this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true; - - if ($class->subClasses) { - foreach ($class->subClasses as $targetSubclassName) { - $this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true; - } - } + $this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true; } } } @@ -175,9 +164,11 @@ class ObjectHydrator extends AbstractHydrator * Initializes a related collection. * * @param object $entity The entity to which the collection belongs. + * @param ClassMetadata $class * @param string $name The name of the field on the entity that holds the collection. + * @param string $parentDqlAlias Alias of the parent fetch joining this collection. */ - private function _initRelatedCollection($entity, $class, $fieldName) + private function _initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias) { $oid = spl_object_hash($entity); $relation = $class->associationMappings[$fieldName]; @@ -199,7 +190,7 @@ class ObjectHydrator extends AbstractHydrator $this->_initializedCollections[$oid . $fieldName] = $value; } else if ( isset($this->_hints[Query::HINT_REFRESH]) || - isset($this->_hints['fetched'][$class->name][$fieldName]) && + isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) && ! $value->isInitialized() ) { // Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED! @@ -243,6 +234,7 @@ class ObjectHydrator extends AbstractHydrator $this->registerManaged($this->_ce[$className], $this->_hints[Query::HINT_REFRESH_ENTITY], $data); } + $this->_hints['fetchAlias'] = $dqlAlias; return $this->_uow->createEntity($className, $data, $this->_hints); } @@ -367,7 +359,7 @@ class ObjectHydrator extends AbstractHydrator if (isset($this->_initializedCollections[$collKey])) { $reflFieldValue = $this->_initializedCollections[$collKey]; } else if ( ! isset($this->_existingCollections[$collKey])) { - $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); + $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } $indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]); @@ -402,7 +394,7 @@ class ObjectHydrator extends AbstractHydrator $this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; } } else if ( ! $reflField->getValue($parentObject)) { - $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField); + $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); } } else { // PATH B: Single-valued association diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 1404569c8..513f58b5a 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -116,7 +116,7 @@ class UnitOfWork implements PropertyChangedListener * Map of entities that are scheduled for dirty checking at commit time. * This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT. * Keys are object ids (spl_object_hash). - * + * * @var array * @todo rename: scheduledForSynchronization */ @@ -135,10 +135,10 @@ class UnitOfWork implements PropertyChangedListener * @var array */ private $entityUpdates = array(); - + /** * Any pending extra updates that have been scheduled by persisters. - * + * * @var array */ private $extraUpdates = array(); @@ -201,17 +201,17 @@ class UnitOfWork implements PropertyChangedListener * @var array */ private $collectionPersisters = array(); - + /** * The EventManager used for dispatching events. - * + * * @var EventManager */ private $evm; - + /** * Orphaned entities that are scheduled for removal. - * + * * @var array */ private $orphanRemovals = array(); @@ -225,7 +225,7 @@ class UnitOfWork implements PropertyChangedListener /** * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested. - * + * * @var array */ private $eagerLoadingEntities = array(); @@ -245,9 +245,9 @@ class UnitOfWork implements PropertyChangedListener * Commits the UnitOfWork, executing all operations that have been postponed * up to this point. The state of all managed entities will be synchronized with * the database. - * + * * The operations are executed in the following order: - * + * * 1) All entity insertions * 2) All entity updates * 3) All collection deletions @@ -285,18 +285,18 @@ class UnitOfWork implements PropertyChangedListener $this->remove($orphan); } } - + // Raise onFlush if ($this->evm->hasListeners(Events::onFlush)) { $this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->em)); } - + // Now we need a commit order to maintain referential integrity $commitOrder = $this->getCommitOrder(); $conn = $this->em->getConnection(); $conn->beginTransaction(); - + try { if ($this->entityInsertions) { foreach ($commitOrder as $class) { @@ -335,10 +335,10 @@ class UnitOfWork implements PropertyChangedListener } catch (Exception $e) { $this->em->close(); $conn->rollback(); - + throw $e; } - + // Take new snapshots from visited collections foreach ($this->visitedCollections as $coll) { $coll->takeSnapshot(); @@ -371,7 +371,7 @@ class UnitOfWork implements PropertyChangedListener { foreach ($this->entityInsertions as $entity) { $class = $this->em->getClassMetadata(get_class($entity)); - + $this->computeChangeSet($class, $entity); } } @@ -413,12 +413,12 @@ class UnitOfWork implements PropertyChangedListener // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); - + if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } } - + /** * Executes any extra updates that have been scheduled. */ @@ -426,7 +426,7 @@ class UnitOfWork implements PropertyChangedListener { foreach ($this->extraUpdates as $oid => $update) { list ($entity, $changeset) = $update; - + $this->entityChangeSets[$oid] = $changeset; $this->getEntityPersister(get_class($entity))->update($entity); } @@ -440,11 +440,11 @@ class UnitOfWork implements PropertyChangedListener public function getEntityChangeSet($entity) { $oid = spl_object_hash($entity); - + if (isset($this->entityChangeSets[$oid])) { return $this->entityChangeSets[$oid]; } - + return array(); } @@ -489,39 +489,39 @@ class UnitOfWork implements PropertyChangedListener if ( ! $class->isInheritanceTypeNone()) { $class = $this->em->getClassMetadata(get_class($entity)); } - + // Fire PreFlush lifecycle callbacks if (isset($class->lifecycleCallbacks[Events::preFlush])) { $class->invokeLifecycleCallbacks(Events::preFlush, $entity); } $actualData = array(); - + foreach ($class->reflFields as $name => $refProp) { $value = $refProp->getValue($entity); - + if ($class->isCollectionValuedAssociation($name) && $value !== null && ! ($value instanceof PersistentCollection)) { // If $value is not a Collection then use an ArrayCollection. if ( ! $value instanceof Collection) { $value = new ArrayCollection($value); } - + $assoc = $class->associationMappings[$name]; - + // Inject PersistentCollection $value = new PersistentCollection( $this->em, $this->em->getClassMetadata($assoc['targetEntity']), $value ); $value->setOwner($entity, $assoc); $value->setDirty( ! $value->isEmpty()); - + $class->reflFields[$name]->setValue($entity, $value); - + $actualData[$name] = $value; - + continue; } - + if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) { $actualData[$name] = $value; } @@ -532,29 +532,29 @@ class UnitOfWork implements PropertyChangedListener // These result in an INSERT. $this->originalEntityData[$oid] = $actualData; $changeSet = array(); - + foreach ($actualData as $propName => $actualValue) { if ( ! isset($class->associationMappings[$propName])) { $changeSet[$propName] = array(null, $actualValue); - + continue; } - + $assoc = $class->associationMappings[$propName]; - + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { $changeSet[$propName] = array(null, $actualValue); } } - + $this->entityChangeSets[$oid] = $changeSet; } else { // Entity is "fully" MANAGED: it was already fully persisted before // and we have a copy of the original data $originalData = $this->originalEntityData[$oid]; $isChangeTrackingNotify = $class->isChangeTrackingNotify(); - $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) - ? $this->entityChangeSets[$oid] + $changeSet = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid])) + ? $this->entityChangeSets[$oid] : array(); foreach ($actualData as $propName => $actualValue) { @@ -562,38 +562,38 @@ class UnitOfWork implements PropertyChangedListener if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) { continue; } - + $orgValue = $originalData[$propName]; - + // skip if value havent changed if ($orgValue === $actualValue) { continue; } - + // if regular field if ( ! isset($class->associationMappings[$propName])) { if ($isChangeTrackingNotify) { continue; } - + $changeSet[$propName] = array($orgValue, $actualValue); - + continue; } - + $assoc = $class->associationMappings[$propName]; - + if ($orgValue instanceof PersistentCollection) { // A PersistentCollection was de-referenced, so delete it. $coid = spl_object_hash($orgValue); - + if (isset($this->collectionDeletions[$coid])) { continue; } - + $this->collectionDeletions[$coid] = $orgValue; $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored. - + continue; } @@ -601,13 +601,13 @@ class UnitOfWork implements PropertyChangedListener if ($assoc['isOwningSide']) { $changeSet[$propName] = array($orgValue, $actualValue); } - + if ($orgValue !== null && $assoc['orphanRemoval']) { $this->scheduleOrphanRemoval($orgValue); } } } - + if ($changeSet) { $this->entityChangeSets[$oid] = $changeSet; $this->originalEntityData[$oid] = $actualData; @@ -648,14 +648,14 @@ class UnitOfWork implements PropertyChangedListener case ($class->isChangeTrackingDeferredImplicit()): $entitiesToProcess = $entities; break; - + case (isset($this->scheduledForDirtyCheck[$className])): $entitiesToProcess = $this->scheduledForDirtyCheck[$className]; break; - + default: $entitiesToProcess = array(); - + } foreach ($entitiesToProcess as $entity) { @@ -663,10 +663,10 @@ class UnitOfWork implements PropertyChangedListener if ($entity instanceof Proxy && ! $entity->__isInitialized__) { continue; } - + // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION are processed here. $oid = spl_object_hash($entity); - + if ( ! isset($this->entityInsertions[$oid]) && isset($this->entityStates[$oid])) { $this->computeChangeSet($class, $entity); } @@ -685,60 +685,60 @@ class UnitOfWork implements PropertyChangedListener if ($value instanceof Proxy && ! $value->__isInitialized__) { return; } - + if ($value instanceof PersistentCollection && $value->isDirty()) { $coid = spl_object_hash($value); - + if ($assoc['isOwningSide']) { $this->collectionUpdates[$coid] = $value; } - + $this->visitedCollections[$coid] = $value; } - - // Look through the entities, and in any of their associations, + + // Look through the entities, and in any of their associations, // for transient (new) entities, recursively. ("Persistence by reachability") // Unwrap. Uninitialized collections will simply be empty. $unwrappedValue = ($assoc['type'] & ClassMetadata::TO_ONE) ? array($value) : $value->unwrap(); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - + foreach ($unwrappedValue as $key => $entry) { $state = $this->getEntityState($entry, self::STATE_NEW); $oid = spl_object_hash($entry); - + switch ($state) { case self::STATE_NEW: if ( ! $assoc['isCascadePersist']) { - $message = "A new entity was found through the relationship '%s#%s' that was not configured " . - ' to cascade persist operations for entity: %s. Explicitly persist the new entity or ' . + $message = "A new entity was found through the relationship '%s#%s' that was not configured " . + ' to cascade persist operations for entity: %s. Explicitly persist the new entity or ' . 'configure cascading persist operations on tbe relationship. If you cannot find out ' . 'which entity causes the problem, implement %s#__toString() to get a clue.'; - + throw new InvalidArgumentException(sprintf( $message, $assoc['sourceEntity'], $assoc['fieldName'], self::objToStr($entry), $assoc['targetEntity'] )); } - + $this->persistNew($targetClass, $entry); $this->computeChangeSet($targetClass, $entry); break; - + case self::STATE_REMOVED: - // Consume the $value as array (it's either an array or an ArrayAccess) + // Consume the $value as array (it's either an array or an ArrayAccess) // and remove the element from Collection. if ($assoc['type'] & ClassMetadata::TO_MANY) { unset($value[$key]); } break; - + case self::STATE_DETACHED: // Can actually not happen right now as we assume STATE_NEW, // so the exception will be raised from the DBAL layer (constraint violation). $message = 'A detached entity was found through a relationship during cascading a persist operation.'; - + throw new InvalidArgumentException($message); break; - + default: // MANAGED associated entities are already taken into account // during changeset calculation anyway, since they are in the identity map. @@ -749,43 +749,43 @@ class UnitOfWork implements PropertyChangedListener private function persistNew($class, $entity) { $oid = spl_object_hash($entity); - + if (isset($class->lifecycleCallbacks[Events::prePersist])) { $class->invokeLifecycleCallbacks(Events::prePersist, $entity); } - + if ($this->evm->hasListeners(Events::prePersist)) { $this->evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($entity, $this->em)); } $idGen = $class->idGenerator; - + if ( ! $idGen->isPostInsertGenerator()) { $idValue = $idGen->generate($this->em, $entity); - + if ( ! $idGen instanceof \Doctrine\ORM\Id\AssignedGenerator) { $idValue = array($class->identifier[0] => $idValue); - + $class->setIdentifierValues($entity, $idValue); } - + $this->entityIdentifiers[$oid] = $idValue; } - + $this->entityStates[$oid] = self::STATE_MANAGED; $this->scheduleForInsert($entity); } - + /** * INTERNAL: * Computes the changeset of an individual entity, independently of the * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit(). - * + * * The passed entity must be a managed entity. If the entity already has a change set * because this method is invoked during a commit cycle then the change sets are added. * whereby changes detected in this method prevail. - * + * * @ignore * @param ClassMetadata $class The class descriptor of the entity. * @param object $entity The entity for which to (re)calculate the change set. @@ -794,11 +794,11 @@ class UnitOfWork implements PropertyChangedListener public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) { $oid = spl_object_hash($entity); - + if ( ! isset($this->entityStates[$oid]) || $this->entityStates[$oid] != self::STATE_MANAGED) { throw new InvalidArgumentException('Entity must be managed.'); } - + // skip if change tracking is "NOTIFY" if ($class->isChangeTrackingNotify()) { return; @@ -809,7 +809,7 @@ class UnitOfWork implements PropertyChangedListener } $actualData = array(); - + foreach ($class->reflFields as $name => $refProp) { if ( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) { $actualData[$name] = $refProp->getValue($entity); @@ -821,7 +821,7 @@ class UnitOfWork implements PropertyChangedListener foreach ($actualData as $propName => $actualValue) { $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null; - + if (is_object($orgValue) && $orgValue !== $actualValue) { $changeSet[$propName] = array($orgValue, $actualValue); } else if ($orgValue != $actualValue || ($orgValue === null ^ $actualValue === null)) { @@ -833,7 +833,7 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->entityChangeSets[$oid])) { $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet); } - + $this->originalEntityData[$oid] = $actualData; } } @@ -848,19 +848,19 @@ class UnitOfWork implements PropertyChangedListener $className = $class->name; $persister = $this->getEntityPersister($className); $entities = array(); - + $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postPersist]); $hasListeners = $this->evm->hasListeners(Events::postPersist); - + foreach ($this->entityInsertions as $oid => $entity) { if (get_class($entity) !== $className) { continue; } - + $persister->addInsert($entity); - + unset($this->entityInsertions[$oid]); - + if ($hasLifecycleCallbacks || $hasListeners) { $entities[] = $entity; } @@ -873,17 +873,17 @@ class UnitOfWork implements PropertyChangedListener foreach ($postInsertIds as $id => $entity) { $oid = spl_object_hash($entity); $idField = $class->identifier[0]; - + $class->reflFields[$idField]->setValue($entity, $id); - + $this->entityIdentifiers[$oid] = array($idField => $id); $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid][$idField] = $id; - + $this->addToIdentityMap($entity); } } - + foreach ($entities as $entity) { if ($hasLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::postPersist, $entity); @@ -907,24 +907,24 @@ class UnitOfWork implements PropertyChangedListener $hasPreUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::preUpdate]); $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate); - + $hasPostUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postUpdate]); $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate); - + foreach ($this->entityUpdates as $oid => $entity) { if ( ! (get_class($entity) === $className || $entity instanceof Proxy && get_parent_class($entity) === $className)) { continue; } - + if ($hasPreUpdateLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::preUpdate, $entity); - + $this->recomputeSingleEntityChangeSet($class, $entity); } if ($hasPreUpdateListeners) { $this->evm->dispatchEvent( - Events::preUpdate, + Events::preUpdate, new Event\PreUpdateEventArgs($entity, $this->em, $this->entityChangeSets[$oid]) ); } @@ -932,13 +932,13 @@ class UnitOfWork implements PropertyChangedListener if ($this->entityChangeSets[$oid]) { $persister->update($entity); } - + unset($this->entityUpdates[$oid]); if ($hasPostUpdateLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::postUpdate, $entity); } - + if ($hasPostUpdateListeners) { $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entity, $this->em)); } @@ -954,24 +954,24 @@ class UnitOfWork implements PropertyChangedListener { $className = $class->name; $persister = $this->getEntityPersister($className); - + $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postRemove]); $hasListeners = $this->evm->hasListeners(Events::postRemove); - + foreach ($this->entityDeletions as $oid => $entity) { if ( ! (get_class($entity) == $className || $entity instanceof Proxy && get_parent_class($entity) == $className)) { continue; } - + $persister->delete($entity); - + unset( $this->entityDeletions[$oid], $this->entityIdentifiers[$oid], $this->originalEntityData[$oid], $this->entityStates[$oid] ); - + // Entity with this $oid after deletion treated as NEW, even if the $oid // is obtained by a new entity because the old one went out of scope. //$this->entityStates[$oid] = self::STATE_NEW; @@ -982,7 +982,7 @@ class UnitOfWork implements PropertyChangedListener if ($hasLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::postRemove, $entity); } - + if ($hasListeners) { $this->evm->dispatchEvent(Events::postRemove, new LifecycleEventArgs($entity, $this->em)); } @@ -999,60 +999,60 @@ class UnitOfWork implements PropertyChangedListener if ($entityChangeSet === null) { $entityChangeSet = array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions); } - + $calc = $this->getCommitOrderCalculator(); - + // See if there are any new classes in the changeset, that are not in the // commit order graph yet (dont have a node). // We have to inspect changeSet to be able to correctly build dependencies. - // It is not possible to use IdentityMap here because post inserted ids + // It is not possible to use IdentityMap here because post inserted ids // are not yet available. $newNodes = array(); - + foreach ($entityChangeSet as $oid => $entity) { $className = get_class($entity); - + if ($calc->hasClass($className)) { continue; } - + $class = $this->em->getClassMetadata($className); $calc->addClass($class); - + $newNodes[] = $class; } - + // Calculate dependencies for new nodes while ($class = array_pop($newNodes)) { foreach ($class->associationMappings as $assoc) { if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { continue; } - + $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - + if ( ! $calc->hasClass($targetClass->name)) { $calc->addClass($targetClass); - + $newNodes[] = $targetClass; } - + $calc->addDependency($targetClass, $class); - + // If the target class has mapped subclasses, these share the same dependency. if ( ! $targetClass->subClasses) { continue; } - + foreach ($targetClass->subClasses as $subClassName) { $targetSubClass = $this->em->getClassMetadata($subClassName); - + if ( ! $calc->hasClass($subClassName)) { $calc->addClass($targetSubClass); - + $newNodes[] = $targetSubClass; } - + $calc->addDependency($targetSubClass, $class); } } @@ -1074,11 +1074,11 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->entityUpdates[$oid])) { throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion."); } - + if (isset($this->entityDeletions[$oid])) { throw new InvalidArgumentException("Removed entity can not be scheduled for insertion."); } - + if (isset($this->entityInsertions[$oid])) { throw new InvalidArgumentException("Entity can not be scheduled for insertion twice."); } @@ -1109,11 +1109,11 @@ class UnitOfWork implements PropertyChangedListener public function scheduleForUpdate($entity) { $oid = spl_object_hash($entity); - + if ( ! isset($this->entityIdentifiers[$oid])) { throw new InvalidArgumentException("Entity has no identity."); } - + if (isset($this->entityDeletions[$oid])) { throw new InvalidArgumentException("Entity is removed."); } @@ -1122,14 +1122,14 @@ class UnitOfWork implements PropertyChangedListener $this->entityUpdates[$oid] = $entity; } } - + /** * INTERNAL: * Schedules an extra update that will be executed immediately after the * regular entity updates within the currently running commit cycle. - * + * * Extra updates for entities are stored as (entity, changeset) tuples. - * + * * @ignore * @param object $entity The entity for which to schedule an extra update. * @param array $changeset The changeset of the entity (what to update). @@ -1138,13 +1138,13 @@ class UnitOfWork implements PropertyChangedListener { $oid = spl_object_hash($entity); $extraUpdate = array($entity, $changeset); - + if (isset($this->extraUpdates[$oid])) { list($ignored, $changeset2) = $this->extraUpdates[$oid]; - + $extraUpdate = array($entity, $changeset + $changeset2); } - + $this->extraUpdates[$oid] = $extraUpdate; } @@ -1161,7 +1161,7 @@ class UnitOfWork implements PropertyChangedListener return isset($this->entityUpdates[spl_object_hash($entity)]); } - + /** * Checks whether an entity is registered to be checked in the unit of work. * @@ -1171,40 +1171,40 @@ class UnitOfWork implements PropertyChangedListener public function isScheduledForDirtyCheck($entity) { $rootEntityName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; - + return isset($this->scheduledForDirtyCheck[$rootEntityName][spl_object_hash($entity)]); } /** * INTERNAL: * Schedules an entity for deletion. - * + * * @param object $entity */ public function scheduleForDelete($entity) { $oid = spl_object_hash($entity); - + if (isset($this->entityInsertions[$oid])) { if ($this->isInIdentityMap($entity)) { $this->removeFromIdentityMap($entity); } - + unset($this->entityInsertions[$oid], $this->entityStates[$oid]); - + return; // entity has not been persisted yet, so nothing more to do. } if ( ! $this->isInIdentityMap($entity)) { return; } - + $this->removeFromIdentityMap($entity); - + if (isset($this->entityUpdates[$oid])) { unset($this->entityUpdates[$oid]); } - + if ( ! isset($this->entityDeletions[$oid])) { $this->entityDeletions[$oid] = $entity; $this->entityStates[$oid] = self::STATE_REMOVED; @@ -1225,16 +1225,16 @@ class UnitOfWork implements PropertyChangedListener /** * Checks whether an entity is scheduled for insertion, update or deletion. - * + * * @param $entity * @return boolean */ public function isEntityScheduled($entity) { $oid = spl_object_hash($entity); - - return isset($this->entityInsertions[$oid]) - || isset($this->entityUpdates[$oid]) + + return isset($this->entityInsertions[$oid]) + || isset($this->entityUpdates[$oid]) || isset($this->entityDeletions[$oid]); } @@ -1253,23 +1253,23 @@ class UnitOfWork implements PropertyChangedListener { $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[spl_object_hash($entity)]); - + if ($idHash === '') { throw new InvalidArgumentException('The given entity has no identity.'); } - + $className = $classMetadata->rootEntityName; - + if (isset($this->identityMap[$className][$idHash])) { return false; } - + $this->identityMap[$className][$idHash] = $entity; - + if ($entity instanceof NotifyPropertyChanged) { $entity->addPropertyChangedListener($this); } - + return true; } @@ -1286,26 +1286,26 @@ class UnitOfWork implements PropertyChangedListener public function getEntityState($entity, $assume = null) { $oid = spl_object_hash($entity); - + if (isset($this->entityStates[$oid])) { return $this->entityStates[$oid]; } - + if ($assume !== null) { return $assume; } - + // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known. // Note that you can not remember the NEW or DETACHED state in _entityStates since // the UoW does not hold references to such objects and the object hash can be reused. // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it. $class = $this->em->getClassMetadata(get_class($entity)); $id = $class->getIdentifierValues($entity); - + if ( ! $id) { return self::STATE_NEW; } - + switch (true) { case ($class->isIdentifierNatural()); // Check for a version field, if available, to avoid a db lookup. @@ -1314,19 +1314,19 @@ class UnitOfWork implements PropertyChangedListener ? self::STATE_DETACHED : self::STATE_NEW; } - + // Last try before db lookup: check the identity map. if ($this->tryGetById($id, $class->rootEntityName)) { return self::STATE_DETACHED; } - + // db lookup if ($this->getEntityPersister(get_class($entity))->exists($entity)) { return self::STATE_DETACHED; } - + return self::STATE_NEW; - + case ( ! $class->idGenerator->isPostInsertGenerator()): // if we have a pre insert generator we can't be sure that having an id // really means that the entity exists. We have to verify this through @@ -1335,15 +1335,15 @@ class UnitOfWork implements PropertyChangedListener // Last try before db lookup: check the identity map. if ($this->tryGetById($id, $class->rootEntityName)) { return self::STATE_DETACHED; - } - + } + // db lookup if ($this->getEntityPersister(get_class($entity))->exists($entity)) { return self::STATE_DETACHED; } - + return self::STATE_NEW; - + default: return self::STATE_DETACHED; } @@ -1363,18 +1363,18 @@ class UnitOfWork implements PropertyChangedListener $oid = spl_object_hash($entity); $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[$oid]); - + if ($idHash === '') { throw new InvalidArgumentException('The given entity has no identity.'); } - + $className = $classMetadata->rootEntityName; - + if (isset($this->identityMap[$className][$idHash])) { unset($this->identityMap[$className][$idHash]); - + //$this->entityStates[$oid] = self::STATE_DETACHED; - + return true; } @@ -1410,7 +1410,7 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->identityMap[$rootClassName][$idHash])) { return $this->identityMap[$rootClassName][$idHash]; } - + return false; } @@ -1423,18 +1423,18 @@ class UnitOfWork implements PropertyChangedListener public function isInIdentityMap($entity) { $oid = spl_object_hash($entity); - + if ( ! isset($this->entityIdentifiers[$oid])) { return false; } - + $classMetadata = $this->em->getClassMetadata(get_class($entity)); $idHash = implode(' ', $this->entityIdentifiers[$oid]); - + if ($idHash === '') { return false; } - + return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]); } @@ -1460,13 +1460,13 @@ class UnitOfWork implements PropertyChangedListener public function persist($entity) { $visited = array(); - + $this->doPersist($entity, $visited); } /** * Persists an entity as part of the current unit of work. - * + * * This method is internally called during persist() cascades as it tracks * the already visited entities to prevent infinite recursions. * @@ -1476,7 +1476,7 @@ class UnitOfWork implements PropertyChangedListener private function doPersist($entity, array &$visited) { $oid = spl_object_hash($entity); - + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1498,22 +1498,22 @@ class UnitOfWork implements PropertyChangedListener $this->scheduleForDirtyCheck($entity); } break; - + case self::STATE_NEW: $this->persistNew($class, $entity); break; - + case self::STATE_REMOVED: // Entity becomes managed again unset($this->entityDeletions[$oid]); - + $this->entityStates[$oid] = self::STATE_MANAGED; break; - + case self::STATE_DETACHED: // Can actually not happen right now since we assume STATE_NEW. throw new InvalidArgumentException('Detached entity passed to persist().'); - + default: throw new UnexpectedValueException(sprintf('Unexpected entity state: %s', $entityState)); } @@ -1529,7 +1529,7 @@ class UnitOfWork implements PropertyChangedListener public function remove($entity) { $visited = array(); - + $this->doRemove($entity, $visited); } @@ -1546,41 +1546,41 @@ class UnitOfWork implements PropertyChangedListener private function doRemove($entity, array &$visited) { $oid = spl_object_hash($entity); - + if (isset($visited[$oid])) { return; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited - - // Cascade first, because scheduleForDelete() removes the entity from the identity map, which + + // Cascade first, because scheduleForDelete() removes the entity from the identity map, which // can cause problems when a lazy proxy has to be initialized for the cascade operation. $this->cascadeRemove($entity, $visited); $class = $this->em->getClassMetadata(get_class($entity)); $entityState = $this->getEntityState($entity); - + switch ($entityState) { case self::STATE_NEW: case self::STATE_REMOVED: // nothing to do break; - + case self::STATE_MANAGED: if (isset($class->lifecycleCallbacks[Events::preRemove])) { $class->invokeLifecycleCallbacks(Events::preRemove, $entity); } - + if ($this->evm->hasListeners(Events::preRemove)) { $this->evm->dispatchEvent(Events::preRemove, new LifecycleEventArgs($entity, $this->em)); } - + $this->scheduleForDelete($entity); break; - + case self::STATE_DETACHED: throw new InvalidArgumentException('A detached entity can not be removed.'); - + default: throw new UnexpectedValueException(sprintf('Unexpected entity state: %s', $entityState)); } @@ -1600,7 +1600,7 @@ class UnitOfWork implements PropertyChangedListener public function merge($entity) { $visited = array(); - + return $this->doMerge($entity, $visited); } @@ -1617,7 +1617,7 @@ class UnitOfWork implements PropertyChangedListener private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null) { $oid = spl_object_hash($entity); - + if (isset($visited[$oid])) { return; // Prevent infinite recursion } @@ -1631,7 +1631,7 @@ class UnitOfWork implements PropertyChangedListener // we need to fetch it from the db anyway in order to merge. // MANAGED entities are ignored by the merge operation. $managedCopy = $entity; - + if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) { if ($entity instanceof Proxy && ! $entity->__isInitialized__) { $entity->__load(); @@ -1643,11 +1643,11 @@ class UnitOfWork implements PropertyChangedListener // If there is no ID, it is actually NEW. if ( ! $id) { $managedCopy = $class->newInstance(); - + $this->persistNew($class, $managedCopy); } else { $managedCopy = $this->tryGetById($id, $class->rootEntityName); - + if ($managedCopy) { // We have the entity in-memory already, just make sure its not removed. if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) { @@ -1665,10 +1665,10 @@ class UnitOfWork implements PropertyChangedListener if ( ! $class->isIdentifierNatural()) { throw new EntityNotFoundException; } - + $managedCopy = $class->newInstance(); $class->setIdentifierValues($managedCopy, $id); - + $this->persistNew($class, $managedCopy); } } @@ -1676,7 +1676,7 @@ class UnitOfWork implements PropertyChangedListener if ($class->isVersioned) { $managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy); $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); - + // Throw exception if versions dont match. if ($managedCopyVersion != $entityVersion) { throw OptimisticLockException::lockFailedVersionMissmatch($entity, $entityVersion, $managedCopyVersion); @@ -1735,12 +1735,12 @@ class UnitOfWork implements PropertyChangedListener } if ($assoc2['isCascadeMerge']) { $managedCol->initialize(); - + // clear and set dirty a managed collection if its not also the same collection to merge from. if (!$managedCol->isEmpty() && $managedCol != $mergeCol) { $managedCol->unwrap()->clear(); $managedCol->setDirty(true); - + if ($assoc2['isOwningSide'] && $assoc2['type'] == ClassMetadata::MANY_TO_MANY && $class->isChangeTrackingNotify()) { $this->scheduleForDirtyCheck($managedCopy); } @@ -1748,13 +1748,13 @@ class UnitOfWork implements PropertyChangedListener } } } - + if ($class->isChangeTrackingNotify()) { // Just treat all properties as changed, there is no other choice. $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy)); } } - + if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } @@ -1763,12 +1763,12 @@ class UnitOfWork implements PropertyChangedListener if ($prevManagedCopy !== null) { $assocField = $assoc['fieldName']; $prevClass = $this->em->getClassMetadata(get_class($prevManagedCopy)); - + if ($assoc['type'] & ClassMetadata::TO_ONE) { $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy); } else { $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy); - + if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy); } @@ -1782,7 +1782,7 @@ class UnitOfWork implements PropertyChangedListener return $managedCopy; } - + /** * Detaches an entity from the persistence management. It's persistence will * no longer be managed by Doctrine. @@ -1794,10 +1794,10 @@ class UnitOfWork implements PropertyChangedListener $visited = array(); $this->doDetach($entity, $visited); } - + /** * Executes a detach operation on the given entity. - * + * * @param object $entity * @param array $visited * @param boolean $noCascade if true, don't cascade detach operation @@ -1810,7 +1810,7 @@ class UnitOfWork implements PropertyChangedListener } $visited[$oid] = $entity; // mark visited - + switch ($this->getEntityState($entity, self::STATE_DETACHED)) { case self::STATE_MANAGED: if ($this->isInIdentityMap($entity)) { @@ -1829,11 +1829,11 @@ class UnitOfWork implements PropertyChangedListener $this->cascadeDetach($entity, $visited); } } - + /** * Refreshes the state of the given entity from the database, overwriting * any local, unpersisted changes. - * + * * @param object $entity The entity to refresh. * @throws InvalidArgumentException If the entity is not MANAGED. */ @@ -1842,10 +1842,10 @@ class UnitOfWork implements PropertyChangedListener $visited = array(); $this->doRefresh($entity, $visited); } - + /** * Executes a refresh operation on an entity. - * + * * @param object $entity The entity to refresh. * @param array $visited The already visited entities during cascades. * @throws InvalidArgumentException If the entity is not MANAGED. @@ -1868,10 +1868,10 @@ class UnitOfWork implements PropertyChangedListener } else { throw new InvalidArgumentException("Entity is not MANAGED."); } - + $this->cascadeRefresh($entity, $visited); } - + /** * Cascades a refresh operation to associated entities. * @@ -1899,7 +1899,7 @@ class UnitOfWork implements PropertyChangedListener } } } - + /** * Cascades a detach operation to associated entities. * @@ -1975,7 +1975,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! $assoc['isCascadePersist']) { continue; } - + $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); if (($relatedEntities instanceof Collection || is_array($relatedEntities))) { if ($relatedEntities instanceof PersistentCollection) { @@ -2000,18 +2000,18 @@ class UnitOfWork implements PropertyChangedListener private function cascadeRemove($entity, array &$visited) { $class = $this->em->getClassMetadata(get_class($entity)); - + foreach ($class->associationMappings as $assoc) { if ( ! $assoc['isCascadeRemove']) { continue; } - + if ($entity instanceof Proxy && !$entity->__isInitialized__) { $entity->__load(); } - + $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity); - + if ($relatedEntities instanceof Collection || is_array($relatedEntities)) { // If its a PersistentCollection initialization is intended! No unwrap! foreach ($relatedEntities as $relatedEntity) { @@ -2035,7 +2035,7 @@ class UnitOfWork implements PropertyChangedListener if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) { throw new InvalidArgumentException("Entity is not MANAGED."); } - + $entityName = get_class($entity); $class = $this->em->getClassMetadata($entityName); @@ -2055,7 +2055,7 @@ class UnitOfWork implements PropertyChangedListener if (!$this->em->getConnection()->isTransactionActive()) { throw TransactionRequiredException::transactionRequired(); } - + $oid = spl_object_hash($entity); $this->getEntityPersister($class->name)->lock( @@ -2099,7 +2099,7 @@ class UnitOfWork implements PropertyChangedListener $this->collectionUpdates = $this->extraUpdates = $this->orphanRemovals = array(); - + if ($this->commitOrderCalculator !== null) { $this->commitOrderCalculator->clear(); } @@ -2118,13 +2118,13 @@ class UnitOfWork implements PropertyChangedListener $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em, $entityName)); } } - + /** * INTERNAL: * Schedules an orphaned entity for removal. The remove() operation will be * invoked on that entity at the beginning of the next commit of this * UnitOfWork. - * + * * @ignore * @param object $entity */ @@ -2132,7 +2132,7 @@ class UnitOfWork implements PropertyChangedListener { $this->orphanRemovals[spl_object_hash($entity)] = $entity; } - + /** * INTERNAL: * Schedules a complete collection for removal when this UnitOfWork commits. @@ -2142,13 +2142,13 @@ class UnitOfWork implements PropertyChangedListener public function scheduleCollectionDeletion(PersistentCollection $coll) { $coid = spl_object_hash($coll); - + //TODO: if $coll is already scheduled for recreation ... what to do? // Just remove $coll from the scheduled recreations? if (isset($this->collectionUpdates[$coid])) { unset($this->collectionUpdates[$coid]); } - + $this->collectionDeletions[$coid] = $coll; } @@ -2167,7 +2167,7 @@ class UnitOfWork implements PropertyChangedListener * @param array $hints Any hints to account for during reconstitution/lookup of the entity. * @return object The managed entity instance. * @internal Highly performance-sensitive method. - * + * * @todo Rename: getOrCreateEntity */ public function createEntity($className, array $data, &$hints = array()) @@ -2193,8 +2193,8 @@ class UnitOfWork implements PropertyChangedListener } $id = array($class->identifier[0] => $idHash); } - - if (isset($this->identityMap[$class->rootEntityName][$idHash])) { + + if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; $oid = spl_object_hash($entity); if ($entity instanceof Proxy && ! $entity->__isInitialized__) { @@ -2237,17 +2237,17 @@ class UnitOfWork implements PropertyChangedListener // Loading the entity right here, if its in the eager loading map get rid of it there. unset($this->eagerLoadingEntities[$class->rootEntityName][$idHash]); - - if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && + + if (isset($this->eagerLoadingEntities[$class->rootEntityName]) && ! $this->eagerLoadingEntities[$class->rootEntityName]) { unset($this->eagerLoadingEntities[$class->rootEntityName]); } - + // Properly initialize any unfetched associations, if partial objects are not allowed. if ( ! isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) { foreach ($class->associationMappings as $field => $assoc) { // Check if the association is not among the fetch-joined associations already. - if (isset($hints['fetched'][$className][$field])) { + if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) { continue; } @@ -2283,7 +2283,7 @@ class UnitOfWork implements PropertyChangedListener $relatedIdHash = implode(' ', $associatedId); if (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])) { $newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash]; - + // if this is an uninitialized proxy, we are deferring eager loads, // this association is marked as eager fetch, and its an uninitialized proxy (wtf!) // then we cann append this entity for eager loading! @@ -2292,7 +2292,7 @@ class UnitOfWork implements PropertyChangedListener !$targetClass->isIdentifierComposite && $newValue instanceof Proxy && $newValue->__isInitialized__ === false) { - + $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($associatedId); } } else { @@ -2328,7 +2328,7 @@ class UnitOfWork implements PropertyChangedListener } $this->originalEntityData[$oid][$field] = $newValue; $class->reflFields[$field]->setValue($entity, $newValue); - + if ($assoc['inversedBy'] && $assoc['type'] & ClassMetadata::ONE_TO_ONE) { $inverseAssoc = $targetClass->associationMappings[$assoc['inversedBy']]; $targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($newValue, $entity); @@ -2358,12 +2358,12 @@ class UnitOfWork implements PropertyChangedListener } } } - + //TODO: These should be invoked later, after hydration, because associations may not yet be loaded here. if (isset($class->lifecycleCallbacks[Events::postLoad])) { $class->invokeLifecycleCallbacks(Events::postLoad, $entity); } - + if ($this->evm->hasListeners(Events::postLoad)) { $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em)); } @@ -2386,7 +2386,7 @@ class UnitOfWork implements PropertyChangedListener foreach ($eagerLoadingEntities as $entityName => $ids) { $class = $this->em->getClassMetadata($entityName); - + $this->getEntityPersister($entityName)->loadAll( array_combine($class->identifier, array(array_values($ids))) ); @@ -2403,12 +2403,12 @@ class UnitOfWork implements PropertyChangedListener { $assoc = $collection->getMapping(); $persister = $this->getEntityPersister($assoc['targetEntity']); - + switch ($assoc['type']) { case ClassMetadata::ONE_TO_MANY: $persister->loadOneToManyCollection($assoc, $collection->getOwner(), $collection); break; - + case ClassMetadata::MANY_TO_MANY: $persister->loadManyToManyCollection($assoc, $collection->getOwner(), $collection); break; @@ -2435,14 +2435,14 @@ class UnitOfWork implements PropertyChangedListener public function getOriginalEntityData($entity) { $oid = spl_object_hash($entity); - + if (isset($this->originalEntityData[$oid])) { return $this->originalEntityData[$oid]; } - + return array(); } - + /** * @ignore */ @@ -2475,7 +2475,7 @@ class UnitOfWork implements PropertyChangedListener * @return array The identifier values. */ public function getEntityIdentifier($entity) - { + { return $this->entityIdentifiers[spl_object_hash($entity)]; } @@ -2491,11 +2491,11 @@ class UnitOfWork implements PropertyChangedListener public function tryGetById($id, $rootClassName) { $idHash = implode(' ', (array) $id); - + if (isset($this->identityMap[$rootClassName][$idHash])) { return $this->identityMap[$rootClassName][$idHash]; } - + return false; } @@ -2508,7 +2508,7 @@ class UnitOfWork implements PropertyChangedListener public function scheduleForDirtyCheck($entity) { $rootClassName = $this->em->getClassMetadata(get_class($entity))->rootEntityName; - + $this->scheduledForDirtyCheck[$rootClassName][spl_object_hash($entity)] = $entity; } @@ -2531,7 +2531,7 @@ class UnitOfWork implements PropertyChangedListener public function size() { $countArray = array_map(function ($item) { return count($item); }, $this->identityMap); - + return array_sum($countArray); } @@ -2547,28 +2547,28 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->persisters[$entityName])) { return $this->persisters[$entityName]; } - + $class = $this->em->getClassMetadata($entityName); - + switch (true) { case ($class->isInheritanceTypeNone()): $persister = new Persisters\BasicEntityPersister($this->em, $class); break; - + case ($class->isInheritanceTypeSingleTable()): $persister = new Persisters\SingleTablePersister($this->em, $class); break; - + case ($class->isInheritanceTypeJoined()): $persister = new Persisters\JoinedSubclassPersister($this->em, $class); break; - + default: $persister = new Persisters\UnionSubclassPersister($this->em, $class); } - + $this->persisters[$entityName] = $persister; - + return $this->persisters[$entityName]; } @@ -2582,23 +2582,23 @@ class UnitOfWork implements PropertyChangedListener public function getCollectionPersister(array $association) { $type = $association['type']; - + if (isset($this->collectionPersisters[$type])) { return $this->collectionPersisters[$type]; } - - switch ($type) { + + switch ($type) { case ClassMetadata::ONE_TO_MANY: $persister = new Persisters\OneToManyPersister($this->em); break; - + case ClassMetadata::MANY_TO_MANY: $persister = new Persisters\ManyToManyPersister($this->em); break; } - + $this->collectionPersisters[$type] = $persister; - + return $this->collectionPersisters[$type]; } @@ -2613,11 +2613,11 @@ class UnitOfWork implements PropertyChangedListener public function registerManaged($entity, array $id, array $data) { $oid = spl_object_hash($entity); - + $this->entityIdentifiers[$oid] = $id; $this->entityStates[$oid] = self::STATE_MANAGED; $this->originalEntityData[$oid] = $data; - + $this->addToIdentityMap($entity); } @@ -2655,7 +2655,7 @@ class UnitOfWork implements PropertyChangedListener // Update changeset and mark entity for synchronization $this->entityChangeSets[$oid][$propertyName] = array($oldValue, $newValue); - + if ( ! isset($this->scheduledForDirtyCheck[$class->rootEntityName][$oid])) { $this->scheduleForDirtyCheck($entity); } @@ -2663,27 +2663,27 @@ class UnitOfWork implements PropertyChangedListener /** * Gets the currently scheduled entity insertions in this UnitOfWork. - * + * * @return array */ public function getScheduledEntityInsertions() { return $this->entityInsertions; } - + /** * Gets the currently scheduled entity updates in this UnitOfWork. - * + * * @return array */ public function getScheduledEntityUpdates() { return $this->entityUpdates; } - + /** * Gets the currently scheduled entity deletions in this UnitOfWork. - * + * * @return array */ public function getScheduledEntityDeletions() @@ -2710,10 +2710,10 @@ class UnitOfWork implements PropertyChangedListener { return $this->collectionUpdates; } - + /** * Helper method to initialize a lazy loading proxy or persistent collection. - * + * * @param object * @return void */ @@ -2721,20 +2721,20 @@ class UnitOfWork implements PropertyChangedListener { if ($obj instanceof Proxy) { $obj->__load(); - + return; - } - + } + if ($obj instanceof PersistentCollection) { $obj->initialize(); } } - + /** * Helper method to show an object as string. - * + * * @param object $obj - * @return string + * @return string */ private static function objToStr($obj) { @@ -2756,7 +2756,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! is_object($object) || ! $this->isInIdentityMap($object)) { throw new InvalidArgumentException("Managed entity required"); } - + $this->readOnlyObjects[spl_object_hash($object)] = true; } @@ -2772,7 +2772,7 @@ class UnitOfWork implements PropertyChangedListener if ( ! is_object($object) ) { throw new InvalidArgumentException("Managed entity required"); } - + return isset($this->readOnlyObjects[spl_object_hash($object)]); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php index e283b9f1c..0500a0e00 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1335Test.php @@ -49,17 +49,21 @@ class DDC1335Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(sizeof($result['bar@bar.com']->phones), 3); $this->assertEquals(sizeof($result['foobar@foobar.com']->phones), 3); - $this->assertArrayHasKey(1, $result['foo@foo.com']->phones->toArray()); - $this->assertArrayHasKey(2, $result['foo@foo.com']->phones->toArray()); - $this->assertArrayHasKey(3, $result['foo@foo.com']->phones->toArray()); + $foo = $result['foo@foo.com']->phones->toArray(); + $bar = $result['bar@bar.com']->phones->toArray(); + $foobar = $result['foobar@foobar.com']->phones->toArray(); - $this->assertArrayHasKey(4, $result['bar@bar.com']->phones->toArray()); - $this->assertArrayHasKey(5, $result['bar@bar.com']->phones->toArray()); - $this->assertArrayHasKey(6, $result['bar@bar.com']->phones->toArray()); + $this->assertArrayHasKey(1, $foo); + $this->assertArrayHasKey(2, $foo); + $this->assertArrayHasKey(3, $foo); - $this->assertArrayHasKey(7, $result['foobar@foobar.com']->phones->toArray()); - $this->assertArrayHasKey(8, $result['foobar@foobar.com']->phones->toArray()); - $this->assertArrayHasKey(9, $result['foobar@foobar.com']->phones->toArray()); + $this->assertArrayHasKey(4, $bar); + $this->assertArrayHasKey(5, $bar); + $this->assertArrayHasKey(6, $bar); + + $this->assertArrayHasKey(7, $foobar); + $this->assertArrayHasKey(8, $foobar); + $this->assertArrayHasKey(9, $foobar); } public function testTicket() diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php new file mode 100644 index 000000000..aef2d10a9 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php @@ -0,0 +1,127 @@ +useModelSet('cms'); + parent::setUp(); + + try { + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1452EntityA'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1452EntityB'), + )); + } catch (\Exception $ignored) { + } + } + + public function testIssue() + { + $a1 = new DDC1452EntityA(); + $a1->title = "foo"; + + $a2 = new DDC1452EntityA(); + $a2->title = "bar"; + + $b = new DDC1452EntityB(); + $b->entityAFrom = $a1; + $b->entityATo = $a2; + + $this->_em->persist($a1); + $this->_em->persist($a2); + $this->_em->persist($b); + $this->_em->flush(); + $this->_em->clear(); + + $dql = "SELECT a, b, ba FROM " . __NAMESPACE__ . "\DDC1452EntityA AS a LEFT JOIN a.entitiesB AS b LEFT JOIN b.entityATo AS ba"; + $results = $this->_em->createQuery($dql)->setMaxResults(1)->getResult(); + + $this->assertSame($results[0], $results[0]->entitiesB[0]->entityAFrom); + $this->assertFalse( $results[0]->entitiesB[0]->entityATo instanceof \Doctrine\ORM\Proxy\Proxy ); + $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $results[0]->entitiesB[0]->entityATo->getEntitiesB()); + } + + public function testFetchJoinOneToOneFromInverse() + { + $address = new \Doctrine\Tests\Models\CMS\CmsAddress(); + $address->city = "Bonn"; + $address->country = "Germany"; + $address->street = "Somestreet"; + $address->zip = 12345; + + $user = new \Doctrine\Tests\Models\CMS\CmsUser(); + $user->name = "beberlei"; + $user->username = "beberlei"; + $user->status = "active"; + $user->address = $address; + $address->user = $user; + + $this->_em->persist($address); + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $dql = "SELECT a, u FROM Doctrine\Tests\Models\CMS\CmsAddress a INNER JOIN a.user u"; + $data = $this->_em->createQuery($dql)->getResult(); + $this->_em->clear(); + + $this->assertFalse($data[0]->user instanceof \Doctrine\ORM\Proxy\Proxy); + + $dql = "SELECT u, a FROM Doctrine\Tests\Models\CMS\CmsUser u INNER JOIN u.address a"; + $data = $this->_em->createQuery($dql)->getResult(); + + $this->assertFalse($data[0]->address instanceof \Doctrine\ORM\Proxy\Proxy); + } +} + +/** + * @Entity + */ +class DDC1452EntityA +{ + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; + /** @Column */ + public $title; + /** @ManyToMany(targetEntity="DDC1452EntityB", mappedBy="entityAFrom") */ + public $entitiesB; + + public function __construct() + { + $this->entitiesB = new ArrayCollection(); + } + + public function getEntitiesB() + { + return $this->entitiesB; + } +} + +/** + * @Entity + */ +class DDC1452EntityB +{ + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; + + /** + * @ManyToOne(targetEntity="DDC1452EntityA", inversedBy="entitiesB") + */ + public $entityAFrom; + /** + * @ManyToOne(targetEntity="DDC1452EntityA") + */ + public $entityATo; +} \ No newline at end of file