Example #1
0
 /**
  * If the parent node has child restrictions, ensure that the given
  * class name is within them.
  *
  * @param NodeInterface $parentNode
  * @param string $classFqn
  */
 private function validateChildClass(NodeInterface $parentNode, ClassMetadata $class)
 {
     $parentClass = $this->documentClassMapper->getClassName($this->dm, $parentNode);
     if (null === $parentClass) {
         return;
     }
     $metadata = $this->dm->getClassMetadata($parentClass);
     $metadata->assertValidChildClass($class);
 }
Example #2
0
 /**
  * Executes all document updates
  *
  * @param array   $documents      array of all to be updated documents
  * @param boolean $dispatchEvents if to dispatch events
  */
 private function executeUpdates($documents, $dispatchEvents = true)
 {
     foreach ($documents as $oid => $document) {
         if (!$this->contains($oid)) {
             continue;
         }
         $class = $this->dm->getClassMetadata(get_class($document));
         $node = $this->session->getNode($this->getDocumentId($document));
         if ($this->writeMetadata) {
             $this->documentClassMapper->writeMetadata($this->dm, $node, $class->name);
         }
         if ($dispatchEvents) {
             if ($invoke = $this->eventListenersInvoker->getSubscribedSystems($class, Event::preUpdate)) {
                 $this->eventListenersInvoker->invoke($class, Event::preUpdate, $document, new PreUpdateEventArgs($document, $this->dm, $this->documentChangesets[$oid]), $invoke);
                 $this->changesetComputed = array_diff($this->changesetComputed, array($oid));
                 $this->computeChangeSet($class, $document);
             }
         }
         $fields = isset($this->documentChangesets[$oid]['fields']) ? $this->documentChangesets[$oid]['fields'] : array();
         foreach ($fields as $fieldName => $data) {
             $fieldValue = $data[1];
             // PHPCR does not validate nullable unless we would start to
             // generate custom node types, which we at the moment don't.
             // the ORM can delegate this validation to the relational database
             // that is using a strict schema.
             // do this after the preUpdate events to give listener a last
             // chance to provide values
             if (null === $fieldValue && in_array($fieldName, $class->fieldMappings) && !$class->isNullable($fieldName) && !$this->isAutocreatedProperty($class, $fieldName)) {
                 throw new PHPCRException(sprintf('Field "%s" of class "%s" is not nullable', $fieldName, $class->name));
             }
             // Ignore translatable fields (they will be persisted by the translation strategy)
             if (in_array($fieldName, $class->translatableFields)) {
                 continue;
             }
             $mapping = $class->mappings[$fieldName];
             if (in_array($fieldName, $class->fieldMappings)) {
                 $type = PropertyType::valueFromName($mapping['type']);
                 if ($mapping['multivalue']) {
                     $value = empty($fieldValue) ? null : ($fieldValue instanceof Collection ? $fieldValue->toArray() : $fieldValue);
                     if ($value && isset($mapping['assoc'])) {
                         $value = $this->processAssoc($node, $mapping, $value);
                     }
                 } else {
                     $value = $fieldValue;
                 }
                 $node->setProperty($mapping['property'], $value, $type);
             } elseif ($mapping['type'] === $class::MANY_TO_ONE || $mapping['type'] === $class::MANY_TO_MANY) {
                 if (!$this->writeMetadata) {
                     continue;
                 }
                 if ($node->hasProperty($mapping['property']) && is_null($fieldValue)) {
                     $node->getProperty($mapping['property'])->remove();
                     if (isset($mapping['assoc'])) {
                         $this->removeAssoc($node, $mapping);
                     }
                     continue;
                 }
                 switch ($mapping['strategy']) {
                     case 'hard':
                         $strategy = PropertyType::REFERENCE;
                         break;
                     case 'path':
                         $strategy = PropertyType::PATH;
                         break;
                     default:
                         $strategy = PropertyType::WEAKREFERENCE;
                         break;
                 }
                 if ($mapping['type'] === $class::MANY_TO_MANY) {
                     if (isset($fieldValue)) {
                         $refNodesIds = array();
                         foreach ($fieldValue as $fv) {
                             if ($fv === null) {
                                 continue;
                             }
                             $associatedNode = $this->session->getNode($this->getDocumentId($fv));
                             if ($strategy === PropertyType::PATH) {
                                 $refNodesIds[] = $associatedNode->getPath();
                             } else {
                                 $refClass = $this->dm->getClassMetadata(get_class($fv));
                                 $this->setMixins($refClass, $associatedNode, $fv);
                                 if (!$associatedNode->isNodeType('mix:referenceable')) {
                                     throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: ' . self::objToStr($document, $this->dm), ClassUtils::getClass($fv)));
                                 }
                                 $refNodesIds[] = $associatedNode->getIdentifier();
                             }
                         }
                         $refNodesIds = empty($refNodesIds) ? null : $refNodesIds;
                         $node->setProperty($mapping['property'], $refNodesIds, $strategy);
                     }
                 } elseif ($mapping['type'] === $class::MANY_TO_ONE) {
                     if (isset($fieldValue)) {
                         $associatedNode = $this->session->getNode($this->getDocumentId($fieldValue));
                         if ($strategy === PropertyType::PATH) {
                             $node->setProperty($fieldName, $associatedNode->getPath(), $strategy);
                         } else {
                             $refClass = $this->dm->getClassMetadata(get_class($fieldValue));
                             $this->setMixins($refClass, $associatedNode, $document);
                             if (!$associatedNode->isNodeType('mix:referenceable')) {
                                 throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: ' . self::objToStr($document, $this->dm), ClassUtils::getClass($fieldValue)));
                             }
                             $node->setProperty($mapping['property'], $associatedNode->getIdentifier(), $strategy);
                         }
                     }
                 }
             } elseif ('referrers' === $mapping['type']) {
                 if (isset($fieldValue)) {
                     /*
                      * each document in referrers field is supposed to
                      * reference this document, so we have to update its
                      * referencing property to contain the uuid of this
                      * document
                      */
                     foreach ($fieldValue as $fv) {
                         if ($fv === null) {
                             continue;
                         }
                         if (!$fv instanceof $mapping['referringDocument']) {
                             throw new PHPCRException(sprintf("%s is not an instance of %s for document %s field %s", self::objToStr($fv, $this->dm), $mapping['referencedBy'], self::objToStr($document, $this->dm), $mapping['fieldName']));
                         }
                         $referencingNode = $this->session->getNode($this->getDocumentId($fv));
                         $referencingMeta = $this->dm->getClassMetadata($mapping['referringDocument']);
                         $referencingField = $referencingMeta->getAssociation($mapping['referencedBy']);
                         $uuid = $node->getIdentifier();
                         $strategy = $referencingField['strategy'] == 'weak' ? PropertyType::WEAKREFERENCE : PropertyType::REFERENCE;
                         switch ($referencingField['type']) {
                             case ClassMetadata::MANY_TO_ONE:
                                 $ref = $referencingMeta->getFieldValue($fv, $referencingField['fieldName']);
                                 if ($ref !== null && $ref !== $document) {
                                     throw new PHPCRException(sprintf('Conflicting settings for referrer and reference: Document %s field %s points to %s but document %s has set first document as referrer on field %s', self::objToStr($fv, $this->dm), $referencingField['fieldName'], self::objToStr($ref, $this->dm), self::objToStr($document, $this->dm), $mapping['fieldName']));
                                 }
                                 // update the referencing document field to point to this document
                                 $referencingMeta->setFieldValue($fv, $referencingField['fieldName'], $document);
                                 // and make sure the reference is not deleted in this change because the field could be null
                                 unset($this->documentChangesets[spl_object_hash($fv)]['fields'][$referencingField['fieldName']]);
                                 // store the change in PHPCR
                                 $referencingNode->setProperty($referencingField['property'], $uuid, $strategy);
                                 break;
                             case ClassMetadata::MANY_TO_MANY:
                                 /** @var $collection ReferenceManyCollection */
                                 $collection = $referencingMeta->getFieldValue($fv, $referencingField['fieldName']);
                                 if ($collection instanceof PersistentCollection && $collection->isDirty()) {
                                     throw new PHPCRException(sprintf('You may not modify the reference and referrer collections of interlinked documents as this is ambiguous. Reference %s on document %s and referrers %s on document %s are both modified', self::objToStr($fv, $this->dm), $referencingField['fieldName'], self::objToStr($document, $this->dm), $mapping['fieldName']));
                                 }
                                 if ($collection) {
                                     // make sure the reference is not deleted in this change because the field could be null
                                     unset($this->documentChangesets[spl_object_hash($fv)]['fields'][$referencingField['fieldName']]);
                                 } else {
                                     $collection = new ReferenceManyCollection($this->dm, $fv, $referencingField['property'], array($node), $class->name);
                                     $referencingMeta->setFieldValue($fv, $referencingField['fieldName'], $collection);
                                 }
                                 if ($referencingNode->hasProperty($referencingField['property'])) {
                                     if (!in_array($uuid, $referencingNode->getProperty($referencingField['property'])->getString())) {
                                         if (!$collection instanceof PersistentCollection || !$collection->isDirty()) {
                                             // update the reference collection: add us to it
                                             $collection->add($document);
                                         }
                                         // store the change in PHPCR
                                         $referencingNode->getProperty($referencingField['property'])->addValue($uuid);
                                         // property should be correct type already
                                     }
                                 } else {
                                     // store the change in PHPCR
                                     $referencingNode->setProperty($referencingField['property'], array($uuid), $strategy);
                                 }
                                 // avoid confusion later, this change to the reference collection is already saved
                                 $collection->setDirty(false);
                                 break;
                             default:
                                 // in class metadata we only did a santiy check but not look at the actual mapping
                                 throw new MappingException(sprintf('Field "%s" of document "%s" is not a reference field. Error in referrer annotation: ' . self::objToStr($document, $this->dm), $mapping['referencedBy'], ClassUtils::getClass($fv)));
                         }
                     }
                 }
             } elseif ('child' === $mapping['type']) {
                 if ($fieldValue === null && $node->hasNode($mapping['nodeName'])) {
                     $child = $node->getNode($mapping['nodeName']);
                     $childDocument = $this->getOrCreateDocument(null, $child);
                     $this->purgeChildren($childDocument);
                     $child->remove();
                 }
             }
         }
         if (!empty($this->documentChangesets[$oid]['reorderings'])) {
             foreach ($this->documentChangesets[$oid]['reorderings'] as $reorderings) {
                 foreach ($reorderings as $srcChildRelPath => $destChildRelPath) {
                     $node->orderBefore($srcChildRelPath, $destChildRelPath);
                 }
             }
         }
         $this->doSaveTranslation($document, $node, $class);
         if ($dispatchEvents) {
             if ($invoke = $this->eventListenersInvoker->getSubscribedSystems($class, Event::postUpdate)) {
                 $this->eventListenersInvoker->invoke($class, Event::postUpdate, $document, new LifecycleEventArgs($document, $this->dm), $invoke);
             }
         }
     }
 }
Example #3
0
 /**
  * Executes all document updates
  *
  * @param array $documents array of all to be updated documents
  * @param boolean $dispatchEvents if to dispatch events
  */
 private function executeUpdates($documents, $dispatchEvents = true)
 {
     foreach ($documents as $oid => $document) {
         if (!$this->contains($oid)) {
             continue;
         }
         $class = $this->dm->getClassMetadata(get_class($document));
         $node = $this->session->getNode($this->getDocumentId($document));
         if ($this->writeMetadata) {
             $this->documentClassMapper->writeMetadata($this->dm, $node, $class->name);
         }
         if ($dispatchEvents) {
             if (isset($class->lifecycleCallbacks[Event::preUpdate])) {
                 $class->invokeLifecycleCallbacks(Event::preUpdate, $document);
                 $this->computeChangeSet($class, $document);
             }
             if ($this->evm->hasListeners(Event::preUpdate)) {
                 $this->evm->dispatchEvent(Event::preUpdate, new LifecycleEventArgs($document, $this->dm));
                 $this->computeChangeSet($class, $document);
             }
         }
         foreach ($this->documentChangesets[$oid] as $fieldName => $fieldValue) {
             // Ignore translatable fields (they will be persisted by the translation strategy)
             if (in_array($fieldName, $class->translatableFields)) {
                 continue;
             }
             if (isset($class->fieldMappings[$fieldName])) {
                 $type = PropertyType::valueFromName($class->fieldMappings[$fieldName]['type']);
                 if ($class->fieldMappings[$fieldName]['multivalue']) {
                     $value = $fieldValue === null ? null : $fieldValue->toArray();
                     $node->setProperty($class->fieldMappings[$fieldName]['name'], $value, $type);
                 } else {
                     $node->setProperty($class->fieldMappings[$fieldName]['name'], $fieldValue, $type);
                 }
             } elseif (isset($class->associationsMappings[$fieldName]) && $this->writeMetadata) {
                 if ($node->hasProperty($class->associationsMappings[$fieldName]['fieldName']) && is_null($fieldValue)) {
                     $node->getProperty($class->associationsMappings[$fieldName]['fieldName'])->remove();
                     continue;
                 }
                 $type = $class->associationsMappings[$fieldName]['weak'] ? PropertyType::WEAKREFERENCE : PropertyType::REFERENCE;
                 if ($class->associationsMappings[$fieldName]['type'] === $class::MANY_TO_MANY) {
                     if (isset($fieldValue)) {
                         $refNodesIds = array();
                         foreach ($fieldValue as $fv) {
                             if ($fv === null) {
                                 continue;
                             }
                             $associatedNode = $this->session->getNode($this->getDocumentId($fv));
                             $refClass = $this->dm->getClassMetadata(get_class($fv));
                             $this->setMixins($refClass, $associatedNode);
                             if (!$associatedNode->isNodeType('mix:referenceable')) {
                                 throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: ' . self::objToStr($document, $this->dm), get_class($fv)));
                             }
                             $refNodesIds[] = $associatedNode->getIdentifier();
                         }
                         if (!empty($refNodesIds)) {
                             $node->setProperty($class->associationsMappings[$fieldName]['fieldName'], $refNodesIds, $type);
                         }
                     }
                 } elseif ($class->associationsMappings[$fieldName]['type'] === $class::MANY_TO_ONE) {
                     if (isset($fieldValue)) {
                         $associatedNode = $this->session->getNode($this->getDocumentId($fieldValue));
                         $refClass = $this->dm->getClassMetadata(get_class($fieldValue));
                         $this->setMixins($refClass, $associatedNode);
                         if (!$associatedNode->isNodeType('mix:referenceable')) {
                             throw new PHPCRException(sprintf('Referenced document %s is not referenceable. Use referenceable=true in Document annotation: ' . self::objToStr($document, $this->dm), get_class($fieldValue)));
                         }
                         $node->setProperty($class->associationsMappings[$fieldName]['fieldName'], $associatedNode->getIdentifier(), $type);
                     }
                 }
             } elseif (isset($class->childMappings[$fieldName])) {
                 if ($fieldValue === null) {
                     if ($node->hasNode($class->childMappings[$fieldName]['name'])) {
                         $child = $node->getNode($class->childMappings[$fieldName]['name']);
                         $childDocument = $this->createDocument(null, $child);
                         $this->purgeChildren($childDocument);
                         $child->remove();
                     }
                 } elseif ($this->originalData[$oid][$fieldName] && $this->originalData[$oid][$fieldName] !== $fieldValue) {
                     throw new PHPCRException('Cannot move/copy children by assignment as it would be ambiguous. Please use the DocumentManager::move() or PHPCR\\Session::copy() operations for this.');
                 }
             }
         }
         $this->doSaveTranslation($document, $node, $class);
         if ($dispatchEvents) {
             if (isset($class->lifecycleCallbacks[Event::postUpdate])) {
                 $class->invokeLifecycleCallbacks(Event::postUpdate, $document);
             }
             if ($this->evm->hasListeners(Event::postUpdate)) {
                 $this->evm->dispatchEvent(Event::postUpdate, new LifecycleEventArgs($document, $this->dm));
             }
         }
     }
 }