protected function getQueryArray(ClassMetadata $metadata, $document, $path) { $class = $metadata->name; $field = $this->getFieldNameFromPropertyPath($path); if (!isset($metadata->fieldMappings[$field])) { throw new \LogicException('Mapping for \'' . $path . '\' doesn\'t exist for ' . $class); } $mapping = $metadata->fieldMappings[$field]; if (isset($mapping['reference']) && $mapping['reference']) { throw new \LogicException('Cannot determine uniqueness of referenced document values'); } switch ($mapping['type']) { case 'one': // TODO: implement support for embed one documents // TODO: implement support for embed one documents case 'many': // TODO: implement support for embed many documents throw new \RuntimeException('Not Implemented.'); case 'hash': $value = $metadata->getFieldValue($document, $mapping['fieldName']); return array($path => $this->getFieldValueRecursively($path, $value)); case 'collection': return array($mapping['fieldName'] => array('$in' => $metadata->getFieldValue($document, $mapping['fieldName']))); default: return array($mapping['fieldName'] => $metadata->getFieldValue($document, $mapping['fieldName'])); } }
/** * Updates the already persisted document if it has any new changesets. * * @param object $document * @param array $options Array of options to be used with update() */ public function update($document, array $options = array()) { $id = $this->uow->getDocumentIdentifier($document); $update = $this->dp->prepareUpdateData($document); if ( ! empty($update)) { $id = $this->class->getDatabaseIdentifierValue($id); $query = array('_id' => $id); // Include versioning updates if ($this->class->isVersioned) { $versionMapping = $this->class->fieldMappings[$this->class->versionField]; $currentVersion = $this->class->getFieldValue($document, $this->class->versionField); if ($versionMapping['type'] === 'int') { $nextVersion = $currentVersion + 1; $update[$this->cmd . 'inc'][$versionMapping['name']] = 1; $query[$versionMapping['name']] = $currentVersion; $this->class->setFieldValue($document, $this->class->versionField, $nextVersion); } elseif ($versionMapping['type'] === 'date') { $nextVersion = new \DateTime(); $update[$this->cmd . 'set'][$versionMapping['name']] = new \MongoDate($nextVersion->getTimestamp()); $query[$versionMapping['name']] = new \MongoDate($currentVersion->getTimestamp()); $this->class->setFieldValue($document, $this->class->versionField, $nextVersion); } $options['safe'] = true; } if ($this->class->isLockable) { $isLocked = $this->class->getFieldValue($document, $this->class->lockField); $lockMapping = $this->class->fieldMappings[$this->class->lockField]; if ($isLocked) { $update[$this->cmd . 'unset'] = array($lockMapping['name'] => true); } else { $query[$lockMapping['name']] = array($this->cmd . 'exists' => false); } } if ($this->dm->getEventManager()->hasListeners(Events::onUpdatePrepared)) { $this->dm->getEventManager()->dispatchEvent( Events::onUpdatePrepared, new OnUpdatePreparedArgs($this->dm, $document, $update) ); } $result = $this->collection->update($query, $update, $options); if (($this->class->isVersioned || $this->class->isLockable) && ! $result['n']) { throw LockException::lockFailed($document); } } }
/** * @param $object * @param $fieldName * @return ArrayCollection|mixed * @throws \NForms\Exceptions\MetadataException */ protected function getCollection($object, $fieldName) { if ($this->isSingleValuedAssociation($fieldName)) { throw new MetadataException("Can't get collection from association toOne."); } $collection = $this->classMetadata->getFieldValue($object, $fieldName); if ($collection === NULL) { $collection = new ArrayCollection(); $this->classMetadata->setFieldValue($object, $fieldName, $collection); } if (!$collection instanceof Collection) { throw new MetadataException('Expected Doctrine\\Common\\Collections\\Collection, given ' . (is_object($collection) ? get_class($collection) : gettype($collection))); } return $collection; }
/** * If you are priming references inside an embedded document you'll need to parse the dot syntax. * This method will traverse through embedded documents to find the reference to prime. * However this method will not traverse through multiple layers of references. * I.e. you can prime this: myDocument.embeddedDocument.embeddedDocuments.embeddedDocuments.referencedDocument(s) * ... but you cannot prime this: myDocument.embeddedDocument.referencedDocuments.referencedDocument(s) * This addresses Issue #624. * * @param string $fieldName * @param ClassMetadata $class * @param array|\Traversable $documents * @param array $mapping * @return array */ private function parseDotSyntaxForPrimer($fieldName, $class, $documents, $mapping = null) { // Recursion passthrough: if ($mapping != null) { return array('fieldName' => $fieldName, 'class' => $class, 'documents' => $documents, 'mapping' => $mapping); } // Gather mapping data: $e = explode('.', $fieldName); if (!isset($class->fieldMappings[$e[0]])) { throw new \InvalidArgumentException(sprintf('Field %s cannot be further parsed for priming because it is unmapped.', $fieldName)); } $mapping = $class->fieldMappings[$e[0]]; $e[0] = $mapping['name']; // Case of embedded document(s) to recurse through: if (!isset($mapping['reference'])) { if (empty($mapping['embedded'])) { throw new \InvalidArgumentException(sprintf('Field "%s" of fieldName "%s" is not an embedded document, therefore no children can be primed. Aborting. This feature does not support traversing nested referenced documents at this time.', $e[0], $fieldName)); } if (!isset($mapping['targetDocument'])) { throw new \InvalidArgumentException(sprintf('No target document class has been specified for this embedded document. However, targetDocument mapping must be specified in order for prime to work on fieldName "%s" for mapping of field "%s".', $fieldName, $mapping['fieldName'])); } $childDocuments = array(); foreach ($documents as $document) { $fieldValue = $class->getFieldValue($document, $e[0]); if ($fieldValue instanceof PersistentCollection) { foreach ($fieldValue as $elemDocument) { array_push($childDocuments, $elemDocument); } } else { array_push($childDocuments, $fieldValue); } } array_shift($e); $childClass = $this->dm->getClassMetadata($mapping['targetDocument']); if (!$childClass->hasField($e[0])) { throw new \InvalidArgumentException(sprintf('Field to prime must exist in embedded target document. Reference fieldName "%s" for mapping of target document class "%s".', $fieldName, $mapping['targetDocument'])); } $childFieldName = implode('.', $e); return $this->parseDotSyntaxForPrimer($childFieldName, $childClass, $childDocuments); } // Case of reference(s) to prime: if ($mapping['reference']) { if (count($e) > 1) { throw new \InvalidArgumentException(sprintf('Cannot prime more than one layer deep but field "%s" is a reference and has children in fieldName "%s".', $e[0], $fieldName)); } return array('fieldName' => $fieldName, 'class' => $class, 'documents' => $documents, 'mapping' => $mapping); } }
/** * Prepares array of values to be stored in mongo to represent embedded object. * * @param ClassMetadata $class * @param Document $doc * @return array */ private function _prepareDocEmbeded(ClassMetadata $class, $doc) { if (!is_object($doc)) { return $doc; } $changeset = array(); foreach ($class->fieldMappings as $mapping) { $rawValue = $class->getFieldValue($doc, $mapping['fieldName']); if (!isset($rawValue)) { continue; } if (isset($mapping['embedded']) || isset($mapping['reference'])) { $classMetadata = $this->_dm->getClassMetadata($mapping['targetDocument']); if (isset($mapping['embedded'])) { if ($mapping['type'] == 'many') { $value = array(); foreach ($rawValue as $doc) { $value[] = $this->_prepareDocEmbeded($classMetadata, $doc); } } elseif ($mapping['type'] == 'one') { $value = $this->_prepareDocEmbeded($classMetadata, $rawValue); } } elseif (isset($mapping['reference'])) { if ($mapping['type'] == 'many') { $value = array(); foreach ($rawValue as $doc) { $value[] = $this->_prepareDocReference($classMetadata, $doc); } } else { $value = $this->_prepareDocReference($classMetadata, $rawValue); } } } else { $value = Type::getType($mapping['type'])->convertToDatabaseValue($rawValue); } $changeset[$mapping['fieldName']] = $value; } return $changeset; }
/** * Cascades the preLoad event to embedded documents. * * @param ClassMetadata $class * @param object $document * @param array $data */ private function cascadePreLoad(ClassMetadata $class, $document, $data) { foreach ($class->fieldMappings as $mapping) { if (isset($mapping['embedded'])) { $value = $class->getFieldValue($document, $mapping['fieldName']); if ($value === null) { continue; } if ($mapping['type'] === 'one') { $value = array($value); } foreach ($value as $entry) { $entryClass = $this->dm->getClassMetadata(get_class($entry)); if (isset($entryClass->lifecycleCallbacks[Events::preLoad])) { $args = array(&$data); $entryClass->invokeLifecycleCallbacks(Events::preLoad, $entry, $args); } if ($this->evm->hasListeners(Events::preLoad)) { $this->evm->dispatchEvent(Events::preLoad, new PreLoadEventArgs($entry, $this->dm, $data[$mapping['name']])); } $this->cascadePreLoad($entryClass, $entry, $data[$mapping['name']]); } } } }
/** * Prime references within a mapped field of one or more documents. * * If a $primer callable is provided, it should have the same signature as * the default primer defined in the constructor. If $primer is not * callable, the default primer will be used. * * @param ClassMetadata $class Class metadata for the document * @param array|\Traversable $documents Documents containing references to prime * @param string $fieldName Field name containing references to prime * @param array $hints UnitOfWork hints for priming queries * @param callable $primer Optional primer callable * @throws \InvalidArgumentException If the mapped field is not the owning * side of a reference relationship. * @throws \InvalidArgumentException If $primer is not callable * @throws \LogicException If the mapped field is a simple reference and is * missing a target document class. */ public function primeReferences(ClassMetadata $class, $documents, $fieldName, array $hints = array(), $primer = null) { $mapping = $class->getFieldMapping($fieldName); /* Inverse-side references would need to be populated before we can * collect references to be primed. This is not supported. */ if (!isset($mapping['reference']) || !$mapping['isOwningSide']) { throw new \InvalidArgumentException(sprintf('Field "%s" is not the owning side of a reference relationship in class "%s"', $fieldName, $class->name)); } /* Simple reference require a target document class so we can construct * the priming query. */ if (!empty($mapping['simple']) && empty($mapping['targetDocument'])) { throw new \LogicException(sprintf('Field "%s" is a simple reference without a target document class in class "%s"', $fieldName, $class->name)); } if ($primer !== null && !is_callable($primer)) { throw new \InvalidArgumentException('$primer is not callable'); } $primer = $primer ?: $this->defaultPrimer; $groupedIds = array(); foreach ($documents as $document) { $fieldValue = $class->getFieldValue($document, $fieldName); /* The field will need to be either a Proxy (reference-one) or * PersistentCollection (reference-many) in order to prime anything. */ if (!is_object($fieldValue)) { continue; } if ($mapping['type'] === 'one' && $fieldValue instanceof Proxy && !$fieldValue->__isInitialized()) { $refClass = $this->dm->getClassMetadata(get_class($fieldValue)); $id = $this->uow->getDocumentIdentifier($fieldValue); $groupedIds[$refClass->name][serialize($id)] = $id; } elseif ($mapping['type'] == 'many' && $fieldValue instanceof PersistentCollection) { $this->addManyReferences($fieldValue, $groupedIds); } } foreach ($groupedIds as $className => $ids) { $refClass = $this->dm->getClassMetadata($className); call_user_func($primer, $this->dm, $refClass, array_values($ids), $hints); } }