annotate vendor/symfony/serializer/Normalizer/AbstractNormalizer.php @ 8:50b0d041100e

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