Yaffs site version 1.1
[yaffs-website] / vendor / symfony / serializer / Normalizer / ObjectNormalizer.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Serializer\Normalizer;
13
14 use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
15 use Symfony\Component\PropertyAccess\PropertyAccess;
16 use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
17 use Symfony\Component\Serializer\Exception\CircularReferenceException;
18 use Symfony\Component\Serializer\Exception\LogicException;
19 use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
20 use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
21
22 /**
23  * Converts between objects and arrays using the PropertyAccess component.
24  *
25  * @author Kévin Dunglas <dunglas@gmail.com>
26  */
27 class ObjectNormalizer extends AbstractNormalizer
28 {
29     private $attributesCache = array();
30
31     /**
32      * @var PropertyAccessorInterface
33      */
34     protected $propertyAccessor;
35
36     public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null)
37     {
38         parent::__construct($classMetadataFactory, $nameConverter);
39
40         $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
41     }
42
43     /**
44      * {@inheritdoc}
45      */
46     public function supportsNormalization($data, $format = null)
47     {
48         return is_object($data) && !$data instanceof \Traversable;
49     }
50
51     /**
52      * {@inheritdoc}
53      *
54      * @throws CircularReferenceException
55      */
56     public function normalize($object, $format = null, array $context = array())
57     {
58         if (!isset($context['cache_key'])) {
59             $context['cache_key'] = $this->getCacheKey($context);
60         }
61         if ($this->isCircularReference($object, $context)) {
62             return $this->handleCircularReference($object);
63         }
64
65         $data = array();
66         $attributes = $this->getAttributes($object, $context);
67
68         foreach ($attributes as $attribute) {
69             if (in_array($attribute, $this->ignoredAttributes)) {
70                 continue;
71             }
72
73             $attributeValue = $this->propertyAccessor->getValue($object, $attribute);
74
75             if (isset($this->callbacks[$attribute])) {
76                 $attributeValue = call_user_func($this->callbacks[$attribute], $attributeValue);
77             }
78
79             if (null !== $attributeValue && !is_scalar($attributeValue)) {
80                 if (!$this->serializer instanceof NormalizerInterface) {
81                     throw new LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $attribute));
82                 }
83
84                 $attributeValue = $this->serializer->normalize($attributeValue, $format, $context);
85             }
86
87             if ($this->nameConverter) {
88                 $attribute = $this->nameConverter->normalize($attribute);
89             }
90
91             $data[$attribute] = $attributeValue;
92         }
93
94         return $data;
95     }
96
97     /**
98      * {@inheritdoc}
99      */
100     public function supportsDenormalization($data, $type, $format = null)
101     {
102         return class_exists($type);
103     }
104
105     /**
106      * {@inheritdoc}
107      */
108     public function denormalize($data, $class, $format = null, array $context = array())
109     {
110         if (!isset($context['cache_key'])) {
111             $context['cache_key'] = $this->getCacheKey($context);
112         }
113         $allowedAttributes = $this->getAllowedAttributes($class, $context, true);
114         $normalizedData = $this->prepareForDenormalization($data);
115
116         $reflectionClass = new \ReflectionClass($class);
117         $object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes);
118
119         foreach ($normalizedData as $attribute => $value) {
120             if ($this->nameConverter) {
121                 $attribute = $this->nameConverter->denormalize($attribute);
122             }
123
124             $allowed = $allowedAttributes === false || in_array($attribute, $allowedAttributes);
125             $ignored = in_array($attribute, $this->ignoredAttributes);
126
127             if ($allowed && !$ignored) {
128                 try {
129                     $this->propertyAccessor->setValue($object, $attribute, $value);
130                 } catch (NoSuchPropertyException $exception) {
131                     // Properties not found are ignored
132                 }
133             }
134         }
135
136         return $object;
137     }
138
139     private function getCacheKey(array $context)
140     {
141         try {
142             return md5(serialize($context));
143         } catch (\Exception $exception) {
144             // The context cannot be serialized, skip the cache
145             return false;
146         }
147     }
148
149     /**
150      * Gets and caches attributes for this class and context.
151      *
152      * @param object $object
153      * @param array  $context
154      *
155      * @return string[]
156      */
157     private function getAttributes($object, array $context)
158     {
159         $class = get_class($object);
160         $key = $class.'-'.$context['cache_key'];
161
162         if (isset($this->attributesCache[$key])) {
163             return $this->attributesCache[$key];
164         }
165
166         $allowedAttributes = $this->getAllowedAttributes($object, $context, true);
167
168         if (false !== $allowedAttributes) {
169             if ($context['cache_key']) {
170                 $this->attributesCache[$key] = $allowedAttributes;
171             }
172
173             return $allowedAttributes;
174         }
175
176         if (isset($this->attributesCache[$class])) {
177             return $this->attributesCache[$class];
178         }
179
180         return $this->attributesCache[$class] = $this->extractAttributes($object);
181     }
182
183     /**
184      * Extracts attributes for this class and context.
185      *
186      * @param object $object
187      *
188      * @return string[]
189      */
190     private function extractAttributes($object)
191     {
192         // If not using groups, detect manually
193         $attributes = array();
194
195         // methods
196         $reflClass = new \ReflectionClass($object);
197         foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
198             if (
199                 $reflMethod->getNumberOfRequiredParameters() !== 0 ||
200                 $reflMethod->isStatic() ||
201                 $reflMethod->isConstructor() ||
202                 $reflMethod->isDestructor()
203             ) {
204                 continue;
205             }
206
207             $name = $reflMethod->name;
208
209             if (0 === strpos($name, 'get') || 0 === strpos($name, 'has')) {
210                 // getters and hassers
211                 $propertyName = substr($name, 3);
212
213                 if (!$reflClass->hasProperty($propertyName)) {
214                     $propertyName = lcfirst($propertyName);
215                 }
216
217                 $attributes[$propertyName] = true;
218             } elseif (strpos($name, 'is') === 0) {
219                 // issers
220                 $propertyName = substr($name, 2);
221
222                 if (!$reflClass->hasProperty($propertyName)) {
223                     $propertyName = lcfirst($propertyName);
224                 }
225
226                 $attributes[$propertyName] = true;
227             }
228         }
229
230         // properties
231         foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) {
232             if ($reflProperty->isStatic()) {
233                 continue;
234             }
235
236             $attributes[$reflProperty->name] = true;
237         }
238
239         return array_keys($attributes);
240     }
241 }