/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php

https://github.com/r1pp3rj4ck/symfony · PHP · 228 lines · 120 code · 28 blank · 80 comment · 13 complexity · 65ba8dbdc7f92395710740b470f35fdc MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Serializer\Normalizer;
  11. use Symfony\Component\Serializer\Exception\InvalidArgumentException;
  12. use Symfony\Component\Serializer\Exception\RuntimeException;
  13. /**
  14. * Converts between objects with getter and setter methods and arrays.
  15. *
  16. * The normalization process looks at all public methods and calls the ones
  17. * which have a name starting with get and take no parameters. The result is a
  18. * map from property names (method name stripped of the get prefix and converted
  19. * to lower case) to property values. Property values are normalized through the
  20. * serializer.
  21. *
  22. * The denormalization first looks at the constructor of the given class to see
  23. * if any of the parameters have the same name as one of the properties. The
  24. * constructor is then called with all parameters or an exception is thrown if
  25. * any required parameters were not present as properties. Then the denormalizer
  26. * walks through the given map of property names to property values to see if a
  27. * setter method exists for any of the properties. If a setter exists it is
  28. * called with the property value. No automatic denormalization of the value
  29. * takes place.
  30. *
  31. * @author Nils Adermann <naderman@naderman.de>
  32. */
  33. class GetSetMethodNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface
  34. {
  35. protected $callbacks = array();
  36. protected $ignoredAttributes = array();
  37. protected $camelizedAttributes = array();
  38. /**
  39. * Set normalization callbacks.
  40. *
  41. * @param callable[] $callbacks help normalize the result
  42. *
  43. * @throws InvalidArgumentException if a non-callable callback is set
  44. */
  45. public function setCallbacks(array $callbacks)
  46. {
  47. foreach ($callbacks as $attribute => $callback) {
  48. if (!is_callable($callback)) {
  49. throw new InvalidArgumentException(sprintf('The given callback for attribute "%s" is not callable.', $attribute));
  50. }
  51. }
  52. $this->callbacks = $callbacks;
  53. }
  54. /**
  55. * Set ignored attributes for normalization
  56. *
  57. * @param array $ignoredAttributes
  58. */
  59. public function setIgnoredAttributes(array $ignoredAttributes)
  60. {
  61. $this->ignoredAttributes = $ignoredAttributes;
  62. }
  63. /**
  64. * Set attributes to be camelized on denormalize
  65. *
  66. * @param array $camelizedAttributes
  67. */
  68. public function setCamelizedAttributes(array $camelizedAttributes)
  69. {
  70. $this->camelizedAttributes = $camelizedAttributes;
  71. }
  72. /**
  73. * {@inheritdoc}
  74. */
  75. public function normalize($object, $format = null, array $context = array())
  76. {
  77. $reflectionObject = new \ReflectionObject($object);
  78. $reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC);
  79. $attributes = array();
  80. foreach ($reflectionMethods as $method) {
  81. if ($this->isGetMethod($method)) {
  82. $attributeName = lcfirst(substr($method->name, 3));
  83. if (in_array($attributeName, $this->ignoredAttributes)) {
  84. continue;
  85. }
  86. $attributeValue = $method->invoke($object);
  87. if (array_key_exists($attributeName, $this->callbacks)) {
  88. $attributeValue = call_user_func($this->callbacks[$attributeName], $attributeValue);
  89. }
  90. if (null !== $attributeValue && !is_scalar($attributeValue)) {
  91. $attributeValue = $this->serializer->normalize($attributeValue, $format);
  92. }
  93. $attributes[$attributeName] = $attributeValue;
  94. }
  95. }
  96. return $attributes;
  97. }
  98. /**
  99. * {@inheritdoc}
  100. */
  101. public function denormalize($data, $class, $format = null, array $context = array())
  102. {
  103. $reflectionClass = new \ReflectionClass($class);
  104. $constructor = $reflectionClass->getConstructor();
  105. if ($constructor) {
  106. $constructorParameters = $constructor->getParameters();
  107. $params = array();
  108. foreach ($constructorParameters as $constructorParameter) {
  109. $paramName = lcfirst($this->formatAttribute($constructorParameter->name));
  110. if (isset($data[$paramName])) {
  111. $params[] = $data[$paramName];
  112. // don't run set for a parameter passed to the constructor
  113. unset($data[$paramName]);
  114. } elseif (!$constructorParameter->isOptional()) {
  115. throw new RuntimeException(
  116. 'Cannot create an instance of '.$class.
  117. ' from serialized data because its constructor requires '.
  118. 'parameter "'.$constructorParameter->name.
  119. '" to be present.');
  120. }
  121. }
  122. $object = $reflectionClass->newInstanceArgs($params);
  123. } else {
  124. $object = new $class;
  125. }
  126. foreach ($data as $attribute => $value) {
  127. $setter = 'set'.$this->formatAttribute($attribute);
  128. if (method_exists($object, $setter)) {
  129. $object->$setter($value);
  130. }
  131. }
  132. return $object;
  133. }
  134. /**
  135. * Format attribute name to access parameters or methods
  136. * As option, if attribute name is found on camelizedAttributes array
  137. * returns attribute name in camelcase format
  138. *
  139. * @param string $attributeName
  140. * @return string
  141. */
  142. protected function formatAttribute($attributeName)
  143. {
  144. if (in_array($attributeName, $this->camelizedAttributes)) {
  145. return preg_replace_callback(
  146. '/(^|_|\.)+(.)/', function ($match) {
  147. return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
  148. }, $attributeName
  149. );
  150. }
  151. return $attributeName;
  152. }
  153. /**
  154. * {@inheritDoc}
  155. */
  156. public function supportsNormalization($data, $format = null)
  157. {
  158. return is_object($data) && $this->supports(get_class($data));
  159. }
  160. /**
  161. * {@inheritDoc}
  162. */
  163. public function supportsDenormalization($data, $type, $format = null)
  164. {
  165. return $this->supports($type);
  166. }
  167. /**
  168. * Checks if the given class has any get{Property} method.
  169. *
  170. * @param string $class
  171. *
  172. * @return Boolean
  173. */
  174. private function supports($class)
  175. {
  176. $class = new \ReflectionClass($class);
  177. $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
  178. foreach ($methods as $method) {
  179. if ($this->isGetMethod($method)) {
  180. return true;
  181. }
  182. }
  183. return false;
  184. }
  185. /**
  186. * Checks if a method's name is get.* and can be called without parameters.
  187. *
  188. * @param \ReflectionMethod $method the method to check
  189. *
  190. * @return Boolean whether the method is a getter.
  191. */
  192. private function isGetMethod(\ReflectionMethod $method)
  193. {
  194. return (
  195. 0 === strpos($method->name, 'get') &&
  196. 3 < strlen($method->name) &&
  197. 0 === $method->getNumberOfRequiredParameters()
  198. );
  199. }
  200. }