/** {@inheritdoc} */ public function takeSnapshot() { if (CollectionHelper::isList($this->mapping['strategy'])) { $array = $this->coll->toArray(); $this->coll->clear(); foreach ($array as $document) { $this->coll->add($document); } } $this->snapshot = $this->coll->toArray(); $this->isDirty = false; }
/** * Cascades the save operation to associated documents. * * @param object $document * @param array $visited */ private function cascadePersist($document, array &$visited) { $class = $this->dm->getClassMetadata(get_class($document)); $associationMappings = array_filter($class->associationMappings, function ($assoc) { return $assoc['isCascadePersist']; }); foreach ($associationMappings as $fieldName => $mapping) { $relatedDocuments = $class->reflFields[$fieldName]->getValue($document); if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) { if ($relatedDocuments instanceof PersistentCollectionInterface) { if ($relatedDocuments->getOwner() !== $document) { $relatedDocuments = $this->fixPersistentCollectionOwnership($relatedDocuments, $document, $class, $mapping['fieldName']); } // Unwrap so that foreach() does not initialize $relatedDocuments = $relatedDocuments->unwrap(); } $count = 0; foreach ($relatedDocuments as $relatedKey => $relatedDocument) { if (!empty($mapping['embedded'])) { list(, $knownParent, ) = $this->getParentAssociation($relatedDocument); if ($knownParent && $knownParent !== $document) { $relatedDocument = clone $relatedDocument; $relatedDocuments[$relatedKey] = $relatedDocument; } $pathKey = CollectionHelper::isList($mapping['strategy']) ? $count++ : $relatedKey; $this->setParentAssociation($relatedDocument, $mapping, $document, $mapping['fieldName'] . '.' . $pathKey); } $this->doPersist($relatedDocument, $visited); } } elseif ($relatedDocuments !== null) { if (!empty($mapping['embedded'])) { list(, $knownParent, ) = $this->getParentAssociation($relatedDocuments); if ($knownParent && $knownParent !== $document) { $relatedDocuments = clone $relatedDocuments; $class->setFieldValue($document, $mapping['fieldName'], $relatedDocuments); } $this->setParentAssociation($relatedDocuments, $mapping, $document, $mapping['fieldName']); } $this->doPersist($relatedDocuments, $visited); } } }
/** * Computes the changes of an association. * * @param object $parentDocument * @param array $assoc * @param mixed $value The value of the association. * @throws \InvalidArgumentException */ 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 PersistentCollection && $value->isDirty() && $assoc['isOwningSide']) { if ($topOrExistingDocument || CollectionHelper::usesSet($assoc['strategy'])) { $this->scheduleCollectionUpdate($value); } $topmostOwner = $this->getOwningDocument($value->getOwner()); $this->visitedCollections[spl_object_hash($topmostOwner)][] = $value; } // 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: " . self::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) { $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."); break; default: // MANAGED associated documents are already taken into account // during changeset calculation anyway, since they are in the identity map. } } }
/** * Returns the collection representation to be stored and unschedules it afterwards. * * @param PersistentCollection $coll * @param bool $includeNestedCollections * @return array */ public function prepareAssociatedCollectionValue(PersistentCollection $coll, $includeNestedCollections = false) { $mapping = $coll->getMapping(); $pb = $this; $callback = isset($mapping['embedded']) ? function ($v) use($pb, $mapping, $includeNestedCollections) { return $pb->prepareEmbeddedDocumentValue($mapping, $v, $includeNestedCollections); } : function ($v) use($pb, $mapping) { return $pb->prepareReferencedDocumentValue($mapping, $v); }; $setData = $coll->map($callback)->toArray(); if (CollectionHelper::isList($mapping['strategy'])) { $setData = array_values($setData); } $this->uow->unscheduleCollectionDeletion($coll); $this->uow->unscheduleCollectionUpdate($coll); return $setData; }