You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
bug #60413 [Serializer] Fix collect_denormalization_errors flag in defaultContext (dmbrson)
This PR was squashed before being merged into the 6.4 branch.
Discussion
----------
[Serializer] Fix collect_denormalization_errors flag in defaultContext
| Q | A
| ------------- | ---
| Branch? | 6.4
| Bug fix? | yes
| New feature? | no
| Deprecations? | no
| Issues | Fix#59721
| License | MIT
When using the `COLLECT_DENORMALIZATION_ERRORS` flag during denormalization, Symfony should collect **all errors** and report them together in a `PartialDenormalizationException`.
Here is an example with two expected errors:
```php
final readonly class Foo
{
public function __construct(
public string $bar,
public \DateTimeInterface $createdAt,
) {}
}
$foo = $this->denormalizer->denormalize(
data: ['createdAt' => ''],
type: Foo::class,
);
```
Expected errors
1. `Failed to create object because the class misses the "bar" property.`
2. `The data is either not a string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.`
---
When the flag is passed via the `context`
```php
$foo = $this->denormalizer->denormalize(
data: ['createdAt' => ''],
type: Foo::class,
context: [
DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
],
);
```
Both errors are correctly collected and returned.
When the flag is set via `default_context` in `framework.yaml`:
```yaml
serializer:
default_context:
collect_denormalization_errors: true
```
Only one error is returned:
`The data is either not a string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.`
#Root Cause
The issue originates in the` \src\Symfony\Component\Serializer\Serializer.php`,
`function normalize` :
```php
if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]) || isset($this->defaultContext[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) {
unset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERROR
8000
S]);
$context['not_normalizable_value_exceptions'] = [];
$errors = &$context['not_normalizable_value_exceptions'];
$denormalized = $normalizer->denormalize($data, $type, $format, $context);
}
```
The first time this block is hit, it checks for the flag either in $context or $defaultContext. If found, it initializes the error array with:
```php
$context['not_normalizable_value_exceptions'] = [];
```
However, during nested denormalization (e.g., when parsing the `createdAt` field), Symfony re-enters this code path. If the flag was provided via `defaultContext`, it is still present on re-entry. Therefore, the `not_normalizable_value_exceptions` array is reset again, losing the previously collected errors.
#My Fix
The fix is to enhance the condition with an additional check to ensure the array of errors is not already initialized:
```php
if (
(isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]) || isset($this->defaultContext[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]))
&& !isset($context['not_normalizable_value_exceptions'])
)
```
This ensures the array is only initialized once, preserving previously collected errors in recursive calls, regardless of whether the flag was passed via context or default_context.
Commits
-------
d4a71ee [Serializer] Fix collect_denormalization_errors flag in defaultContext
Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Serializer.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -222,7 +222,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a
222
222
thrownewNotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type));
223
223
}
224
224
225
-
if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]) || isset($this->defaultContext[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) {
225
+
if ((isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]) || isset($this->defaultContext[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) && !isset($context['not_normalizable_value_exceptions'])) {
'message' => 'Failed to create object because the class misses the "bar" property.',
1715
+
],
1716
+
[
1717
+
'currentType' => 'string',
1718
+
'expectedTypes' => ['string'],
1719
+
'path' => 'date',
1720
+
'useMessageForUser' => true,
1721
+
'message' => 'The data is either not an string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.',
1722
+
],
1723
+
];
1724
+
1725
+
$this->assertSame($expected, $exceptionsAsArray);
1726
+
}
1727
+
}
1680
1728
}
1681
1729
1682
1730
class Model
@@ -1743,6 +1791,15 @@ public function __construct($value)
0 commit comments