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, null, $this->getReferenceManyCollectionTypeFromMetadata($mapping));
$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);
}
}
}
}