comparison vendor/symfony/serializer/Normalizer/AbstractNormalizer.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 7a779792577d
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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 (configured limit: %d).', $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 }