/** * @param mixed $value The unexpected value found while traversing property path * @param PropertyPathInterface $path The property path * @param int $pathIndex The property path index when the unexpected value was found */ public function __construct($value, $path, $pathIndex = null) { if (func_num_args() === 3 && $path instanceof PropertyPathInterface) { $message = sprintf('PropertyAccessor requires a graph of objects or arrays to operate on, ' . 'but it found type "%s" while trying to traverse path "%s" at property "%s".', gettype($value), (string) $path, $path->getElement($pathIndex)); } else { trigger_error('The ' . __CLASS__ . ' constructor now expects 3 arguments: the invalid property value, the ' . __NAMESPACE__ . '\\PropertyPathInterface object and the current index of the property path.', E_USER_DEPRECATED); $message = sprintf('Expected argument of type "%s", "%s" given', $path, is_object($value) ? get_class($value) : gettype($value)); } parent::__construct($message); }
/** * Reads the path from an object up to a given path index. * * @param object|array $objectOrArray The object or array to read from * @param PropertyPathInterface $propertyPath The property path to read * @param int $lastIndex The index up to which should be read * * @return array The values read in the path. * * @throws UnexpectedTypeException If a value within the path is neither object nor array. */ private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex) { if (!is_object($objectOrArray) && !is_array($objectOrArray)) { throw new UnexpectedTypeException($objectOrArray, 'object or array'); } $propertyValues = array(); for ($i = 0; $i < $lastIndex; ++$i) { $property = $propertyPath->getElement($i); $isIndex = $propertyPath->isIndex($i); // Create missing nested arrays on demand if ($isIndex && ($objectOrArray instanceof \ArrayAccess && !isset($objectOrArray[$property]) || is_array($objectOrArray) && !array_key_exists($property, $objectOrArray))) { $objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null; } if ($isIndex) { $propertyValue =& $this->readIndex($objectOrArray, $property); } else { $propertyValue =& $this->readProperty($objectOrArray, $property); } $objectOrArray =& $propertyValue[self::VALUE]; // the final value of the path must not be validated if ($i + 1 < $propertyPath->getLength() && !is_object($objectOrArray) && !is_array($objectOrArray)) { throw new UnexpectedTypeException($objectOrArray, 'object or array'); } $propertyValues[] =& $propertyValue; } return $propertyValues; }
/** * Reads a value of the given property from an object or array. * * @param array|object $object The object or array to read from * @param mixed $property The property or index to read * @param boolean $strict * @param PropertyPathInterface $propertyPath * @param int $propertyPathIndex * * @return mixed * * @throws Exception\NoSuchPropertyException if the property does not exist or is not public * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function &readValue(&$object, $property, $strict, PropertyPathInterface $propertyPath = null, $propertyPathIndex = null) { // Use an array instead of an object since performance is very crucial here $result = [self::VALUE => null, self::IS_REF => false]; if (is_array($object)) { if (isset($object[$property])) { $result[self::VALUE] =& $object[$property]; $result[self::IS_REF] = true; } elseif ($strict && !array_key_exists($property, $object)) { throw new Exception\NoSuchPropertyException(sprintf('The key "%s" does exist in an array.', $property)); } } elseif ($object instanceof \ArrayAccess) { if (isset($object[$property])) { $result[self::VALUE] = $object[$property]; } elseif ($strict) { $reflClass = new \ReflectionClass($object); throw new Exception\NoSuchPropertyException(sprintf('The key "%s" does exist in class "%s".', $property, $reflClass->name)); } } elseif (is_object($object)) { $reflClass = new \ReflectionClass($object); $camel = $this->camelize($property); if ($reflClass->hasMethod($getter = 'get' . $camel) && $reflClass->getMethod($getter)->isPublic()) { $result[self::VALUE] = $object->{$getter}(); } elseif ($reflClass->hasMethod($isser = 'is' . $camel) && $reflClass->getMethod($isser)->isPublic()) { $result[self::VALUE] = $object->{$isser}(); } elseif ($reflClass->hasMethod($hasser = 'has' . $camel) && $reflClass->getMethod($hasser)->isPublic()) { $result[self::VALUE] = $object->{$hasser}(); } elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) { if ($reflClass->hasMethod('__isset') && $reflClass->getMethod('__isset')->isPublic()) { if (!isset($object->{$property})) { throw new Exception\NoSuchPropertyException(sprintf('The property "%s" cannot be got by "__get" method ' . 'because "__isset" method returns false. Class "%s".', $property, $reflClass->name)); } } $result[self::VALUE] = $object->{$property}; } else { $hasProp = $reflClass->hasProperty($property); if ($hasProp && $reflClass->getProperty($property)->isPublic()) { $result[self::VALUE] =& $object->{$property}; $result[self::IS_REF] = true; } elseif (!$hasProp && property_exists($object, $property)) { // Needed to support \stdClass instances. We need to explicitly // exclude $hasProp, otherwise if in the previous clause // a *protected* property was found on the class, property_exists() // returns true, consequently the following line will result in a // fatal error. $result[self::VALUE] =& $object->{$property}; $result[self::IS_REF] = true; } elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) { // we call the getter and hope the __call do the job $result[self::VALUE] = $object->{$getter}(); } else { $methods = [$getter, $isser, $hasser, '__get']; if ($this->magicCall) { $methods[] = '__call'; } throw new Exception\NoSuchPropertyException(sprintf('Neither the property "%s" nor one of the methods "%s()" ' . 'exist and have public access in class "%s".', $property, implode('()", "', $methods), $reflClass->name)); } } } else { if ($propertyPath !== null && $propertyPathIndex !== null) { throw new Exception\NoSuchPropertyException(sprintf('PropertyAccessor requires a graph of objects or arrays to operate on, ' . 'but it found type "%s" while trying to traverse path "%s" at property "%s".', gettype($object), (string) $propertyPath, $propertyPath->getElements()[$propertyPathIndex])); } else { throw new Exception\NoSuchPropertyException(sprintf('Unexpected object type. Expected "array or object", "%s" given.', is_object($object) ? get_class($object) : gettype($object))); } } // Objects are always passed around by reference if (!$result[self::IS_REF] && is_object($result[self::VALUE])) { $result[self::IS_REF] = true; } return $result; }
/** * Reads the path from an object up to a given path index. * * @param array $zval The array containing the object or array to read from * @param PropertyPathInterface $propertyPath The property path to read * @param int $lastIndex The index up to which should be read * @param bool $ignoreInvalidIndices Whether to ignore invalid indices or throw an exception * * @return array The values read in the path. * * @throws UnexpectedTypeException If a value within the path is neither object nor array. * @throws NoSuchIndexException If a non-existing index is accessed */ private function readPropertiesUntil($zval, PropertyPathInterface $propertyPath, $lastIndex, $ignoreInvalidIndices = true) { if (!is_object($zval[self::VALUE]) && !is_array($zval[self::VALUE])) { throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, 0); } // Add the root object to the list $propertyValues = array($zval); for ($i = 0; $i < $lastIndex; ++$i) { $property = $propertyPath->getElement($i); $isIndex = $propertyPath->isIndex($i); if ($isIndex) { // Create missing nested arrays on demand if ($zval[self::VALUE] instanceof \ArrayAccess && !$zval[self::VALUE]->offsetExists($property) || is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !array_key_exists($property, $zval[self::VALUE])) { if (!$ignoreInvalidIndices) { if (!is_array($zval[self::VALUE])) { if (!$zval[self::VALUE] instanceof \Traversable) { throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s".', $property, (string) $propertyPath)); } $zval[self::VALUE] = iterator_to_array($zval[self::VALUE]); } throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s". Available indices are "%s".', $property, (string) $propertyPath, print_r(array_keys($zval[self::VALUE]), true))); } if ($i + 1 < $propertyPath->getLength()) { if (isset($zval[self::REF])) { $zval[self::VALUE][$property] = array(); $zval[self::REF] = $zval[self::VALUE]; } else { $zval[self::VALUE] = array($property => array()); } } } $zval = $this->readIndex($zval, $property); } else { $zval = $this->readProperty($zval, $property); } // the final value of the path must not be validated if ($i + 1 < $propertyPath->getLength() && !is_object($zval[self::VALUE]) && !is_array($zval[self::VALUE])) { throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, $i + 1); } if (isset($zval[self::REF]) && (0 === $i || isset($propertyValues[$i - 1][self::IS_REF_CHAINED]))) { // Set the IS_REF_CHAINED flag to true if: // current property is passed by reference and // it is the first element in the property path or // the IS_REF_CHAINED flag of its parent element is true // Basically, this flag is true only when the reference chain from the top element to current element is not broken $zval[self::IS_REF_CHAINED] = true; } $propertyValues[] = $zval; } return $propertyValues; }
/** * Reads the path from an object up to a given path index. * * @param object|array $objectOrArray The object or array to read from * @param PropertyPathInterface $propertyPath The property path to read * @param int $lastIndex The index up to which should be read * * @return array The values read in the path. * * @throws UnexpectedTypeException If a value within the path is neither object nor array. */ private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex, $ignoreInvalidIndices = true) { $propertyValues = array(); for ($i = 0; $i < $lastIndex; ++$i) { if (!is_object($objectOrArray) && !is_array($objectOrArray)) { throw new UnexpectedTypeException($objectOrArray, 'object or array'); } $property = $propertyPath->getElement($i); $isIndex = $propertyPath->isIndex($i); $isArrayAccess = is_array($objectOrArray) || $objectOrArray instanceof \ArrayAccess; // Create missing nested arrays on demand if ($isIndex && $isArrayAccess && !isset($objectOrArray[$property])) { if (!$ignoreInvalidIndices) { if (!is_array($objectOrArray)) { if (!$objectOrArray instanceof \Traversable) { throw new NoSuchIndexException(sprintf('Cannot read property "%s".', $property)); } $objectOrArray = iterator_to_array($objectOrArray); } throw new NoSuchIndexException(sprintf('Cannot read property "%s". Available properties are "%s"', $property, print_r(array_keys($objectOrArray), true))); } $objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null; } if ($isIndex) { $propertyValue =& $this->readIndex($objectOrArray, $property); } else { $propertyValue =& $this->readProperty($objectOrArray, $property); } $objectOrArray =& $propertyValue[self::VALUE]; $propertyValues[] =& $propertyValue; } return $propertyValues; }
/** * @param PropertyPathInterface $propertyPath * * @return string */ protected function compilePathIndexes(PropertyPathInterface $propertyPath) { $indexes = []; $count = count($propertyPath->getElements()); for ($i = 0; $i < $count; $i++) { $indexes[] = $propertyPath->isIndex($i); } return implode(', ', array_map(function ($val) { return $val ? 'true' : 'false'; }, $indexes)); }
/** * Reads the path from an object up to a given path index. * * @param object|array $objectOrArray The object or array to read from * @param PropertyPathInterface $propertyPath The property path to read * @param int $lastIndex The index up to which should be read * @param bool $ignoreInvalidIndices Whether to ignore invalid indices * or throw an exception * * @return array The values read in the path. * * @throws UnexpectedTypeException If a value within the path is neither object nor array. * @throws NoSuchIndexException If a non-existing index is accessed */ private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex, $ignoreInvalidIndices = true) { if (!is_object($objectOrArray) && !is_array($objectOrArray)) { throw new UnexpectedTypeException($objectOrArray, $propertyPath, 0); } $propertyValues = array(); for ($i = 0; $i < $lastIndex; ++$i) { $property = $propertyPath->getElement($i); $isIndex = $propertyPath->isIndex($i); // Create missing nested arrays on demand if ($isIndex && ($objectOrArray instanceof \ArrayAccess && !isset($objectOrArray[$property]) || is_array($objectOrArray) && !array_key_exists($property, $objectOrArray))) { if (!$ignoreInvalidIndices) { if (!is_array($objectOrArray)) { if (!$objectOrArray instanceof \Traversable) { throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s".', $property, (string) $propertyPath)); } $objectOrArray = iterator_to_array($objectOrArray); } throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s". Available indices are "%s".', $property, (string) $propertyPath, print_r(array_keys($objectOrArray), true))); } $objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null; } if ($isIndex) { $propertyValue =& $this->readIndex($objectOrArray, $property); } else { $propertyValue =& $this->readProperty($objectOrArray, $property); } $objectOrArray =& $propertyValue[self::VALUE]; // the final value of the path must not be validated if ($i + 1 < $propertyPath->getLength() && !is_object($objectOrArray) && !is_array($objectOrArray)) { throw new UnexpectedTypeException($objectOrArray, $propertyPath, $i + 1); } $propertyValues[] =& $propertyValue; } return $propertyValues; }
/** * Replaces a sub-path by a different (sub-) path. * * @param integer $offset The offset at which to replace. * @param integer $length The length of the piece to replace. * @param PropertyPathInterface $path The path to insert. * @param integer $pathOffset The offset where the inserted piece * starts in $path. * @param integer $pathLength The length of the inserted piece. * If 0, the full path is inserted. * * @throws OutOfBoundsException If the offset is invalid. */ public function replace($offset, $length, PropertyPathInterface $path, $pathOffset = 0, $pathLength = 0) { if (!isset($this->elements[$offset])) { throw new OutOfBoundsException('The offset ' . $offset . ' is not within the property path'); } if (0 === $pathLength) { $pathLength = $path->getLength() - $pathOffset; } $this->resize($offset, $length, $pathLength); for ($i = 0; $i < $pathLength; ++$i) { $this->elements[$offset + $i] = $path->getElement($pathOffset + $i); $this->isIndex[$offset + $i] = $path->isIndex($pathOffset + $i); } }
/** * @param \Symfony\Component\PropertyAccess\PropertyPathInterface $propertyPath * @return string */ private function getMostNestedProperty(PropertyPathInterface $propertyPath) { return $propertyPath->getElement($propertyPath->getLength() - 1); }
/** * @param mixed $value The unexpected value found while traversing property path * @param PropertyPathInterface $path The property path * @param int $pathIndex The property path index when the unexpected value was found */ public function __construct($value, PropertyPathInterface $path, $pathIndex) { $message = sprintf('PropertyAccessor requires a graph of objects or arrays to operate on, ' . 'but it found type "%s" while trying to traverse path "%s" at property "%s".', gettype($value), (string) $path, $path->getElement($pathIndex)); parent::__construct($message); }