/** * Executes a merge operation on an entity. * * @param object $entity * @param array $visited * @param object|null $prevManagedCopy * @param array|null $assoc * * @return object The managed copy of the entity. * * @throws OptimisticLockException If the entity uses optimistic locking through a version * attribute and the version check against the managed copy fails. * @throws ORMInvalidArgumentException If the entity instance is NEW. * @throws EntityNotFoundException */ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return $visited[$oid]; // Prevent infinite recursion } $visited[$oid] = $entity; // mark visited $class = $this->em->getClassMetadata(get_class($entity)); // First we assume DETACHED, although it can still be NEW but we can avoid // an extra db-roundtrip this way. If it is not MANAGED but has an identity, // 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()) { $this->em->getProxyFactory()->resetUninitializedProxy($entity); $entity->__load(); } // Try to look the entity up in the identity map. $id = $class->getIdentifierValues($entity); // If there is no ID, it is actually NEW. if (!$id) { $managedCopy = $this->newInstance($class); $this->persistNew($class, $managedCopy); } else { $flatId = $class->containsForeignIdentifier ? $this->flattenIdentifier($class, $id) : $id; $managedCopy = $this->tryGetById($flatId, $class->rootEntityName); if ($managedCopy) { // We have the entity in-memory already, just make sure its not removed. if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) { throw ORMInvalidArgumentException::entityIsRemoved($managedCopy, "merge"); } } else { // We need to fetch the managed copy in order to merge. $managedCopy = $this->em->find($class->name, $flatId); } if ($managedCopy === null) { // If the identifier is ASSIGNED, it is NEW, otherwise an error // since the managed entity was not found. if (!$class->isIdentifierNatural()) { throw new EntityNotFoundException($class->getName()); } $managedCopy = $this->newInstance($class); $class->setIdentifierValues($managedCopy, $id); $this->persistNew($class, $managedCopy); } else { if ($managedCopy instanceof Proxy && !$managedCopy->__isInitialized__) { $managedCopy->__load(); } } } if ($class->isVersioned) { $reflField = $class->reflFields[$class->versionField]; $managedCopyVersion = $reflField->getValue($managedCopy); $entityVersion = $reflField->getValue($entity); // Throw exception if versions don't match. if ($managedCopyVersion != $entityVersion) { throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion); } } // Merge state of $entity into existing (managed) entity foreach ($class->reflClass->getProperties() as $prop) { $name = $prop->name; $prop->setAccessible(true); if (!isset($class->associationMappings[$name])) { if (!$class->isIdentifier($name)) { $prop->setValue($managedCopy, $prop->getValue($entity)); } } else { $assoc2 = $class->associationMappings[$name]; if ($assoc2['type'] & ClassMetadata::TO_ONE) { $other = $prop->getValue($entity); if ($other === null) { $prop->setValue($managedCopy, null); } else { if ($other instanceof Proxy && !$other->__isInitialized__) { // do not merge fields marked lazy that have not been fetched. continue; } else { if (!$assoc2['isCascadeMerge']) { if ($this->getEntityState($other) === self::STATE_DETACHED) { $targetClass = $this->em->getClassMetadata($assoc2['targetEntity']); $relatedId = $targetClass->getIdentifierValues($other); if ($targetClass->subClasses) { $other = $this->em->find($targetClass->name, $relatedId); } else { $other = $this->em->getProxyFactory()->getProxy($assoc2['targetEntity'], $relatedId); $this->registerManaged($other, $relatedId, array()); } } $prop->setValue($managedCopy, $other); } } } } else { $mergeCol = $prop->getValue($entity); if ($mergeCol instanceof PersistentCollection && !$mergeCol->isInitialized()) { // do not merge fields marked lazy that have not been fetched. // keep the lazy persistent collection of the managed copy. continue; } $managedCol = $prop->getValue($managedCopy); if (!$managedCol) { $managedCol = new PersistentCollection($this->em, $this->em->getClassMetadata($assoc2['targetEntity']), new ArrayCollection()); $managedCol->setOwner($managedCopy, $assoc2); $prop->setValue($managedCopy, $managedCol); $this->originalEntityData[$oid][$name] = $managedCol; } 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); } } } } } 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); } } 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); } } } // Mark the managed copy visited as well $visited[spl_object_hash($managedCopy)] = true; $this->cascadeMerge($entity, $managedCopy, $visited); return $managedCopy; }
/** * Executes a merge operation on an entity. * * @param object $entity * @param array $visited * @param object|null $prevManagedCopy * @param array|null $assoc * * @return object The managed copy of the entity. * * @throws OptimisticLockException If the entity uses optimistic locking through a version * attribute and the version check against the managed copy fails. * @throws ORMInvalidArgumentException If the entity instance is NEW. * @throws EntityNotFoundException */ private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { $managedCopy = $visited[$oid]; if ($prevManagedCopy !== null) { $this->updateAssociationWithMergedEntity($entity, $assoc, $prevManagedCopy, $managedCopy); } return $managedCopy; } $class = $this->em->getClassMetadata(get_class($entity)); // First we assume DETACHED, although it can still be NEW but we can avoid // an extra db-roundtrip this way. If it is not MANAGED but has an identity, // 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) { // Try to look the entity up in the identity map. $id = $class->getIdentifierValues($entity); // If there is no ID, it is actually NEW. if (!$id) { $managedCopy = $this->newInstance($class); $this->persistNew($class, $managedCopy); } else { $flatId = $class->containsForeignIdentifier ? $this->identifierFlattener->flattenIdentifier($class, $id) : $id; $managedCopy = $this->tryGetById($flatId, $class->rootEntityName); if ($managedCopy) { // We have the entity in-memory already, just make sure its not removed. if ($this->getEntityState($managedCopy) == self::STATE_REMOVED) { throw ORMInvalidArgumentException::entityIsRemoved($managedCopy, "merge"); } } else { // We need to fetch the managed copy in order to merge. $managedCopy = $this->em->find($class->name, $flatId); } if ($managedCopy === null) { // If the identifier is ASSIGNED, it is NEW, otherwise an error // since the managed entity was not found. if (!$class->isIdentifierNatural()) { throw EntityNotFoundException::fromClassNameAndIdentifier($class->getName(), $this->identifierFlattener->flattenIdentifier($class, $id)); } $managedCopy = $this->newInstance($class); $class->setIdentifierValues($managedCopy, $id); $this->persistNew($class, $managedCopy); } } if ($class->isVersioned) { $reflField = $class->reflFields[$class->versionField]; $managedCopyVersion = $reflField->getValue($managedCopy); $entityVersion = $reflField->getValue($entity); // Throw exception if versions don't match. if ($managedCopyVersion != $entityVersion) { throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion); } } $visited[$oid] = $managedCopy; // mark visited if (!($entity instanceof Proxy && !$entity->__isInitialized())) { if ($managedCopy instanceof Proxy && !$managedCopy->__isInitialized()) { $managedCopy->__load(); } $this->mergeEntityStateIntoManagedCopy($entity, $managedCopy); } if ($class->isChangeTrackingDeferredExplicit()) { $this->scheduleForDirtyCheck($entity); } } if ($prevManagedCopy !== null) { $this->updateAssociationWithMergedEntity($entity, $assoc, $prevManagedCopy, $managedCopy); } // Mark the managed copy visited as well $visited[spl_object_hash($managedCopy)] = $managedCopy; $this->cascadeMerge($entity, $managedCopy, $visited); return $managedCopy; }