INTERNAL:
Unschedules a collection from being updated when this UnitOfWork commits.
public unscheduleCollectionUpdate ( Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface $coll ) | ||
$coll | Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface |
private function getAtomicCollectionUpdateQuery($document) { $update = array(); $collections = $this->uow->getScheduledCollections($document); $collPersister = $this->uow->getCollectionPersister(); foreach ($collections as $coll) { /* @var $coll PersistentCollection */ $mapping = $coll->getMapping(); if ($mapping['strategy'] !== "atomicSet" && $mapping['strategy'] !== "atomicSetArray") { continue; } if ($this->uow->isCollectionScheduledForUpdate($coll)) { $update = array_merge_recursive($update, $collPersister->prepareSetQuery($coll)); $this->uow->unscheduleCollectionUpdate($coll); /* TODO: * Collection can be set for both deletion and update if * PersistentCollection instance was changed. Since we're dealing * with collection update in one query we won't need the $unset. * Line can be removed once the issue is fixed. */ $this->uow->unscheduleCollectionDeletion($coll); } elseif ($this->uow->isCollectionScheduledForDeletion($coll)) { $update = array_merge_recursive($update, $collPersister->prepareDeleteQuery($coll)); $this->uow->unscheduleCollectionDeletion($coll); } } return $update; }
/** * 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; }
/** * Returns the embedded document to be stored in MongoDB. * * The return value will usually be an associative array with string keys * corresponding to field names on the embedded document. An object may be * returned if the document is empty, to ensure that a BSON object will be * stored in lieu of an array. * * If $includeNestedCollections is true, nested collections will be included * in this prepared value and the option will cascade to all embedded * associations. If any nested PersistentCollections (embed or reference) * within this value were previously scheduled for deletion or update, they * will also be unscheduled. * * @param array $embeddedMapping * @param object $embeddedDocument * @param boolean $includeNestedCollections * @return array|object * @throws \UnexpectedValueException if an unsupported associating mapping is found */ public function prepareEmbeddedDocumentValue(array $embeddedMapping, $embeddedDocument, $includeNestedCollections = false) { $embeddedDocumentValue = array(); $class = $this->dm->getClassMetadata(get_class($embeddedDocument)); foreach ($class->fieldMappings as $mapping) { // Skip notSaved fields if (!empty($mapping['notSaved'])) { continue; } // Inline ClassMetadataInfo::getFieldValue() $rawValue = $class->reflFields[$mapping['fieldName']]->getValue($embeddedDocument); $value = null; if ($rawValue !== null) { switch (isset($mapping['association']) ? $mapping['association'] : null) { // @Field, @String, @Date, etc. case null: $value = Type::getType($mapping['type'])->convertToDatabaseValue($rawValue); break; case ClassMetadata::EMBED_ONE: case ClassMetadata::REFERENCE_ONE: // Nested collections should only be included for embedded relationships $value = $this->prepareAssociatedDocumentValue($mapping, $rawValue, $includeNestedCollections && isset($mapping['embedded'])); break; case ClassMetadata::EMBED_MANY: case ClassMetadata::REFERENCE_MANY: // Skip PersistentCollections already scheduled for deletion if (!$includeNestedCollections && $rawValue instanceof PersistentCollection && $this->uow->isCollectionScheduledForDeletion($rawValue)) { break; } // We're handling atomicSet or atomicSetArray collection if ($includeNestedCollections && $rawValue instanceof PersistentCollection) { $this->uow->unscheduleCollectionDeletion($rawValue); $this->uow->unscheduleCollectionUpdate($rawValue); } $pb = $this; $value = $rawValue->map(function ($v) use($pb, $mapping, $includeNestedCollections) { // Nested collections should only be included for embedded relationships return $pb->prepareAssociatedDocumentValue($mapping, $v, $includeNestedCollections && isset($mapping['embedded'])); })->toArray(); // Numerical reindexing may be necessary to ensure BSON array storage if (in_array($mapping['strategy'], array('atomicSetArray', 'setArray', 'pushAll', 'addToSet'))) { $value = array_values($value); } break; default: throw new \UnexpectedValueException('Unsupported mapping association: ' . $mapping['association']); } } // Omit non-nullable fields that would have a null value if ($value === null && $mapping['nullable'] === false) { continue; } $embeddedDocumentValue[$mapping['name']] = $value; } /* Add a discriminator value if the embedded document is not mapped * explicitly to a targetDocument class. */ if (!isset($embeddedMapping['targetDocument'])) { $discriminatorField = $embeddedMapping['discriminatorField']; $discriminatorValue = isset($embeddedMapping['discriminatorMap']) ? array_search($class->name, $embeddedMapping['discriminatorMap']) : $class->name; /* If the discriminator value was not found in the map, use the full * class name. In the future, it may be preferable to throw an * exception here (perhaps based on some strictness option). * * @see DocumentManager::createDBRef() */ if ($discriminatorValue === false) { $discriminatorValue = $class->name; } $embeddedDocumentValue[$discriminatorField] = $discriminatorValue; } /* If the class has a discriminator (field and value), use it. A child * class that is not defined in the discriminator map may only have a * discriminator field and no value, so default to the full class name. */ if (isset($class->discriminatorField)) { $embeddedDocumentValue[$class->discriminatorField] = isset($class->discriminatorValue) ? $class->discriminatorValue : $class->name; } // Ensure empty embedded documents are stored as BSON objects if (empty($embeddedDocumentValue)) { return (object) $embeddedDocumentValue; } /* @todo Consider always casting the return value to an object, or * building $embeddedDocumentValue as an object instead of an array, to * handle the edge case where all database field names are sequential, * numeric keys. */ return $embeddedDocumentValue; }
private function getAtomicCollectionUpdateQuery($document) { $update = array(); $atomicCollUpdates = array(); $atomicCollDeletes = array(); $collPersister = $this->uow->getCollectionPersister(); /* Collect all atomic collections (top-level and nested) to be included * in the update. */ foreach ($this->uow->getScheduledCollections($document) as $coll) { /* If this is a top-level, atomic collection, its scheduled update * or deletion must be included in the document's update query. */ if ($coll->getOwner() === $document) { $mapping = $coll->getMapping(); if ($mapping['strategy'] !== "atomicSet" && $mapping['strategy'] !== "atomicSetArray") { continue; } if ($this->uow->isCollectionScheduledForUpdate($coll)) { $atomicCollUpdates[spl_object_hash($coll)] = $coll; } elseif ($this->uow->isCollectionScheduledForDeletion($coll)) { $atomicCollDeletes[spl_object_hash($coll)] = $coll; } continue; } /* Otherwise, the collection is nested. Check if its top-most parent * is an atomic collection and include it for updating if so. This * is necessary because the atomic parent may not have directly * changed. */ $parent = $coll->getOwner(); while (null !== ($parentAssoc = $this->uow->getParentAssociation($parent))) { list($mapping, $parent, ) = $parentAssoc; } if (!isset($mapping['association']) || $mapping['association'] !== ClassMetadata::EMBED_MANY || $mapping['strategy'] !== 'atomicSet' && $mapping['strategy'] !== 'atomicSetArray') { continue; } $classMetadata = $this->dm->getClassMetadata(get_class($document)); $parentColl = $classMetadata->getFieldValue($document, $mapping['fieldName']); /* It's possible that the atomic parent was independently scheduled * for deletion. In that case, updating nested data is unnecessary. */ if (!$this->uow->isCollectionScheduledForDeletion($parentColl)) { $atomicCollUpdates[spl_object_hash($parentColl)] = $parentColl; } } foreach ($atomicCollUpdates as $coll) { $update = array_merge_recursive($update, $collPersister->prepareSetQuery($coll)); /* Note: If the collection is only be handled because it's an atomic * parent of a scheduled child, the following calls are NOPs. */ $this->uow->unscheduleCollectionUpdate($coll); } foreach ($atomicCollDeletes as $coll) { $update = array_merge_recursive($update, $collPersister->prepareDeleteQuery($coll)); /* Note: We don't need to call unscheduleCollectionUpdate(), because * the collection should never have been added to $atomicCollDeletes * if it was independently scheduled for update. */ $this->uow->unscheduleCollectionDeletion($coll); } return $update; }