Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1189063

Browse files
Merge branch '4.4' into 5.4
* 4.4: [Serializer] Fix ignored callbacks in denormalization fix sorting bug
2 parents c5be706 + 7fd9127 commit 1189063

File tree

13 files changed

+345
-52
lines changed

13 files changed

+345
-52
lines changed

src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ protected function sortTaggedServicesByPriority(array $services): array
266266
{
267267
$maxPriority = [];
268268
foreach ($services as $service => $tags) {
269-
$maxPriority[$service] = 0;
269+
$maxPriority[$service] = \PHP_INT_MIN;
270270
foreach ($tags as $tag) {
271271
$currentPriority = $tag['priority'] ?? 0;
272272
if ($maxPriority[$service] < $currentPriority) {

src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ public static function getContainerDefinitionsWithPriorityTags()
191191
$definition1 = new Definition('Full\\Qualified\\Class1');
192192
$definition2 = new Definition('Full\\Qualified\\Class2');
193193
$definition3 = new Definition('Full\\Qualified\\Class3');
194+
$definition4 = new Definition('Full\\Qualified\\Class4');
194195

195196
return [
196197
'definition_1' => $definition1
@@ -219,6 +220,13 @@ public static function getContainerDefinitionsWithPriorityTags()
219220
->setAbstract(false)
220221
->addTag('tag1', ['attr1' => 'val1', 'attr2' => 'val2', 'priority' => 0])
221222
->addTag('tag1', ['attr3' => 'val3', 'priority' => 40]),
223+
'definition_4' => $definition4
224+
->setPublic(true)
225+
->setSynthetic(true)
226+
->setFile('/path/to/file')
227+
->setLazy(false)
228+
->setAbstract(false)
229+
->addTag('tag1', ['priority' => 0]),
222230
];
223231
}
224232

src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,25 @@
6363
}
6464
]
6565
},
66+
"definition_4": {
67+
"class": "Full\\Qualified\\Class4",
68+
"public": true,
69+
"synthetic": true,
70+
"lazy": false,
71+
"shared": true,
72+
"abstract": false,
73+
"autowire": false,
74+
"autoconfigure": false,
75+
"file": "\/path\/to\/file",
76+
"tags": [
77+
{
78+
"name": "tag1",
79+
"parameters": {
80+
"priority": 0
81+
}
82+
}
83+
]
84+
},
6685
"definition_2": {
6786
"class": "Full\\Qualified\\Class2",
6887
"public": true,

src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ Definitions
4444
- Attr2: val2
4545
- Tag: `tag2`
4646

47+
### definition_4
48+
49+
- Class: `Full\Qualified\Class4`
50+
- Public: yes
51+
- Synthetic: yes
52+
- Lazy: no
53+
- Shared: yes
54+
- Abstract: no
55+
- Autowired: no
56+
- Autoconfigured: no
57+
- File: `/path/to/file`
58+
- Tag: `tag1`
59+
- Priority: 0
60+
4761
### definition_2
4862

4963
- Class: `Full\Qualified\Class2`

src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
(same service as previous, another tag) val1 val2 0
1010
definition_1 val1 30 Full\Qualified\Class1
1111
(same service as previous, another tag) val2
12+
definition_4 0 Full\Qualified\Class4
1213
definition_2 val1 val2 -20 Full\Qualified\Class2
1314
------------------------------------------ ------- ------- ---------- ------- -----------------------
1415

src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_priority_tag.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@
2929
<tag name="tag2"/>
3030
</tags>
3131
</definition>
32+
<definition id="definition_4" class="Full\Qualified\Class4" public="true" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
33+
<tags>
34+
<tag name="tag1">
35+
<parameter name="priority">0</parameter>
36+
</tag>
37+
</tags>
38+
</definition>
3239
<definition id="definition_2" class="Full\Qualified\Class2" public="true" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
3340
<tags>
3441
<tag name="tag1">

src/Symfony/Component/Serializer/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
}

src/Symfony/Component/Serializer/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
/**

src/Symfony/Component/Serializer/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)