Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Serializer\Normalizer; Chris@0: Chris@0: /** Chris@0: * Converts between objects with getter and setter methods and arrays. Chris@0: * Chris@0: * The normalization process looks at all public methods and calls the ones Chris@0: * which have a name starting with get and take no parameters. The result is a Chris@0: * map from property names (method name stripped of the get prefix and converted Chris@0: * to lower case) to property values. Property values are normalized through the Chris@0: * serializer. Chris@0: * Chris@0: * The denormalization first looks at the constructor of the given class to see Chris@0: * if any of the parameters have the same name as one of the properties. The Chris@0: * constructor is then called with all parameters or an exception is thrown if Chris@0: * any required parameters were not present as properties. Then the denormalizer Chris@0: * walks through the given map of property names to property values to see if a Chris@0: * setter method exists for any of the properties. If a setter exists it is Chris@0: * called with the property value. No automatic denormalization of the value Chris@0: * takes place. Chris@0: * Chris@0: * @author Nils Adermann Chris@0: * @author Kévin Dunglas Chris@0: */ Chris@0: class GetSetMethodNormalizer extends AbstractObjectNormalizer Chris@0: { Chris@17: private static $setterAccessibleCache = []; Chris@17: private $cache = []; Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function supportsNormalization($data, $format = null) Chris@0: { Chris@14: return parent::supportsNormalization($data, $format) && (isset($this->cache[$type = \get_class($data)]) ? $this->cache[$type] : $this->cache[$type] = $this->supports($type)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function supportsDenormalization($data, $type, $format = null) Chris@0: { Chris@14: return parent::supportsDenormalization($data, $type, $format) && (isset($this->cache[$type]) ? $this->cache[$type] : $this->cache[$type] = $this->supports($type)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks if the given class has any get{Property} method. Chris@0: * Chris@0: * @param string $class Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: private function supports($class) Chris@0: { Chris@0: $class = new \ReflectionClass($class); Chris@0: $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); Chris@0: foreach ($methods as $method) { Chris@0: if ($this->isGetMethod($method)) { Chris@0: return true; Chris@0: } Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks if a method's name is get.* or is.*, and can be called without parameters. Chris@0: * Chris@0: * @return bool whether the method is a getter or boolean getter Chris@0: */ Chris@0: private function isGetMethod(\ReflectionMethod $method) Chris@0: { Chris@14: $methodLength = \strlen($method->name); Chris@0: Chris@0: return Chris@0: !$method->isStatic() && Chris@0: ( Chris@0: ((0 === strpos($method->name, 'get') && 3 < $methodLength) || Chris@14: (0 === strpos($method->name, 'is') && 2 < $methodLength) || Chris@14: (0 === strpos($method->name, 'has') && 3 < $methodLength)) && Chris@0: 0 === $method->getNumberOfRequiredParameters() Chris@0: ) Chris@0: ; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: protected function extractAttributes($object, $format = null, array $context = []) Chris@0: { Chris@0: $reflectionObject = new \ReflectionObject($object); Chris@0: $reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC); Chris@0: Chris@17: $attributes = []; Chris@0: foreach ($reflectionMethods as $method) { Chris@0: if (!$this->isGetMethod($method)) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: $attributeName = lcfirst(substr($method->name, 0 === strpos($method->name, 'is') ? 2 : 3)); Chris@0: Chris@18: if ($this->isAllowedAttribute($object, $attributeName, $format, $context)) { Chris@0: $attributes[] = $attributeName; Chris@0: } Chris@0: } Chris@0: Chris@0: return $attributes; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: protected function getAttributeValue($object, $attribute, $format = null, array $context = []) Chris@0: { Chris@0: $ucfirsted = ucfirst($attribute); Chris@0: Chris@0: $getter = 'get'.$ucfirsted; Chris@17: if (\is_callable([$object, $getter])) { Chris@0: return $object->$getter(); Chris@0: } Chris@0: Chris@0: $isser = 'is'.$ucfirsted; Chris@17: if (\is_callable([$object, $isser])) { Chris@0: return $object->$isser(); Chris@0: } Chris@14: Chris@14: $haser = 'has'.$ucfirsted; Chris@17: if (\is_callable([$object, $haser])) { Chris@14: return $object->$haser(); Chris@14: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = []) Chris@0: { Chris@0: $setter = 'set'.ucfirst($attribute); Chris@14: $key = \get_class($object).':'.$setter; Chris@0: Chris@0: if (!isset(self::$setterAccessibleCache[$key])) { Chris@17: self::$setterAccessibleCache[$key] = \is_callable([$object, $setter]) && !(new \ReflectionMethod($object, $setter))->isStatic(); Chris@0: } Chris@0: Chris@0: if (self::$setterAccessibleCache[$key]) { Chris@0: $object->$setter($value); Chris@0: } Chris@0: } Chris@0: }