ObjectNormalizer.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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\PropertyAccess\Exception\NoSuchPropertyException;
  12. use Symfony\Component\PropertyAccess\PropertyAccess;
  13. use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
  14. use Symfony\Component\Serializer\Exception\CircularReferenceException;
  15. use Symfony\Component\Serializer\Exception\LogicException;
  16. use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
  17. use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
  18. /**
  19. * Converts between objects and arrays using the PropertyAccess component.
  20. *
  21. * @author Kévin Dunglas <dunglas@gmail.com>
  22. */
  23. class ObjectNormalizer extends AbstractNormalizer
  24. {
  25. private $attributesCache = array();
  26. /**
  27. * @var PropertyAccessorInterface
  28. */
  29. protected $propertyAccessor;
  30. public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null)
  31. {
  32. parent::__construct($classMetadataFactory, $nameConverter);
  33. $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
  34. }
  35. /**
  36. * {@inheritdoc}
  37. */
  38. public function supportsNormalization($data, $format = null)
  39. {
  40. return is_object($data) && !$data instanceof \Traversable;
  41. }
  42. /**
  43. * {@inheritdoc}
  44. *
  45. * @throws CircularReferenceException
  46. */
  47. public function normalize($object, $format = null, array $context = array())
  48. {
  49. if (!isset($context['cache_key'])) {
  50. $context['cache_key'] = $this->getCacheKey($context);
  51. }
  52. if ($this->isCircularReference($object, $context)) {
  53. return $this->handleCircularReference($object);
  54. }
  55. $data = array();
  56. $attributes = $this->getAttributes($object, $context);
  57. foreach ($attributes as $attribute) {
  58. if (in_array($attribute, $this->ignoredAttributes)) {
  59. continue;
  60. }
  61. $attributeValue = $this->propertyAccessor->getValue($object, $attribute);
  62. if (isset($this->callbacks[$attribute])) {
  63. $attributeValue = call_user_func($this->callbacks[$attribute], $attributeValue);
  64. }
  65. if (null !== $attributeValue && !is_scalar($attributeValue)) {
  66. if (!$this->serializer instanceof NormalizerInterface) {
  67. throw new LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $attribute));
  68. }
  69. $attributeValue = $this->serializer->normalize($attributeValue, $format, $context);
  70. }
  71. if ($this->nameConverter) {
  72. $attribute = $this->nameConverter->normalize($attribute);
  73. }
  74. $data[$attribute] = $attributeValue;
  75. }
  76. return $data;
  77. }
  78. /**
  79. * {@inheritdoc}
  80. */
  81. public function supportsDenormalization($data, $type, $format = null)
  82. {
  83. return class_exists($type);
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function denormalize($data, $class, $format = null, array $context = array())
  89. {
  90. if (!isset($context['cache_key'])) {
  91. $context['cache_key'] = $this->getCacheKey($context);
  92. }
  93. $allowedAttributes = $this->getAllowedAttributes($class, $context, true);
  94. $normalizedData = $this->prepareForDenormalization($data);
  95. $reflectionClass = new \ReflectionClass($class);
  96. $object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes);
  97. foreach ($normalizedData as $attribute => $value) {
  98. if ($this->nameConverter) {
  99. $attribute = $this->nameConverter->denormalize($attribute);
  100. }
  101. $allowed = $allowedAttributes === false || in_array($attribute, $allowedAttributes);
  102. $ignored = in_array($attribute, $this->ignoredAttributes);
  103. if ($allowed && !$ignored) {
  104. try {
  105. $this->propertyAccessor->setValue($object, $attribute, $value);
  106. } catch (NoSuchPropertyException $exception) {
  107. // Properties not found are ignored
  108. }
  109. }
  110. }
  111. return $object;
  112. }
  113. private function getCacheKey(array $context)
  114. {
  115. try {
  116. return md5(serialize($context));
  117. } catch (\Exception $exception) {
  118. // The context cannot be serialized, skip the cache
  119. return false;
  120. }
  121. }
  122. /**
  123. * Gets and caches attributes for this class and context.
  124. *
  125. * @param object $object
  126. * @param array $context
  127. *
  128. * @return string[]
  129. */
  130. private function getAttributes($object, array $context)
  131. {
  132. $class = get_class($object);
  133. $key = $class.'-'.$context['cache_key'];
  134. if (isset($this->attributesCache[$key])) {
  135. return $this->attributesCache[$key];
  136. }
  137. $allowedAttributes = $this->getAllowedAttributes($object, $context, true);
  138. if (false !== $allowedAttributes) {
  139. if ($context['cache_key']) {
  140. $this->attributesCache[$key] = $allowedAttributes;
  141. }
  142. return $allowedAttributes;
  143. }
  144. if (isset($this->attributesCache[$class])) {
  145. return $this->attributesCache[$class];
  146. }
  147. return $this->attributesCache[$class] = $this->extractAttributes($object);
  148. }
  149. /**
  150. * Extracts attributes for this class and context.
  151. *
  152. * @param object $object
  153. *
  154. * @return string[]
  155. */
  156. private function extractAttributes($object)
  157. {
  158. // If not using groups, detect manually
  159. $attributes = array();
  160. // methods
  161. $reflClass = new \ReflectionClass($object);
  162. foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
  163. if (
  164. $reflMethod->getNumberOfRequiredParameters() !== 0 ||
  165. $reflMethod->isStatic() ||
  166. $reflMethod->isConstructor() ||
  167. $reflMethod->isDestructor()
  168. ) {
  169. continue;
  170. }
  171. $name = $reflMethod->name;
  172. if (0 === strpos($name, 'get') || 0 === strpos($name, 'has')) {
  173. // getters and hassers
  174. $attributes[lcfirst(substr($name, 3))] = true;
  175. } elseif (strpos($name, 'is') === 0) {
  176. // issers
  177. $attributes[lcfirst(substr($name, 2))] = true;
  178. }
  179. }
  180. // properties
  181. foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) {
  182. if ($reflProperty->isStatic()) {
  183. continue;
  184. }
  185. $attributes[$reflProperty->name] = true;
  186. }
  187. return array_keys($attributes);
  188. }
  189. }