/** * Create the document database for a mapped class. * * @param string $documentName * @throws \InvalidArgumentException */ public function createDocumentDatabase($documentName) { $class = $this->dm->getClassMetadata($documentName); if ($class->isMappedSuperclass || $class->isEmbeddedDocument) { throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes or embedded documents.'); } $this->dm->getDocumentDatabase($documentName)->execute("function() { return true; }"); }
/** * INTERNAL: * Sets the collection's owning entity together with the AssociationMapping that * describes the association between the owner and the elements of the collection. * * @param object $document * @param array $mapping */ public function setOwner($document, array $mapping) { $this->owner = $document; $this->mapping = $mapping; if (!empty($this->mapping['targetDocument'])) { $this->typeClass = $this->dm->getClassMetadata($this->mapping['targetDocument']); } }
/** * Create a new repository instance for a document class. * * @param DocumentManager $documentManager The DocumentManager instance. * @param string $documentName The name of the document. * * @return \Doctrine\Common\Persistence\ObjectRepository */ protected function createRepository(DocumentManager $documentManager, $documentName) { $metadata = $documentManager->getClassMetadata($documentName); $repositoryClassName = $metadata->customRepositoryClassName; if ($repositoryClassName === null) { $configuration = $documentManager->getConfiguration(); $repositoryClassName = $configuration->getDefaultRepositoryClassName(); } return new $repositoryClassName($documentManager, $documentManager->getUnitOfWork(), $metadata); }
/** * Get Discriminator Values * * @param \Iterator|array $classNames * @return array an array of discriminatorValues (mixed type) * @throws \InvalidArgumentException if the number of found collections > 1 */ private function getDiscriminatorValues($classNames) { $discriminatorValues = array(); $collections = array(); foreach ($classNames as $className) { $class = $this->dm->getClassMetadata($className); $discriminatorValues[] = $class->discriminatorValue; $key = $this->dm->getDocumentDatabase($className)->getName() . '.' . $class->getCollection(); $collections[$key] = $key; } if (count($collections) > 1) { throw new \InvalidArgumentException('Documents involved are not all mapped to the same database collection.'); } return $discriminatorValues; }
/** * Executes a query updating the given document. * * @param object $document * @param array $newObj * @param array $options */ private function executeQuery($document, array $newObj, array $options) { $className = get_class($document); $class = $this->dm->getClassMetadata($className); $id = $class->getDatabaseIdentifierValue($this->uow->getDocumentIdentifier($document)); $query = array('_id' => $id); if ($class->isVersioned) { $query[$class->versionField] = $class->reflFields[$class->versionField]->getValue($document); } $collection = $this->dm->getDocumentCollection($className); $result = $collection->update($query, $newObj, $options); if ($class->isVersioned && !$result['n']) { throw LockException::lockFailed($document); } }
/** * Adds identifiers from a PersistentCollection to $groupedIds. * * If the relation contains simple references, the mapping is assumed to * have a target document class defined. Without that, there is no way to * infer the class of the referenced documents. * * @param PersistentCollection $persistentCollection * @param array $groupedIds */ private function addManyReferences(PersistentCollection $persistentCollection, array &$groupedIds) { $mapping = $persistentCollection->getMapping(); if (!empty($mapping['simple'])) { $className = $mapping['targetDocument']; $class = $this->dm->getClassMetadata($className); } foreach ($persistentCollection->getMongoData() as $reference) { if (!empty($mapping['simple'])) { $id = $reference; } else { $id = $reference['$id']; $className = $this->uow->getClassNameForAssociation($mapping, $reference); $class = $this->dm->getClassMetadata($className); } $document = $this->uow->tryGetById($id, $class); if (!$document || $document instanceof Proxy && !$document->__isInitialized()) { $id = $class->getPHPIdentifierValue($id); $groupedIds[$className][serialize($id)] = $id; } } }
/** * Hydrate array of Riak document data into the given document object. * * @param object $document The document object to hydrate the data into. * @param array $data The array of document data. * @param array $hints Any hints to account for during reconstitution/lookup of the document. * @return array $values The array of hydrated values. */ public function hydrate($document, $data, array $hints = array()) { $metadata = $this->dm->getClassMetadata(get_class($document)); // Invoke preLoad lifecycle events and listeners if (!empty($metadata->lifecycleCallbacks[Events::preLoad])) { $args = array(&$data); $metadata->invokeLifecycleCallbacks(Events::preLoad, $document, $args); } if ($this->evm->hasListeners(Events::preLoad)) { $this->evm->dispatchEvent(Events::preLoad, new PreLoadEventArgs($document, $this->dm, $data)); } // alsoLoadMethods may transform the document before hydration if (!empty($metadata->alsoLoadMethods)) { foreach ($metadata->alsoLoadMethods as $method => $fieldNames) { foreach ($fieldNames as $fieldName) { // Invoke the method only once for the first field we find if (array_key_exists($fieldName, $data)) { $document->{$method}($data[$fieldName]); continue 2; } } } } $data = $this->getHydratorFor($metadata->name)->hydrate($document, $data, $hints); if ($document instanceof Proxy) { $document->__isInitialized__ = true; } // Invoke the postLoad lifecycle callbacks and listeners if (!empty($metadata->lifecycleCallbacks[Events::postLoad])) { $metadata->invokeLifecycleCallbacks(Events::postLoad, $document); } if ($this->evm->hasListeners(Events::postLoad)) { $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($document, $this->dm)); } return $data; }
/** * Prepares a query value and converts the PHP value to the database value * if it is an identifier. * * It also handles converting $fieldName to the database name if they are different. * * @param string $fieldName * @param mixed $value * @param ClassMetadata $class Defaults to $this->class * @param boolean $prepareValue Whether or not to prepare the value * @return array Prepared field name and value */ private function prepareQueryElement($fieldName, $value = null, $class = null, $prepareValue = true) { $class = isset($class) ? $class : $this->class; // @todo Consider inlining calls to ClassMetadataInfo methods // Process all non-identifier fields by translating field names if ($class->hasField($fieldName) && !$class->isIdentifier($fieldName)) { $mapping = $class->fieldMappings[$fieldName]; $fieldName = $mapping['name']; if (!$prepareValue) { return array($fieldName, $value); } // Prepare mapped, embedded objects if (!empty($mapping['embedded']) && is_object($value) && !$this->dm->getMetadataFactory()->isTransient(get_class($value))) { return array($fieldName, $this->pb->prepareEmbeddedDocumentValue($mapping, $value)); } // No further preparation unless we're dealing with a simple reference if (empty($mapping['reference']) || empty($mapping['simple'])) { return array($fieldName, $value); } // Additional preparation for one or more simple reference values $targetClass = $this->dm->getClassMetadata($mapping['targetDocument']); if (!is_array($value)) { return array($fieldName, $targetClass->getDatabaseIdentifierValue($value)); } // Objects without operators or with DBRef fields can be converted immediately if (!$this->hasQueryOperators($value) || $this->hasDBRefFields($value)) { return array($fieldName, $targetClass->getDatabaseIdentifierValue($value)); } return array($fieldName, $this->prepareQueryExpression($value, $targetClass)); } // Process identifier fields if ($class->hasField($fieldName) && $class->isIdentifier($fieldName) || $fieldName === '_id') { $fieldName = '_id'; if (!$prepareValue) { return array($fieldName, $value); } if (!is_array($value)) { return array($fieldName, $class->getDatabaseIdentifierValue($value)); } // Objects without operators or with DBRef fields can be converted immediately if (!$this->hasQueryOperators($value) || $this->hasDBRefFields($value)) { return array($fieldName, $class->getDatabaseIdentifierValue($value)); } return array($fieldName, $this->prepareQueryExpression($value, $class)); } // No processing for unmapped, non-identifier, non-dotted field names if (strpos($fieldName, '.') === false) { return array($fieldName, $value); } /* Process "fieldName.objectProperty" queries (on arrays or objects). * * We can limit parsing here, since at most three segments are * significant: "fieldName.objectProperty" with an optional index or key * for collections stored as either BSON arrays or objects. */ $e = explode('.', $fieldName, 4); // No further processing for unmapped fields if (!isset($class->fieldMappings[$e[0]])) { return array($fieldName, $value); } $mapping = $class->fieldMappings[$e[0]]; $e[0] = $mapping['name']; // Hash and raw fields will not be prepared beyond the field name if ($mapping['type'] === Type::HASH || $mapping['type'] === Type::RAW) { $fieldName = implode('.', $e); return array($fieldName, $value); } if (isset($mapping['strategy']) && CollectionHelper::isHash($mapping['strategy']) && isset($e[2])) { $objectProperty = $e[2]; $objectPropertyPrefix = $e[1] . '.'; $nextObjectProperty = implode('.', array_slice($e, 3)); } elseif ($e[1] != '$') { $fieldName = $e[0] . '.' . $e[1]; $objectProperty = $e[1]; $objectPropertyPrefix = ''; $nextObjectProperty = implode('.', array_slice($e, 2)); } elseif (isset($e[2])) { $fieldName = $e[0] . '.' . $e[1] . '.' . $e[2]; $objectProperty = $e[2]; $objectPropertyPrefix = $e[1] . '.'; $nextObjectProperty = implode('.', array_slice($e, 3)); } else { $fieldName = $e[0] . '.' . $e[1]; return array($fieldName, $value); } // No further processing for fields without a targetDocument mapping if (!isset($mapping['targetDocument'])) { if ($nextObjectProperty) { $fieldName .= '.' . $nextObjectProperty; } return array($fieldName, $value); } $targetClass = $this->dm->getClassMetadata($mapping['targetDocument']); // No further processing for unmapped targetDocument fields if (!$targetClass->hasField($objectProperty)) { if ($nextObjectProperty) { $fieldName .= '.' . $nextObjectProperty; } return array($fieldName, $value); } $targetMapping = $targetClass->getFieldMapping($objectProperty); $objectPropertyIsId = $targetClass->isIdentifier($objectProperty); // Prepare DBRef identifiers or the mapped field's property path $fieldName = $objectPropertyIsId && !empty($mapping['reference']) && empty($mapping['simple']) ? $e[0] . '.$id' : $e[0] . '.' . $objectPropertyPrefix . $targetMapping['name']; // Process targetDocument identifier fields if ($objectPropertyIsId) { if (!$prepareValue) { return array($fieldName, $value); } if (!is_array($value)) { return array($fieldName, $targetClass->getDatabaseIdentifierValue($value)); } // Objects without operators or with DBRef fields can be converted immediately if (!$this->hasQueryOperators($value) || $this->hasDBRefFields($value)) { return array($fieldName, $targetClass->getDatabaseIdentifierValue($value)); } return array($fieldName, $this->prepareQueryExpression($value, $targetClass)); } /* The property path may include a third field segment, excluding the * collection item pointer. If present, this next object property must * be processed recursively. */ if ($nextObjectProperty) { // Respect the targetDocument's class metadata when recursing $nextTargetClass = isset($targetMapping['targetDocument']) ? $this->dm->getClassMetadata($targetMapping['targetDocument']) : null; list($key, $value) = $this->prepareQueryElement($nextObjectProperty, $value, $nextTargetClass, $prepareValue); $fieldName .= '.' . $key; } return array($fieldName, $value); }
/** * Notifies this UnitOfWork of a property change in a document. * * @param object $document The document that owns the property. * @param string $propertyName The name of the property that changed. * @param mixed $oldValue The old value of the property. * @param mixed $newValue The new value of the property. */ public function propertyChanged($document, $propertyName, $oldValue, $newValue) { $oid = spl_object_hash($document); $class = $this->dm->getClassMetadata(get_class($document)); if (!isset($class->fieldMappings[$propertyName])) { return; // ignore non-persistent fields } // Update changeset and mark document for synchronization $this->documentChangeSets[$oid][$propertyName] = array($oldValue, $newValue); if (!isset($this->scheduledForDirtyCheck[$class->name][$oid])) { $this->scheduleForDirtyCheck($document); } }
/** * Returns the embedded document to be stored in Riak. * * 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; } $value = $this->prepareAssociatedCollectionValue($rawValue, $includeNestedCollections); 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; }