/** * 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; }
/** * @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)); }