From 414ce0fd06461d26987719c3f29b6414f3a56119 Mon Sep 17 00:00:00 2001 From: Meritoo Date: Sat, 13 Apr 2019 21:38:06 +0200 Subject: [PATCH] Reflection > getPropertyValue() method > refactoring --- src/Utilities/Reflection.php | 333 ++++++++++++++++++++++------------- 1 file changed, 208 insertions(+), 125 deletions(-) diff --git a/src/Utilities/Reflection.php b/src/Utilities/Reflection.php index 66d5e28..d675b72 100644 --- a/src/Utilities/Reflection.php +++ b/src/Utilities/Reflection.php @@ -156,139 +156,43 @@ class Reflection } /** - * Returns value of given property. - * Looks for proper getter for the property. + * Returns value of given property * - * @param mixed $object Object that should contains given property + * @param mixed $source Object that should contains given property * @param string $property Name of the property that contains a value. It may be also multiple properties * dot-separated, e.g. "invoice.user.email". * @param bool $force (optional) If is set to true, try to retrieve value even if the object doesn't have * property. Otherwise - not. * @return mixed */ - public static function getPropertyValue($object, $property, $force = false) + public static function getPropertyValue($source, $property, $force = false) { - $value = null; - - /* - * Property is a dot-separated string? - * Let's find all values of the chain, of the dot-separated properties - */ if (Regex::contains($property, '.')) { - $exploded = explode('.', $property); + return self::getPropertyValueByPropertiesChain($source, $property, $force); + } - $property = $exploded[0]; - $object = self::getPropertyValue($object, $property, $force); + [ + $value, + $valueFound, + ] = self::getPropertyValueByReflectionProperty($source, $property); - /* - * Value of processed property from the chain is not null? - * Let's dig more and get proper value - * - * Required to avoid bug: - * \ReflectionObject::__construct() expects parameter 1 to be object, null given - * (...) - * 4. at \ReflectionObject->__construct (null) - * 5. at Reflection ::getPropertyValue (null, 'name', true) - * 6. at ListService->getItemValue (object(Deal), 'project.name', '0') - * - * while using "project.name" as property - $project has $name property ($project exists in the Deal class) - * and the $project equals null - * - * Meritoo - * 2016-11-07 - */ - if (null !== $object) { - unset($exploded[0]); + if (!$valueFound) { + [ + $value, + $valueFound, + ] = self::getPropertyValueByParentClasses($source, $property); + } - $property = implode('.', $exploded); - $value = self::getPropertyValue($object, $property, $force); - } - } else { - $className = self::getClassName($object); - $reflectionProperty = null; + if (!$valueFound && ($force || self::hasProperty($source, $property))) { + [ + $value, + $valueFound, + ] = self::getPropertyValueByGetter($source, $property); + } - /* - * 1st try: - * Use \ReflectionObject class - */ - try { - $reflectionProperty = new \ReflectionProperty($className, $property); - $value = $reflectionProperty->getValue($object); - } catch (\ReflectionException $exception) { - /* - * 2nd try: - * Look for the property in parent classes - */ - if (null === $reflectionProperty) { - $propertyFound = false; - $reflectionProperties = self::getProperties($object, null, true); - - foreach ($reflectionProperties as $reflectionProperty) { - if ($reflectionProperty->getName() === $property) { - $propertyFound = true; - break; - } - } - - if ($propertyFound && null !== $reflectionProperty) { - $reflectionProperty->setAccessible(true); - $value = $reflectionProperty->getValue($object); - $reflectionProperty->setAccessible(false); - } - } else { - /* - * 3rd try: - * Look for the get / has / is methods - */ - $class = new \ReflectionObject($object); - $valueFound = false; - - if ($force || $class->hasProperty($property)) { - $property = Inflector::classify($property); - - $getterPrefixes = [ - 'get', - 'has', - 'is', - ]; - - foreach ($getterPrefixes as $prefix) { - $getterName = sprintf('%s%s', $prefix, $property); - - if ($class->hasMethod($getterName)) { - $method = new \ReflectionMethod($object, $getterName); - - /* - * Getter is not accessible publicly? - * I have to skip it, to avoid an error like this: - * - * Call to protected method My\ExtraClass::getExtraProperty() from context 'My\ExtraClass' - */ - if ($method->isProtected() || $method->isPrivate()) { - continue; - } - - $value = $object->{$getterName}(); - $valueFound = true; - - break; - } - } - } - - if (!$valueFound) { - /* - * Oops, value of the property is still unknown - * - * 4th try: - * Let's modify accessibility of the property and try again to get value - */ - $reflectionProperty->setAccessible(true); - $value = $reflectionProperty->getValue($object); - $reflectionProperty->setAccessible(false); - } - } - } + if (!$valueFound) { + $byReflectionProperty = self::getPropertyValueByReflectionProperty($source, $property); + $value = $byReflectionProperty[0]; } return $value; @@ -460,9 +364,9 @@ class Reflection * constants. By default all properties are returned. * @param bool $includeParents (optional) If is set to true, properties of parent classes are * included (recursively). Otherwise - not. - * @return array|\ReflectionProperty + * @return \ReflectionProperty[] */ - public static function getProperties($source, $filter = null, $includeParents = false) + public static function getProperties($source, $filter = null, $includeParents = false): array { $className = self::getClassName($source); $reflection = new \ReflectionClass($className); @@ -693,15 +597,15 @@ class Reflection throw NotExistingPropertyException::create($object, $property); } - $notAccessible = $reflectionProperty->isPrivate() || $reflectionProperty->isProtected(); + $isPublic = $reflectionProperty->isPublic(); - if ($notAccessible) { + if (!$isPublic) { $reflectionProperty->setAccessible(true); } $reflectionProperty->setValue($object, $value); - if ($notAccessible) { + if (!$isPublic) { $reflectionProperty->setAccessible(false); } } @@ -741,4 +645,183 @@ class Reflection return substr($class, $pos + Proxy::MARKER_LENGTH + 2); } + + /** + * Returns value of given property using the property represented by reflection. + * If value cannot be fetched, makes the property accessible temporarily. + * + * @param mixed $object Object that should contains given property + * @param string $property Name of the property that contains a value + * @param null|\ReflectionProperty $reflectionProperty (optional) Property represented by reflection + * @return mixed + */ + private static function getPropertyValueByReflectionProperty( + $object, + string $property, + ?\ReflectionProperty $reflectionProperty = null + ) { + $value = null; + $valueFound = false; + $className = self::getClassName($object); + + try { + if (null === $reflectionProperty) { + $reflectionProperty = new \ReflectionProperty($className, $property); + } + + $value = $reflectionProperty->getValue($object); + $valueFound = true; + } catch (\ReflectionException $exception) { + } + + if (null !== $reflectionProperty) { + $reflectionProperty->setAccessible(true); + + $value = $reflectionProperty->getValue($object); + $valueFound = true; + + $reflectionProperty->setAccessible(false); + } + + return [ + $value, + $valueFound, + ]; + } + + /** + * Returns value of given property using getter of the property + * + * An array with 2 elements is returned: + * - value of given property + * - information if the value was found (because null may be returned) + * + * @param mixed $source Object that should contains given property + * @param string $property Name of the property that contains a value + * @return array + */ + private static function getPropertyValueByGetter($source, string $property): array + { + $value = null; + $valueFound = false; + + $reflectionObject = new \ReflectionObject($source); + $property = Inflector::classify($property); + + $gettersPrefixes = [ + 'get', + 'has', + 'is', + ]; + + foreach ($gettersPrefixes as $prefix) { + $getter = sprintf('%s%s', $prefix, $property); + + if ($reflectionObject->hasMethod($getter)) { + $method = new \ReflectionMethod($source, $getter); + + /* + * Getter is not accessible publicly? + * I have to skip it, to avoid an error like this: + * + * Call to protected method My\ExtraClass::getExtraProperty() from context 'My\ExtraClass' + */ + if ($method->isProtected() || $method->isPrivate()) { + continue; + } + + $value = $source->{$getter}(); + $valueFound = true; + + break; + } + } + + return [ + $value, + $valueFound, + ]; + } + + /** + * Returns value of given property using parent classes + * + * @param mixed $source Object that should contains given property + * @param string $property Name of the property that contains a value + * @return array + */ + private static function getPropertyValueByParentClasses($source, string $property): array + { + $properties = self::getProperties($source, null, true); + + if (empty($properties)) { + return [ + null, + false, + ]; + } + + foreach ($properties as $reflectionProperty) { + if ($reflectionProperty->getName() === $property) { + $byReflectionProperty = self::getPropertyValueByReflectionProperty( + $source, + $property, + $reflectionProperty + ); + + return [ + $byReflectionProperty[0], + true, + ]; + } + } + + return [ + null, + false, + ]; + } + + /** + * Returns value of given property represented as chain of properties + * + * @param mixed $source Object that should contains given property + * @param string $property Dot-separated properties, e.g. "invoice.user.email" + * @param bool $force (optional) If is set to true, try to retrieve value even if the object doesn't have + * property. Otherwise - not. + * @return mixed + */ + private static function getPropertyValueByPropertiesChain($source, $property, $force) + { + $exploded = explode('.', $property); + + $property = $exploded[0]; + $source = self::getPropertyValue($source, $property, $force); + + /* + * Value of processed property from the chain is not null? + * Let's dig more and get proper value + * + * Required to avoid bug: + * \ReflectionObject::__construct() expects parameter 1 to be object, null given + * (...) + * 4. at \ReflectionObject->__construct (null) + * 5. at Reflection ::getPropertyValue (null, 'name', true) + * 6. at ListService->getItemValue (object(Deal), 'project.name', '0') + * + * while using "project.name" as property - $project has $name property ($project exists in the Deal class) + * and the $project equals null + * + * Meritoo + * 2016-11-07 + */ + if (null !== $source) { + unset($exploded[0]); + $property = implode('.', $exploded); + + return self::getPropertyValue($source, $property, $force); + } + + return null; + } }