Mercurial > hg > isophonics-drupal-site
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 } |