/** * Create a property, either from server data or locally * * To indicate a property has newly been created locally, make sure to pass * true for the $new parameter. * * @param object $factory an object factory implementing "get" as described in \jackalope\Factory * @param array $data array with fields * type (integer or string from PropertyType) * and value (data for creating value object - array for multivalue property) * @param string $path the absolute path of this item * @param Session the session instance * @param ObjectManager the objectmanager instance - the caller has to take care of registering this item with the object manager * @param boolean $new optional: set to true to make this property aware its not yet existing on the server. defaults to false */ public function __construct($factory, array $data, $path, Session $session, ObjectManager $objectManager, $new = false) { parent::__construct($factory, $path, $session, $objectManager, $new); $type = $data['type']; if (is_string($type)) { $type = \PHPCR\PropertyType::valueFromName($type); } elseif (!is_numeric($type)) { throw new \PHPCR\RepositoryException("INTERNAL ERROR -- No valid type specified ({$type})"); } else { //sanity check. this will throw InvalidArgumentException if $type is not a valid type \PHPCR\PropertyType::nameFromValue($type); } $this->type = $type; if (is_array($data['value'])) { $this->isMultiple = true; $this->value = array(); foreach ($data['value'] as $value) { $this->value[] = Helper::convertType($value, $type); } } elseif (null !== $data['value']) { $this->value = Helper::convertType($data['value'], $type); } else { throw new \PHPCR\RepositoryException('INTERNAL ERROR -- data[value] may not be null'); } }
private function walkNode(NodeBuilder $builder, NodeInterface $parentNode) { for ($i = $builder->getRangeStart(); $i <= $builder->getRangeEnd(); $i++) { $name = str_replace(NodeBuilder::ITERATION_TOKEN, $i, $builder->getName()); $this->count++; $node = $parentNode->addNode($name, $builder->getNodeTypeName()); foreach ($builder->getProperties() as $property) { list($name, $value, $type) = $property; $node->setProperty($name, $value, PropertyType::valueFromName($type)); } foreach ($builder->getNodes() as $childNodeBuilder) { $this->walkNode($childNodeBuilder, $node); } } }
private function configure() { $this->nodeTypeMap = array('name' => function ($t, $v) { $t->setName($v); }, 'auto_created' => function ($t, $v) { $t->setAutoCreated((bool) $v); }, 'declared_supertypes' => function ($t, $v) { $t->setDeclaredSuperTypeNames((array) $v); }, 'abstract' => function ($t, $v) { $t->setAbstract($v); }, 'mixin' => function ($t, $v) { $t->setMixin((bool) $v); }, 'orderable_child_nodes' => function ($t, $v) { $t->setOrderableChildNodes((bool) $v); }, 'primary_item' => function ($t, $v) { $t->setPrimaryItemName((string) $v); }, 'queryable' => function ($t, $v) { $t->setQueryable((bool) $v); }); $this->childDefinitionMap = array_merge($this->getItemDefinitionMap(), array('default_primary_type' => function ($t, $v) { $t->setDefaultPrimaryTypeName($v); }, 'same_name_siblings' => function ($t, $v) { $t->setSameNameSiblings((bool) $v); }, 'required_primary_types' => function ($t, $v) { $t->setRequiredPrimaryTypeNames((array) $v); })); $this->propertyMap = array_merge($this->getItemDefinitionMap(), array('multiple' => function ($t, $v) { $t->setMultiple((bool) $v); }, 'default_values' => function ($t, $v) { $t->setDefaultValues((array) $v); }, 'full_text_searchable' => function ($t, $v) { $t->setFullTextSearchable((bool) $v); }, 'query_orderable' => function ($t, $v) { $t->setQueryOrderable((bool) $v); }, 'value_constraints' => function ($t, $v) { $t->setValueConstraints((array) $v); }, 'required_type' => function ($t, $v) { $t->setRequiredType(PropertyType::valueFromName($v)); }, 'available_query_operators' => function ($t, $v) { $queryOperators = array(); foreach ($v as $queryOperator) { $queryOperator = constant('PHPCR\\Query\\QOM\\QueryObjectModelConstantsInterface::' . strtoupper(str_replace('.', '_', $queryOperator))); $queryOperators[] = $queryOperator; } $t->setAvailableQueryOperators($queryOperators); })); }
protected function fromXML(DOMElement $node) { parent::fromXML($node); $this->requiredType = \PHPCR\PropertyType::valueFromName($node->getAttribute('requiredType')); $this->isMultiple = Helper::getBoolAttribute($node, 'multiple'); $this->isFullTextSearchable = Helper::getBoolAttribute($node, 'fullTextSearchable'); $this->isQueryOrderable = Helper::getBoolAttribute($node, 'queryOrderable'); $xp = new DOMXPath($node->ownerDocument); $valueConstraints = $xp->query('valueConstraints/valueConstraint', $node); foreach ($valueConstraints as $valueConstraint) { $this->valueConstraints[] = $valueConstraint->nodeValue; } $availableQueryOperators = $xp->query('availableQueryOperators/availableQueryOperator', $node); foreach ($availableQueryOperators as $availableQueryOperator) { $this->availableQueryOperators[] = $availableQueryOperator->nodeValue; } $defaultValues = $xp->query('defaultValues/defaultValue', $node); foreach ($defaultValues as $defaultValue) { $this->defaultValues[] = $defaultValue->nodeValue; } }
public function execute(InputInterface $input, OutputInterface $output) { $session = $this->get('phpcr.session'); $pathHelper = $this->get('helper.path'); $path = $session->getAbsPath($input->getArgument('path')); $value = $input->getArgument('value'); $type = $input->getOption('type'); $nodePath = $pathHelper->getParentPath($path); $propName = $pathHelper->getNodeName($path); $nodes = $session->findNodes($nodePath); foreach ($nodes as $node) { $intType = null; if ($type) { $intType = PropertyType::valueFromName($type); if ($intType === PropertyType::REFERENCE || $intType === PropertyType::WEAKREFERENCE) { // convert path to UUID if (false === UUIDHelper::isUuid($value)) { $path = $value; try { $targetNode = $session->getNode($path); $value = $targetNode->getIdentifier(); } catch (PathNotFoundException $e) { } if (null === $value) { throw new \InvalidArgumentException(sprintf('Node at path "%s" specified for reference is not referenceable', $path)); } } } } else { try { $property = $node->getProperty($propName); $intType = $property->getType(); } catch (PathNotFoundException $e) { // property doesn't exist and no type specified, default to string $intType = PropertyType::STRING; } } $node->setProperty($propName, $value, $intType); } }
/** * Convert property definition xml into array. * * @param \DOMElement $node * @return array */ public function getPropertyDefinitionFromXml(DOMElement $node) { $data = $this->getItemDefinitionFromXml($node); $data['requiredType'] = \PHPCR\PropertyType::valueFromName($node->getAttribute('requiredType')); $data['multiple'] = Helper::getBoolAttribute($node, 'multiple'); $data['fullTextSearchable'] = Helper::getBoolAttribute($node, 'fullTextSearchable'); $data['queryOrderable'] = Helper::getBoolAttribute($node, 'queryOrderable'); $xp = new DOMXPath($node->ownerDocument); $valueConstraints = $xp->query('valueConstraints/valueConstraint', $node); foreach ($valueConstraints as $valueConstraint) { $data['valueConstraints'][] = $valueConstraint->nodeValue; } $availableQueryOperators = $xp->query('availableQueryOperators/availableQueryOperator', $node); foreach ($availableQueryOperators as $availableQueryOperator) { $data['availableQueryOperators'][] = $availableQueryOperator->nodeValue; } $defaultValues = $xp->query('defaultValues/defaultValue', $node); foreach ($defaultValues as $defaultValue) { $data['defaultValues'][] = $defaultValue->nodeValue; } return $data; }
/** * Create a property, either from server data or locally * * To indicate a property has newly been created locally, make sure to pass * true for the $new parameter. In that case, you should pass an empty array * for $data and use setValue afterwards to let the type magic be handled. * Then multivalue is determined on setValue * * For binary properties, the value is the length of the data(s), not the * data itself. * * @param FactoryInterface $factory the object factory * @param array $data array with fields <tt>type</tt> (integer or string * from PropertyType) and <tt>value</tt> (data for creating the * property value - array for multivalue property) * @param string $path the absolute path of this item * @param Session $session the session instance * @param ObjectManager $objectManager the objectManager instance - the caller has to take * care of registering this item with the object manager * @param boolean $new optional: set to true to make this property aware * its not yet existing on the server. defaults to false */ public function __construct(FactoryInterface $factory, array $data, $path, Session $session, ObjectManager $objectManager, $new = false) { parent::__construct($factory, $path, $session, $objectManager, $new); $this->wrapBinaryStreams = $session->getRepository()->getDescriptor(Repository::JACKALOPE_OPTION_STREAM_WRAPPER); if (empty($data) && $new) { return; } if (!isset($data['value'])) { throw new InvalidArgumentException("Can't create property at {$path} without any data"); } if (isset($data['type']) && PropertyType::UNDEFINED !== $data['type']) { $type = $data['type']; if (is_string($type)) { $type = PropertyType::valueFromName($type); } elseif (!is_numeric($type)) { // @codeCoverageIgnoreStart throw new RepositoryException("INTERNAL ERROR -- No valid type specified ({$type})"); // @codeCoverageIgnoreEnd } else { //sanity check. this will throw InvalidArgumentException if $type is not a valid type PropertyType::nameFromValue($type); } } else { // we are creating a node $type = PropertyType::determineType(is_array($data['value']) ? reset($data['value']) : $data['value']); } $this->type = $type; if ($type == PropertyType::BINARY && !$new) { // reading a binary property from backend, we do not get the stream immediately but just the size if (is_array($data['value'])) { $this->isMultiple = true; } $this->length = $data['value']; $this->value = null; return; } if (is_array($data['value'])) { $this->isMultiple = true; $this->value = array(); foreach ($data['value'] as $value) { $this->value[] = PropertyType::convertType($value, $type); } } elseif (null !== $data['value']) { $this->value = PropertyType::convertType($data['value'], $type); } else { // @codeCoverageIgnoreStart throw new RepositoryException('INTERNAL ERROR -- data[value] may not be null'); // @codeCoverageIgnoreEnd } }
public static function xmlToProps($xml, ValueConverter $valueConverter, $filter = null) { $data = new \stdClass(); $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->loadXML($xml); foreach ($dom->getElementsByTagNameNS('http://www.jcp.org/jcr/sv/1.0', 'property') as $propertyNode) { $name = $propertyNode->getAttribute('sv:name'); // only return the properties that pass through the filter callback if (null !== $filter && is_callable($filter) && false === $filter($name)) { continue; } $values = array(); $type = PropertyType::valueFromName($propertyNode->getAttribute('sv:type')); foreach ($propertyNode->childNodes as $valueNode) { switch ($type) { case PropertyType::NAME: case PropertyType::URI: case PropertyType::WEAKREFERENCE: case PropertyType::REFERENCE: case PropertyType::PATH: case PropertyType::DECIMAL: case PropertyType::STRING: $values[] = $valueNode->nodeValue; break; case PropertyType::BOOLEAN: $values[] = (bool) $valueNode->nodeValue; break; case PropertyType::LONG: $values[] = (int) $valueNode->nodeValue; break; case PropertyType::BINARY: $values[] = (int) $valueNode->nodeValue; break; case PropertyType::DATE: $date = $valueNode->nodeValue; if ($date) { $date = new \DateTime($date); $date->setTimezone(new \DateTimeZone(date_default_timezone_get())); // Jackalope expects a string, might make sense to refactor to allow \DateTime instances too $date = $valueConverter->convertType($date, PropertyType::STRING); } $values[] = $date; break; case PropertyType::DOUBLE: $values[] = (double) $valueNode->nodeValue; break; default: throw new \InvalidArgumentException("Type with constant $type not found."); } } switch ($type) { case PropertyType::BINARY: $data->{':' . $name} = $propertyNode->getAttribute('sv:multi-valued') ? $values : $values[0]; break; default: $data->{$name} = $propertyNode->getAttribute('sv:multi-valued') ? $values : $values[0]; $data->{':' . $name} = $type; break; } } return $data; }
/** * {@inheritDoc} */ public function denormalize($data, $class, $format = null, array $context = array()) { if (!$data) { throw new \InvalidArgumentException('Editor returned nothing .. nodes must have at least one property (i.e. the jcr:primaryType property)'); } if (!isset($context['node'])) { throw new \InvalidArgumentException(sprintf('You must provide the PHPCR node instance to update in the context using the "node" key.')); } $node = $context['node']; $errors = array(); // Update / remove existing properties foreach ($node->getProperties() as $property) { if (false === $this->isPropertyEditable($property)) { continue; } try { if (!isset($data[$property->getName()])) { $property->remove(); continue; } $datum = $this->normalizeDatum($data[$property->getName()]); $typeValue = isset($datum['type']) ? PropertyType::valueFromName($datum['type']) : null; if (isset($datum['value'])) { // if the type or the value is differnet, update the property if ($datum['value'] != $property->getValue() || $typeValue != $property->getType()) { // setValue doesn't like being passed a null value as a type ... if ($typeValue !== null) { $property->setValue($datum['value'], $typeValue); } else { $property->setValue($datum['value']); } } } } catch (\Exception $e) { $errors[] = $e->getMessage(); } unset($data[$property->getName()]); } // Add new properties foreach ($data as $pName => $datum) { $datum = $this->normalizeDatum($datum); $pValue = isset($datum['value']) ? $datum['value'] : null; $pType = isset($datum['type']) ? PropertyType::valueFromName($datum['type']) : null; if ($pValue !== null) { $node->setProperty($pName, $pValue, $pType); } } if (count($errors) > 0) { throw new InvalidArgumentException(sprintf('Errors encountered during denormalization: %s', implode($errors, "\n"))); } }
/** * Initialize or update this object with raw data from backend. * * @param array $rawData in the format as returned from Jackalope\Transport\TransportInterface * @param boolean $update whether to initialize this object or update * @param boolean $keepChanges only used if $update is true, same as $keepChanges in refresh() * * @see Node::__construct() * @see Node::refresh() */ private function parseData($rawData, $update, $keepChanges = false) { //TODO: refactor to use hash array instead of stdClass struct if ($update) { // keep backup of old state so we can remove what needs to be removed $oldNodes = array_flip(array_values($this->nodes)); $oldProperties = $this->properties; } /* * we collect all nodes coming from the backend. if we update with * $keepChanges, we use this to update the node list rather than losing * reorders * * properties are easy as they are not ordered. */ $nodesInBackend = array(); foreach ($rawData as $key => $value) { $node = false; // reset to avoid trouble if (is_object($value)) { // this is a node. add it if if (!$update || !$keepChanges || isset($oldNodes[$key]) || !($node = $this->objectManager->getCachedNode($this->path . '/' . $key))) { // for all those cases, if the node was moved away or is deleted in current session, we do not add it if (!$this->objectManager->isNodeMoved($this->path . '/' . $key) && !$this->objectManager->isNodeDeleted($this->path . '/' . $key)) { // otherwise we (re)load a node from backend but a child has been moved away already $nodesInBackend[] = $key; } } if ($update) { unset($oldNodes[$key]); } } else { //property or meta information /* Property type declarations start with :, the value then is * the type string from the NodeType constants. We skip that and * look at the type when we encounter the value of the property. * * If its a binary data, we only get the type declaration and * no data. Then the $value of the type declaration is not the * type string for binary, but the number of bytes of the * property - resp. array of number of bytes. * * The magic property ::NodeIteratorSize tells this node has no * children. Ignore that info for now. We might optimize with * this info once we do prefetch nodes. */ if (0 === strpos($key, ':')) { if ((is_int($value) || is_array($value)) && $key != '::NodeIteratorSize') { // This is a binary property and we just got its length with no data $key = substr($key, 1); if (!isset($rawData->{$key})) { $binaries[$key] = $value; if ($update) { unset($oldProperties[$key]); } if (isset($this->properties[$key])) { // refresh existing binary, this will only happen in update // only update length if (!($keepChanges && $this->properties[$key]->isModified())) { $this->properties[$key]->_setLength($value); if ($this->properties[$key]->isDirty()) { $this->properties[$key]->setClean(); } } } else { // this will always fall into the creation mode $this->_setProperty($key, $value, PropertyType::BINARY, true); } } } //else this is a type declaration //skip this entry (if its binary, its already processed continue; } if ($update && array_key_exists($key, $this->properties)) { unset($oldProperties[$key]); $prop = $this->properties[$key]; if ($keepChanges && $prop->isModified()) { continue; } } elseif ($update && array_key_exists($key, $this->deletedProperties)) { if ($keepChanges) { // keep the delete continue; } else { // restore the property $this->properties[$key] = $this->deletedProperties[$key]; $this->properties[$key]->setClean(); // now let the loop update the value. no need to talk to ObjectManager as it // does not store property deletions } } switch ($key) { case 'jcr:index': $this->index = $value; break; case 'jcr:primaryType': $this->primaryType = $value; // type information is exposed as property too, // although there exist more specific methods $this->_setProperty('jcr:primaryType', $value, PropertyType::NAME, true); break; case 'jcr:mixinTypes': // type information is exposed as property too, // although there exist more specific methods $this->_setProperty($key, $value, PropertyType::NAME, true); break; // OPTIMIZE: do not instantiate properties until needed // OPTIMIZE: do not instantiate properties until needed default: if (isset($rawData->{':' . $key})) { /* * this is an inconsistency between jackrabbit and * dbal transport: jackrabbit has type name, dbal * delivers numeric type. * we should eventually fix the format returned by * transport and either have jackrabbit transport * do the conversion or let dbal store a string * value instead of numerical. */ $type = is_numeric($rawData->{':' . $key}) ? $rawData->{':' . $key} : PropertyType::valueFromName($rawData->{':' . $key}); } else { $type = $this->valueConverter->determineType($value); } $this->_setProperty($key, $value, $type, true); break; } } } if ($update) { if ($keepChanges) { // we keep changes. merge new nodes to the right place $previous = null; $newFromBackend = array_diff($nodesInBackend, array_intersect($this->nodes, $nodesInBackend)); foreach ($newFromBackend as $name) { $pos = array_search($name, $nodesInBackend); if (is_array($this->originalNodesOrder)) { // update original order to send the correct reorderings array_splice($this->originalNodesOrder, $pos, 0, $name); } if ($pos === 0) { array_unshift($this->nodes, $name); } else { // do we find the predecessor of the new node in the list? $insert = array_search($nodesInBackend[$pos - 1], $this->nodes); if (false !== $insert) { array_splice($this->nodes, $insert + 1, 0, $name); } else { // failed to find predecessor, add to the end $this->nodes[] = $name; } } } } else { // discard changes, just overwrite node list $this->nodes = $nodesInBackend; $this->originalNodesOrder = null; } foreach ($oldProperties as $name => $property) { if (!($keepChanges && $property->isNew())) { // may not call remove(), we don't want another delete with // the backend to be attempted $this->properties[$name]->setDeleted(); unset($this->properties[$name]); } } // notify nodes that where not received again that they disappeared foreach ($oldNodes as $name => $index) { if ($this->objectManager->purgeDisappearedNode($this->path . '/' . $name, $keepChanges)) { // drop, it was not a new child if ($keepChanges) { // otherwise we overwrote $this->nodes with the backend $id = array_search($name, $this->nodes); if (false !== $id) { unset($this->nodes[$id]); } } } } } else { // new node loaded from backend $this->nodes = $nodesInBackend; } }
protected function parseCastLiteral($token) { if (!$this->scanner->tokenIs($token, 'CAST')) { throw new \LogicException('parseCastLiteral when not a CAST'); } $this->scanner->expectToken('('); $token = $this->scanner->fetchNextToken(); $quoteString = false; if (substr($token, 0, 1) === '\'') { $quoteString = "'"; } elseif (substr($token, 0, 1) === '"') { $quoteString = '"'; } if ($quoteString) { while (substr($token, -1) !== $quoteString) { $nextToken = $this->scanner->fetchNextToken(); if ('' === $nextToken) { break; } $token .= $nextToken; } if (substr($token, -1) !== $quoteString) { throw new InvalidQueryException("Syntax error: unterminated quoted string '{$token}' in '{$this->sql2}'"); } $token = substr($token, 1, -1); $token = str_replace('\\' . $quoteString, $quoteString, $token); } $this->scanner->expectToken('AS'); $type = $this->scanner->fetchNextToken(); try { $typeValue = PropertyType::valueFromName($type); } catch (\InvalidArgumentException $e) { throw new InvalidQueryException("Syntax error: attempting to cast to an invalid type '{$type}'"); } $this->scanner->expectToken(')'); try { $token = $this->valueConverter->convertType($token, $typeValue, PropertyType::STRING); } catch (\Exception $e) { throw new InvalidQueryException("Syntax error: attempting to cast string '{$token}' to type '{$type}'"); } return $token; }
/** * Internally used to set the value of the property without any notification * of changes nor state change. * * @param mixed $value The value to set. * @param int|string $type PropertyType constant * @param boolean $constructor Whether this is called from the constructor. * * @see Property::setValue() * * @throws AccessDeniedException * @throws ItemNotFoundException * @throws LockException * @throws ConstraintViolationException * @throws RepositoryException * @throws VersionException * @throws InvalidArgumentException * @throws ValueFormatException * * @private */ public function _setValue($value, $type = PropertyType::UNDEFINED, $constructor = false) { if (null === $value) { $this->remove(); return; } if (is_string($type)) { $type = PropertyType::valueFromName($type); } elseif (!is_numeric($type)) { // @codeCoverageIgnoreStart throw new RepositoryException("INTERNAL ERROR -- No valid type specified ({$type})"); // @codeCoverageIgnoreEnd } else { //sanity check. this will throw InvalidArgumentException if $type is not a valid type PropertyType::nameFromValue($type); } if ($constructor || $this->isNew()) { $this->isMultiple = is_array($value); } if (is_array($value) && !$this->isMultiple) { throw new ValueFormatException('Can not set a single value property (' . $this->name . ') with an array of values'); } if ($this->isMultiple && is_scalar($value)) { throw new ValueFormatException('Can not set a multivalue property (' . $this->name . ') with a scalar value'); } if ($this->isMultiple) { foreach ($value as $key => $v) { if (null === $v) { unset($value[$key]); } } } //TODO: check if changing type allowed. /* * if ($type !== null && ! canHaveType($type)) { * throw new ConstraintViolationException("Can not set this property to type ".PropertyType::nameFromValue($type)); * } */ if (PropertyType::UNDEFINED === $type) { // avoid changing type of multivalue property with empty array if (!$this->isMultiple() || count($value) || PropertyType::UNDEFINED === $this->type) { $type = $this->valueConverter->determineType($value); } else { $type = $this->type; } } $targetType = $type; /* * TODO: find out with node type definition if the new type is allowed if ($this->type !== $type) { if (!canHaveType($type)) { //convert to an allowed type } } */ if (PropertyType::BINARY !== $targetType || $constructor && $this->isNew() || !$this->isNew() && PropertyType::UNDEFINED !== $this->type && $this->type !== $targetType) { $value = $this->valueConverter->convertType($value, $targetType, $constructor ? PropertyType::UNDEFINED : $type); } if (PropertyType::BINARY === $targetType) { if ($constructor && !$this->isNew()) { // reading a binary property from backend, we do not get the stream immediately but just the size if (is_array($value)) { $this->isMultiple = true; } $this->type = PropertyType::BINARY; $this->length = $value; $this->value = null; return; } if (is_array($value)) { $this->length = array(); foreach ($value as $v) { $stat = is_resource($v) ? fstat($v) : array('size' => -1); $this->length[] = $stat['size']; } } elseif (is_resource($value)) { $stat = fstat($value); $this->length = $stat['size']; } else { $this->length = -1; } } $this->type = $targetType; $this->value = $value; }
/** * Map a property from the given property XML node to the given \stdClass node representation * * @param \DOMElement $propertyNode * @param \stdClass $data */ protected function mapPropertyFromElement(\DOMElement $propertyNode, \stdClass $data) { $name = $propertyNode->getAttribute('sv:name'); $values = array(); $type = PropertyType::valueFromName($propertyNode->getAttribute('sv:type')); foreach ($propertyNode->childNodes as $valueNode) { switch ($type) { case PropertyType::NAME: case PropertyType::URI: case PropertyType::WEAKREFERENCE: case PropertyType::REFERENCE: case PropertyType::PATH: case PropertyType::DECIMAL: case PropertyType::STRING: $values[] = $valueNode->nodeValue; break; case PropertyType::BOOLEAN: $values[] = (bool) $valueNode->nodeValue; break; case PropertyType::LONG: $values[] = (int) $valueNode->nodeValue; break; case PropertyType::BINARY: $values[] = (int) $valueNode->nodeValue; break; case PropertyType::DATE: $date = $valueNode->nodeValue; if ($date) { $date = new \DateTime($date); $date->setTimezone(new \DateTimeZone(date_default_timezone_get())); // Jackalope expects a string, might make sense to refactor to allow \DateTime instances too $date = $this->valueConverter->convertType($date, PropertyType::STRING); } $values[] = $date; break; case PropertyType::DOUBLE: $values[] = (double) $valueNode->nodeValue; break; default: throw new \InvalidArgumentException("Type with constant {$type} not found."); } } switch ($type) { case PropertyType::BINARY: $data->{':' . $name} = $propertyNode->getAttribute('sv:multi-valued') ? $values : $values[0]; break; default: $data->{$name} = $propertyNode->getAttribute('sv:multi-valued') ? $values : $values[0]; $data->{':' . $name} = $type; break; } }
/** * @expectedException \InvalidArgumentException */ public function testValueFromNameInvalid() { PropertyType::valueFromName('Notexisting'); }
/** * The property type is delimited by parentheses ('*' is a synonym for UNDEFINED). * If this element is absent, STRING is assumed. A '?' indicates that this * attribute is a variant. * * PropertyType ::= '(' ('STRING' | 'BINARY' | 'LONG' | 'DOUBLE' | * 'BOOLEAN' | 'DATE' | 'NAME' | 'PATH' | * 'REFERENCE' | 'WEAKREFERENCE' | * 'DECIMAL' | 'URI' | 'UNDEFINED' | '*' | * '?') ')' */ protected function parsePropertyType(PropertyDefinitionTemplateInterface $property) { $types = array("STRING", "BINARY", "LONG", "DOUBLE", "BOOLEAN", "DATE", "NAME", "PATH", "REFERENCE", "WEAKREFERENCE", "DECIMAL", "URI", "UNDEFINED", "*", "?"); if (!$this->checkTokenIn(Token::TK_IDENTIFIER, $types, true)) { throw new ParserException($this->tokenQueue, sprintf("Invalid property type: %s", $this->tokenQueue->get()->getData())); } $data = $this->tokenQueue->get()->getData(); $this->expectToken(Token::TK_SYMBOL, ')'); $property->setRequiredType(PropertyType::valueFromName($data)); }
/** * Get the value of a dcr:value node in the right format specified by the * dcr type. * * This uses PropertyType but takes into account the special case that * boolean false is encoded as string "false" which is otherwise true in php. * * <dcr:value dcr:type="Boolean">false</dcr:value> * * @param DOMElement $node a dcr:value xml element * @param string $attribute the attribute name * * @return mixed the node value converted to the specified type. */ private function getDcrValue(DOMElement $node) { $type = $node->getAttribute('dcr:type'); if (PropertyType::TYPENAME_BOOLEAN == $type && 'false' == $node->nodeValue) { return false; } return $this->valueConverter->convertType($node->nodeValue, PropertyType::valueFromName($type)); }
/** * 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)); } } } }
/** * 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); } } } }
/** * Import document in system view * * @param NodeInterface $parentNode * @param NamespaceRegistryInterface $ns * @param FilteredXMLReader $xml * @param int $uuidBehavior * @param array $namespaceMap hashmap of prefix => uri for namespaces in the document */ private static function importSystemView(NodeInterface $parentNode, NamespaceRegistryInterface $ns, FilteredXMLReader $xml, $uuidBehavior, $namespaceMap = array()) { while ($xml->moveToNextAttribute()) { if ('xmlns' == $xml->prefix) { try { $prefix = $ns->getPrefix($xml->value); } catch (NamespaceException $e) { $prefix = $xml->localName; $ns->registerNamespace($prefix, $xml->value); } // @codeCoverageIgnoreStart if ('jcr' == $prefix && 'jcr' != $xml->localName) { throw new RepositoryException('Can not handle a document where the {http://www.jcp.org/jcr/1.0} namespace is not mapped to jcr'); } if ('nt' == $prefix && 'nt' != $xml->localName) { throw new RepositoryException('Can not handle a document where the {http://www.jcp.org/jcr/nt/1.0} namespace is not mapped to nt'); } // @codeCoverageIgnoreEnd $namespaceMap[$xml->localName] = $prefix; } elseif (NamespaceRegistryInterface::NAMESPACE_SV == $xml->namespaceURI && 'name' == $xml->localName) { $nodename = $xml->value; } } if (!isset($nodename)) { throw new InvalidSerializedDataException('there was no sv:name attribute in an element'); } // now get jcr:primaryType if (!$xml->read()) { throw new InvalidSerializedDataException('missing information to create node'); } if ('property' != $xml->localName || NamespaceRegistryInterface::NAMESPACE_SV != $xml->namespaceURI) { throw new InvalidSerializedDataException('first child of node must be sv:property for jcr:primaryType. Found {' . $xml->namespaceURI . '}' . $xml->localName . '="' . $xml->value . '"' . $xml->nodeType); } if (!$xml->moveToAttributeNs('name', NamespaceRegistryInterface::NAMESPACE_SV) || 'jcr:primaryType' != $xml->value) { throw new InvalidSerializedDataException('first child of node must be sv:property for jcr:primaryType. Found {' . $xml->namespaceURI . '}' . $xml->localName . '="' . $xml->value . '"'); } $xml->read(); // value child of property jcr:primaryType $xml->read(); // text content $nodetype = $xml->value; $nodename = self::cleanNamespace($nodename, $namespaceMap); $properties = array(); $xml->read(); // </value> $xml->read(); // </property> $xml->read(); // next thing // read the properties of the node. they must come first. while (XMLReader::END_ELEMENT != $xml->nodeType && 'property' == $xml->localName) { $xml->moveToAttributeNs('name', NamespaceRegistryInterface::NAMESPACE_SV); $name = $xml->value; $xml->moveToAttributeNs('type', NamespaceRegistryInterface::NAMESPACE_SV); $type = PropertyType::valueFromName($xml->value); if ($xml->moveToAttributeNs('multiple', NamespaceRegistryInterface::NAMESPACE_SV)) { $multiple = strcasecmp($xml->value, 'true') === 0; } else { $multiple = false; } $values = array(); // go to the value child. if empty property, brings us to closing // property tag. if self-closing, brings us to the next property or // node closing tag $xml->read(); while ('value' == $xml->localName) { if ($xml->isEmptyElement) { $values[] = ''; } else { $xml->read(); if (XMLReader::END_ELEMENT == $xml->nodeType) { // this is an empty tag $values[] = ''; } else { $values[] = PropertyType::BINARY == $type ? base64_decode($xml->value) : $xml->value; $xml->read(); // consume the content } } $xml->read(); // consume closing tag } if (!$multiple && count($values) == 1) { $values = reset($values); // unbox if it does not need to be multivalue } $name = self::cleanNamespace($name, $namespaceMap); $properties[$name] = array('values' => $values, 'type' => $type); /* only consume closing property, but not the next element if we * had an empty multiple property with no value children at all * and don't consume the closing node tag after a self-closing * empty property */ if (XMLReader::END_ELEMENT == $xml->nodeType && 'property' == $xml->localName) { $xml->read(); } } $node = self::addNode($parentNode, $nodename, $nodetype, $properties, $uuidBehavior); // if there are child nodes, they all come after the properties while (XMLReader::END_ELEMENT != $xml->nodeType && 'node' == $xml->localName) { self::importSystemView($node, $ns, $xml, $uuidBehavior, $namespaceMap); } if (XMLReader::END_ELEMENT != $xml->nodeType) { throw new InvalidSerializedDataException('Unexpected element "' . $xml->localName . '" type "' . $xml->nodeType . '" with content "' . $xml->value . '" after ' . $node->getPath()); } $xml->read(); // </node> }