private function doMerge($document, array &$visited, $prevManagedCopy = null, $assoc = null)
{
$oid = spl_object_hash($document);
if (isset($visited[$oid])) {
return $document;
// Prevent infinite recursion
}
$visited[$oid] = $document;
// mark visited
$class = $this->dm->getClassMetadata(get_class($document));
$locale = $this->getCurrentLocale($document, $class);
// 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.
if ($this->getDocumentState($document) == self::STATE_MANAGED) {
$managedCopy = $document;
} else {
$id = $this->determineDocumentId($document, $class);
$persist = false;
if (!$id) {
// document is new
$managedCopy = $class->newInstance();
$persist = true;
} else {
$managedCopy = $this->getDocumentById($id);
if ($managedCopy) {
// We have the document in-memory already, just make sure its not removed.
if ($this->getDocumentState($managedCopy) == self::STATE_REMOVED) {
throw new InvalidArgumentException("Removed document detected during merge at '{$id}'. Cannot merge with a removed document.");
}
if (ClassUtils::getClass($managedCopy) != ClassUtils::getClass($document)) {
throw new InvalidArgumentException('Can not merge documents of different classes.');
}
if ($this->getCurrentLocale($managedCopy, $class) !== $locale) {
$this->doLoadTranslation($document, $class, $locale, true);
}
} elseif ($locale) {
// We need to fetch the managed copy in order to merge.
$managedCopy = $this->dm->findTranslation($class->name, $id, $locale);
} else {
// We need to fetch the managed copy in order to merge.
$managedCopy = $this->dm->find($class->name, $id);
}
if ($managedCopy === null) {
// If the identifier is ASSIGNED, it is NEW, otherwise an error
// since the managed document was not found.
if ($class->idGenerator !== ClassMetadata::GENERATOR_TYPE_ASSIGNED) {
throw new InvalidArgumentException("Document not found in merge operation: {$id}");
}
$managedCopy = $class->newInstance();
$class->setIdentifierValue($managedCopy, $id);
$persist = true;
}
}
$managedOid = spl_object_hash($managedCopy);
// Merge state of $document into existing (managed) document
foreach ($class->reflFields as $fieldName => $prop) {
$other = $prop->getValue($document);
if ($other instanceof PersistentCollection && !$other->isInitialized() || $other instanceof Proxy && !$other->__isInitialized()) {
// do not merge fields marked lazy that have not been fetched.
// keep the lazy persistent collection of the managed copy.
continue;
}
$mapping = $class->mappings[$fieldName];
if (ClassMetadata::MANY_TO_ONE === $mapping['type']) {
$this->doMergeSingleDocumentProperty($managedCopy, $other, $prop, $mapping);
} elseif (ClassMetadata::MANY_TO_MANY === $mapping['type']) {
$managedCol = $prop->getValue($managedCopy);
if (!$managedCol) {
$managedCol = new ReferenceManyCollection($this->dm, $document, $mapping['property'], array(), isset($mapping['targetDocument']) ? $mapping['targetDocument'] : null, $locale, $this->getReferenceManyCollectionTypeFromMetadata($mapping));
$prop->setValue($managedCopy, $managedCol);
$this->originalData[$managedOid][$fieldName] = $managedCol;
}
$this->cascadeMergeCollection($managedCol, $mapping);
} elseif ('child' === $mapping['type']) {
if (null !== $other) {
$this->doMergeSingleDocumentProperty($managedCopy, $other, $prop, $mapping);
}
} elseif ('children' === $mapping['type']) {
$managedCol = $prop->getValue($managedCopy);
if (!$managedCol) {
$managedCol = new ChildrenCollection($this->dm, $managedCopy, $mapping['filter'], $mapping['fetchDepth'], $locale);
$prop->setValue($managedCopy, $managedCol);
$this->originalData[$managedOid][$fieldName] = $managedCol;
}
$this->cascadeMergeCollection($managedCol, $mapping);
} elseif ('referrers' === $mapping['type']) {
$managedCol = $prop->getValue($managedCopy);
if (!$managedCol) {
$referringMeta = $this->dm->getClassMetadata($mapping['referringDocument']);
$referringField = $referringMeta->mappings[$mapping['referencedBy']];
$managedCol = new ReferrersCollection($this->dm, $managedCopy, $referringField['strategy'], $referringField['property'], $locale);
$prop->setValue($managedCopy, $managedCol);
$this->originalData[$managedOid][$fieldName] = $managedCol;
}
$this->cascadeMergeCollection($managedCol, $mapping);
} elseif ('mixedreferrers' === $mapping['type']) {
$managedCol = $prop->getValue($managedCopy);
if (!$managedCol) {
$managedCol = new ImmutableReferrersCollection($this->dm, $managedCopy, $mapping['referenceType'], $locale);
$prop->setValue($managedCopy, $managedCol);
$this->originalData[$managedOid][$fieldName] = $managedCol;
}
$this->cascadeMergeCollection($managedCol, $mapping);
} elseif ('parent' === $mapping['type']) {
$this->doMergeSingleDocumentProperty($managedCopy, $other, $prop, $mapping);
} elseif (in_array($mapping['type'], array('locale', 'versionname', 'versioncreated', 'node', 'nodename'))) {
if (null !== $other) {
$prop->setValue($managedCopy, $other);
}
} elseif (!$class->isIdentifier($fieldName)) {
$prop->setValue($managedCopy, $other);
}
}
if ($persist) {
$this->persistNew($class, $managedCopy);
}
// Mark the managed copy visited as well
$visited[$managedOid] = true;
}
if ($prevManagedCopy !== null) {
$prevClass = $this->dm->getClassMetadata(get_class($prevManagedCopy));
if ($assoc['type'] == ClassMetadata::MANY_TO_ONE) {
$prevClass->reflFields[$assoc['fieldName']]->setValue($prevManagedCopy, $managedCopy);
} else {
$prevClass->reflFields[$assoc['fieldName']]->getValue($prevManagedCopy)->add($managedCopy);
}
}
$this->cascadeMerge($class, $document, $managedCopy, $visited);
return $managedCopy;
}