private function computeAssociationChanges($parentDocument, array $assoc, $value)
{
$isNewParentDocument = isset($this->documentInsertions[spl_object_hash($parentDocument)]);
$class = $this->dm->getClassMetadata(get_class($parentDocument));
$topOrExistingDocument = !$isNewParentDocument || !$class->isEmbeddedDocument;
if ($value instanceof Proxy && !$value->__isInitialized__) {
return;
}
if ($value instanceof PersistentCollectionInterface && $value->isDirty() && ($assoc['isOwningSide'] || isset($assoc['embedded']))) {
if ($topOrExistingDocument || CollectionHelper::usesSet($assoc['strategy'])) {
$this->scheduleCollectionUpdate($value);
}
$topmostOwner = $this->getOwningDocument($value->getOwner());
$this->visitedCollections[spl_object_hash($topmostOwner)][] = $value;
if (!empty($assoc['orphanRemoval']) || isset($assoc['embedded'])) {
$value->initialize();
foreach ($value->getDeletedDocuments() as $orphan) {
$this->scheduleOrphanRemoval($orphan);
}
}
}
// Look through the documents, and in any of their associations,
// for transient (new) documents, recursively. ("Persistence by reachability")
// Unwrap. Uninitialized collections will simply be empty.
$unwrappedValue = $assoc['type'] === ClassMetadata::ONE ? array($value) : $value->unwrap();
$count = 0;
foreach ($unwrappedValue as $key => $entry) {
if (!is_object($entry)) {
throw new \InvalidArgumentException(sprintf('Expected object, found "%s" in %s::%s', $entry, get_class($parentDocument), $assoc['name']));
}
$targetClass = $this->dm->getClassMetadata(get_class($entry));
$state = $this->getDocumentState($entry, self::STATE_NEW);
// Handle "set" strategy for multi-level hierarchy
$pathKey = !isset($assoc['strategy']) || CollectionHelper::isList($assoc['strategy']) ? $count : $key;
$path = $assoc['type'] === 'many' ? $assoc['name'] . '.' . $pathKey : $assoc['name'];
$count++;
switch ($state) {
case self::STATE_NEW:
if (!$assoc['isCascadePersist']) {
throw new \InvalidArgumentException('A new document was found through a relationship that was not' . ' configured to cascade persist operations: ' . $this->objToStr($entry) . '.' . ' Explicitly persist the new document or configure cascading persist operations' . ' on the relationship.');
}
$this->persistNew($targetClass, $entry);
$this->setParentAssociation($entry, $assoc, $parentDocument, $path);
$this->computeChangeSet($targetClass, $entry);
break;
case self::STATE_MANAGED:
if ($targetClass->isEmbeddedDocument) {
list(, $knownParent, ) = $this->getParentAssociation($entry);
if ($knownParent && $knownParent !== $parentDocument) {
$entry = clone $entry;
if ($assoc['type'] === ClassMetadata::ONE) {
$class->setFieldValue($parentDocument, $assoc['fieldName'], $entry);
$this->setOriginalDocumentProperty(spl_object_hash($parentDocument), $assoc['fieldName'], $entry);
} else {
// must use unwrapped value to not trigger orphan removal
$unwrappedValue[$key] = $entry;
}
$this->persistNew($targetClass, $entry);
}
$this->setParentAssociation($entry, $assoc, $parentDocument, $path);
$this->computeChangeSet($targetClass, $entry);
}
break;
case self::STATE_REMOVED:
// Consume the $value as array (it's either an array or an ArrayAccess)
// and remove the element from Collection.
if ($assoc['type'] === ClassMetadata::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).
throw new \InvalidArgumentException('A detached document was found through a ' . 'relationship during cascading a persist operation.');
default:
// MANAGED associated documents are already taken into account
// during changeset calculation anyway, since they are in the identity map.
}
}
}