Security update for Core, with self-updated composer
[yaffs-website] / vendor / symfony / serializer / Normalizer / AbstractNormalizer.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\Serializer\Exception\CircularReferenceException;
15 use Symfony\Component\Serializer\Exception\InvalidArgumentException;
16 use Symfony\Component\Serializer\Exception\LogicException;
17 use Symfony\Component\Serializer\Exception\RuntimeException;
18 use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
19 use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
20 use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
21 use Symfony\Component\Serializer\SerializerAwareInterface;
22
23 /**
24  * Normalizer implementation.
25  *
26  * @author Kévin Dunglas <dunglas@gmail.com>
27  */
28 abstract class AbstractNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface
29 {
30     const CIRCULAR_REFERENCE_LIMIT = 'circular_reference_limit';
31     const OBJECT_TO_POPULATE = 'object_to_populate';
32     const GROUPS = 'groups';
33
34     /**
35      * @var int
36      */
37     protected $circularReferenceLimit = 1;
38
39     /**
40      * @var callable
41      */
42     protected $circularReferenceHandler;
43
44     /**
45      * @var ClassMetadataFactoryInterface|null
46      */
47     protected $classMetadataFactory;
48
49     /**
50      * @var NameConverterInterface|null
51      */
52     protected $nameConverter;
53
54     /**
55      * @var array
56      */
57     protected $callbacks = array();
58
59     /**
60      * @var array
61      */
62     protected $ignoredAttributes = array();
63
64     /**
65      * @var array
66      */
67     protected $camelizedAttributes = array();
68
69     /**
70      * Sets the {@link ClassMetadataFactoryInterface} to use.
71      *
72      * @param ClassMetadataFactoryInterface|null $classMetadataFactory
73      * @param NameConverterInterface|null        $nameConverter
74      */
75     public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null)
76     {
77         $this->classMetadataFactory = $classMetadataFactory;
78         $this->nameConverter = $nameConverter;
79     }
80
81     /**
82      * Set circular reference limit.
83      *
84      * @param int $circularReferenceLimit limit of iterations for the same object
85      *
86      * @return self
87      */
88     public function setCircularReferenceLimit($circularReferenceLimit)
89     {
90         $this->circularReferenceLimit = $circularReferenceLimit;
91
92         return $this;
93     }
94
95     /**
96      * Set circular reference handler.
97      *
98      * @param callable $circularReferenceHandler
99      *
100      * @return self
101      */
102     public function setCircularReferenceHandler(callable $circularReferenceHandler)
103     {
104         $this->circularReferenceHandler = $circularReferenceHandler;
105
106         return $this;
107     }
108
109     /**
110      * Set normalization callbacks.
111      *
112      * @param callable[] $callbacks help normalize the result
113      *
114      * @return self
115      *
116      * @throws InvalidArgumentException if a non-callable callback is set
117      */
118     public function setCallbacks(array $callbacks)
119     {
120         foreach ($callbacks as $attribute => $callback) {
121             if (!is_callable($callback)) {
122                 throw new InvalidArgumentException(sprintf(
123                     'The given callback for attribute "%s" is not callable.',
124                     $attribute
125                 ));
126             }
127         }
128         $this->callbacks = $callbacks;
129
130         return $this;
131     }
132
133     /**
134      * Set ignored attributes for normalization and denormalization.
135      *
136      * @param array $ignoredAttributes
137      *
138      * @return self
139      */
140     public function setIgnoredAttributes(array $ignoredAttributes)
141     {
142         $this->ignoredAttributes = $ignoredAttributes;
143
144         return $this;
145     }
146
147     /**
148      * Detects if the configured circular reference limit is reached.
149      *
150      * @param object $object
151      * @param array  $context
152      *
153      * @return bool
154      *
155      * @throws CircularReferenceException
156      */
157     protected function isCircularReference($object, &$context)
158     {
159         $objectHash = spl_object_hash($object);
160
161         if (isset($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash])) {
162             if ($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash] >= $this->circularReferenceLimit) {
163                 unset($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash]);
164
165                 return true;
166             }
167
168             ++$context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash];
169         } else {
170             $context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash] = 1;
171         }
172
173         return false;
174     }
175
176     /**
177      * Handles a circular reference.
178      *
179      * If a circular reference handler is set, it will be called. Otherwise, a
180      * {@class CircularReferenceException} will be thrown.
181      *
182      * @param object $object
183      *
184      * @return mixed
185      *
186      * @throws CircularReferenceException
187      */
188     protected function handleCircularReference($object)
189     {
190         if ($this->circularReferenceHandler) {
191             return call_user_func($this->circularReferenceHandler, $object);
192         }
193
194         throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', get_class($object), $this->circularReferenceLimit));
195     }
196
197     /**
198      * Gets attributes to normalize using groups.
199      *
200      * @param string|object $classOrObject
201      * @param array         $context
202      * @param bool          $attributesAsString If false, return an array of {@link AttributeMetadataInterface}
203      *
204      * @return string[]|AttributeMetadataInterface[]|bool
205      */
206     protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false)
207     {
208         if (!$this->classMetadataFactory || !isset($context[static::GROUPS]) || !is_array($context[static::GROUPS])) {
209             return false;
210         }
211
212         $allowedAttributes = array();
213         foreach ($this->classMetadataFactory->getMetadataFor($classOrObject)->getAttributesMetadata() as $attributeMetadata) {
214             $name = $attributeMetadata->getName();
215
216             if (
217                 count(array_intersect($attributeMetadata->getGroups(), $context[static::GROUPS])) &&
218                 $this->isAllowedAttribute($classOrObject, $name, null, $context)
219             ) {
220                 $allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata;
221             }
222         }
223
224         return $allowedAttributes;
225     }
226
227     /**
228      * Is this attribute allowed?
229      *
230      * @param object|string $classOrObject
231      * @param string        $attribute
232      * @param string|null   $format
233      * @param array         $context
234      *
235      * @return bool
236      */
237     protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = array())
238     {
239         return !in_array($attribute, $this->ignoredAttributes);
240     }
241
242     /**
243      * Normalizes the given data to an array. It's particularly useful during
244      * the denormalization process.
245      *
246      * @param object|array $data
247      *
248      * @return array
249      */
250     protected function prepareForDenormalization($data)
251     {
252         return (array) $data;
253     }
254
255     /**
256      * Returns the method to use to construct an object. This method must be either
257      * the object constructor or static.
258      *
259      * @param array            $data
260      * @param string           $class
261      * @param array            $context
262      * @param \ReflectionClass $reflectionClass
263      * @param array|bool       $allowedAttributes
264      *
265      * @return \ReflectionMethod|null
266      */
267     protected function getConstructor(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes)
268     {
269         return $reflectionClass->getConstructor();
270     }
271
272     /**
273      * Instantiates an object using constructor parameters when needed.
274      *
275      * This method also allows to denormalize data into an existing object if
276      * it is present in the context with the object_to_populate. This object
277      * is removed from the context before being returned to avoid side effects
278      * when recursively normalizing an object graph.
279      *
280      * @param array            $data
281      * @param string           $class
282      * @param array            $context
283      * @param \ReflectionClass $reflectionClass
284      * @param array|bool       $allowedAttributes
285      * @param string|null      $format
286      *
287      * @return object
288      *
289      * @throws RuntimeException
290      */
291     protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes/*, $format = null*/)
292     {
293         if (func_num_args() >= 6) {
294             $format = func_get_arg(5);
295         } else {
296             if (__CLASS__ !== get_class($this)) {
297                 $r = new \ReflectionMethod($this, __FUNCTION__);
298                 if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
299                     @trigger_error(sprintf('Method %s::%s() will have a 6th `$format = null` argument in version 4.0. Not defining it is deprecated since 3.2.', get_class($this), __FUNCTION__), E_USER_DEPRECATED);
300                 }
301             }
302
303             $format = null;
304         }
305
306         if (
307             isset($context[static::OBJECT_TO_POPULATE]) &&
308             is_object($context[static::OBJECT_TO_POPULATE]) &&
309             $context[static::OBJECT_TO_POPULATE] instanceof $class
310         ) {
311             $object = $context[static::OBJECT_TO_POPULATE];
312             unset($context[static::OBJECT_TO_POPULATE]);
313
314             return $object;
315         }
316
317         $constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes);
318         if ($constructor) {
319             $constructorParameters = $constructor->getParameters();
320
321             $params = array();
322             foreach ($constructorParameters as $constructorParameter) {
323                 $paramName = $constructorParameter->name;
324                 $key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName;
325
326                 $allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes);
327                 $ignored = in_array($paramName, $this->ignoredAttributes);
328                 if (method_exists($constructorParameter, 'isVariadic') && $constructorParameter->isVariadic()) {
329                     if ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
330                         if (!is_array($data[$paramName])) {
331                             throw new RuntimeException(sprintf('Cannot create an instance of %s from serialized data because the variadic parameter %s can only accept an array.', $class, $constructorParameter->name));
332                         }
333
334                         $params = array_merge($params, $data[$paramName]);
335                     }
336                 } elseif ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
337                     $parameterData = $data[$key];
338                     try {
339                         if (null !== $constructorParameter->getClass()) {
340                             if (!$this->serializer instanceof DenormalizerInterface) {
341                                 throw new LogicException(sprintf('Cannot create an instance of %s from serialized data because the serializer inject in "%s" is not a denormalizer', $constructorParameter->getClass(), static::class));
342                             }
343                             $parameterClass = $constructorParameter->getClass()->getName();
344                             $parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $context);
345                         }
346                     } catch (\ReflectionException $e) {
347                         throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $key), 0, $e);
348                     }
349
350                     // Don't run set for a parameter passed to the constructor
351                     $params[] = $parameterData;
352                     unset($data[$key]);
353                 } elseif ($constructorParameter->isDefaultValueAvailable()) {
354                     $params[] = $constructorParameter->getDefaultValue();
355                 } else {
356                     throw new RuntimeException(
357                         sprintf(
358                             'Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.',
359                             $class,
360                             $constructorParameter->name
361                         )
362                     );
363                 }
364             }
365
366             if ($constructor->isConstructor()) {
367                 return $reflectionClass->newInstanceArgs($params);
368             } else {
369                 return $constructor->invokeArgs(null, $params);
370             }
371         }
372
373         return new $class();
374     }
375 }