Skip to content

Commit 521aaf7

Browse files
Merge branch '4.4' into 5.4
* 4.4: [Serializer] Fix ignored callbacks in denormalization fix sorting bug
2 parents 701d7e6 + 5e29f45 commit 521aaf7

File tree

7 files changed

+295
-51
lines changed

7 files changed

+295
-51
lines changed

Normalizer/AbstractNormalizer.php

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
8080
public const DEFAULT_CONSTRUCTOR_ARGUMENTS = 'default_constructor_arguments';
8181

8282
/**
83-
* Hashmap of field name => callable to normalize this field.
83+
* Hashmap of field name => callable to (de)normalize this field.
8484
*
8585
* The callable is called if the field is encountered with the arguments:
8686
*
87-
* - mixed $attributeValue value of this field
88-
* - object $object the whole object being normalized
89-
* - string $attributeName name of the attribute being normalized
90-
* - string $format the requested format
91-
* - array $context the serialization context
87+
* - mixed $attributeValue value of this field
88+
* - object|string $object the whole object being normalized or the object's class being denormalized
89+
* - string $attributeName name of the attribute being (de)normalized
90+
* - string $format the requested format
91+
* - array $context the serialization context
9292
*/
9393
public const CALLBACKS = 'callbacks';
9494

@@ -143,17 +143,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory
143143
$this->nameConverter = $nameConverter;
144144
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
145145

146-
if (isset($this->defaultContext[self::CALLBACKS])) {
147-
if (!\is_array($this->defaultContext[self::CALLBACKS])) {
148-
throw new InvalidArgumentException(sprintf('The "%s" default context option must be an array of callables.', self::CALLBACKS));
149-
}
150-
151-
foreach ($this->defaultContext[self::CALLBACKS] as $attribute => $callback) {
152-
if (!\is_callable($callback)) {
153-
throw new InvalidArgumentException(sprintf('Invalid callback found for attribute "%s" in the "%s" default context option.', $attribute, self::CALLBACKS));
154-
}
155-
}
156-
}
146+
$this->validateCallbackContext($this->defaultContext, 'default');
157147

158148
if (isset($this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER]) && !\is_callable($this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER])) {
159149
throw new InvalidArgumentException(sprintf('Invalid callback found in the "%s" default context option.', self::CIRCULAR_REFERENCE_HANDLER));
@@ -450,7 +440,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
450440
throw new LogicException(sprintf('Cannot create an instance of "%s" from serialized data because the serializer inject in "%s" is not a denormalizer.', $parameterClass, static::class));
451441
}
452442

453-
return $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $parameterName, $format));
443+
$parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $parameterName, $format));
454444
}
455445
} catch (\ReflectionException $e) {
456446
throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e);
@@ -462,7 +452,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
462452
return null;
463453
}
464454

465-
return $parameterData;
455+
return $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context);
466456
}
467457

468458
/**
@@ -478,4 +468,46 @@ protected function createChildContext(array $parentContext, string $attribute, ?
478468

479469
return $parentContext;
480470
}
471+
472+
/**
473+
* Validate callbacks set in context.
474+
*
475+
* @param string $contextType Used to specify which context is invalid in exceptions
476+
*
477+
* @throws InvalidArgumentException
478+
*/
479+
final protected function validateCallbackContext(array $context, string $contextType = ''): void
480+
{
481+
if (!isset($context[self::CALLBACKS])) {
482+
return;
483+
}
484+
485+
if (!\is_array($context[self::CALLBACKS])) {
486+
throw new InvalidArgumentException(sprintf('The "%s"%s context option must be an array of callables.', self::CALLBACKS, '' !== $contextType ? " $contextType" : ''));
487+
}
488+
489+
foreach ($context[self::CALLBACKS] as $attribute => $callback) {
490+
if (!\is_callable($callback)) {
491+
throw new InvalidArgumentException(sprintf('Invalid callback found for attribute "%s" in the "%s"%s context option.', $attribute, self::CALLBACKS, '' !== $contextType ? " $contextType" : ''));
492+
}
493+
}
494+
}
495+
496+
/**
497+
* Apply callbacks set in context.
498+
*
499+
* @param mixed $value
500+
* @param object|string $object Can be either the object being normalizing or the object's class being denormalized
501+
*
502+
* @return mixed
503+
*/
504+
final protected function applyCallbacks($value, $object, string $attribute, ?string $format, array $context)
505+
{
506+
/**
507+
* @var callable|null
508+
*/
509+
$callback = $context[self::CALLBACKS][$attribute] ?? $this->defaultContext[self::CALLBACKS][$attribute] ?? null;
510+
511+
return $callback ? $callback($value, $object, $attribute, $format, $context) : $value;
512+
}
481513
}

Normalizer/AbstractObjectNormalizer.php

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,7 @@ public function normalize($object, string $format = null, array $context = [])
149149
$context['cache_key'] = $this->getCacheKey($format, $context);
150150
}
151151

152-
if (isset($context[self::CALLBACKS])) {
153-
if (!\is_array($context[self::CALLBACKS])) {
154-
throw new InvalidArgumentException(sprintf('The "%s" context option must be an array of callables.', self::CALLBACKS));
155-
}
156-
157-
foreach ($context[self::CALLBACKS] as $attribute => $callback) {
158-
if (!\is_callable($callback)) {
159-
throw new InvalidArgumentException(sprintf('Invalid callback found for attribute "%s" in the "%s" context option.', $attribute, self::CALLBACKS));
160-
}
161-
}
162-
}
152+
$this->validateCallbackContext($context);
163153

164154
if ($this->isCircularReference($object, $context)) {
165155
return $this->handleCircularReference($object, $format, $context);
@@ -205,13 +195,7 @@ public function normalize($object, string $format = null, array $context = [])
205195
$attributeValue = $maxDepthHandler($attributeValue, $object, $attribute, $format, $attributeContext);
206196
}
207197

208-
/**
209-
* @var callable|null
210-
*/
211-
$callback = $context[self::CALLBACKS][$attribute] ?? $this->defaultContext[self::CALLBACKS][$attribute] ?? null;
212-
if ($callback) {
213-
$attributeValue = $callback($attributeValue, $object, $attribute, $format, $attributeContext);
214-
}
198+
$attributeValue = $this->applyCallbacks($attributeValue, $object, $attribute, $format, $attributeContext);
215199

216200
if (null !== $attributeValue && !is_scalar($attributeValue)) {
217201
$stack[$attribute] = $attributeValue;
@@ -364,6 +348,8 @@ public function denormalize($data, string $type, string $format = null, array $c
364348
$context['cache_key'] = $this->getCacheKey($format, $context);
365349
}
366350

351+
$this->validateCallbackContext($context);
352+
367353
$allowedAttributes = $this->getAllowedAttributes($type, $context, true);
368354
$normalizedData = $this->prepareForDenormalization($data);
369355
$extraAttributes = [];
@@ -407,6 +393,9 @@ public function denormalize($data, string $type, string $format = null, array $c
407393
throw $exception;
408394
}
409395
}
396+
397+
$value = $this->applyCallbacks($value, $resolvedClass, $attribute, $format, $attributeContext);
398+
410399
try {
411400
$this->setAttributeValue($object, $attribute, $value, $format, $attributeContext);
412401
} catch (InvalidArgumentException $e) {
@@ -598,7 +587,9 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
598587
return parent::denormalizeParameter($class, $parameter, $parameterName, $parameterData, $context, $format);
599588
}
600589

601-
return $this->validateAndDenormalize($types, $class->getName(), $parameterName, $parameterData, $format, $context);
590+
$parameterData = $this->validateAndDenormalize($types, $class->getName(), $parameterName, $parameterData, $format, $context);
591+
592+
return $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context);
602593
}
603594

604595
/**

Tests/Normalizer/Features/CallbacksObject.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,34 @@ class CallbacksObject
66
{
77
public $bar;
88

9-
public function __construct($bar = null)
9+
/**
10+
* @var string|null
11+
*/
12+
public $foo;
13+
14+
public function __construct($bar = null, string $foo = null)
1015
{
1116
$this->bar = $bar;
17+
$this->foo = $foo;
1218
}
1319

1420
public function getBar()
1521
{
1622
return $this->bar;
1723
}
24+
25+
public function setBar($bar)
26+
{
27+
$this->bar = $bar;
28+
}
29+
30+
public function getFoo(): ?string
31+
{
32+
return $this->foo;
33+
}
34+
35+
public function setFoo(?string $foo)
36+
{
37+
$this->foo = $foo;
38+
}
1839
}

0 commit comments

Comments
 (0)