Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 98b5fd3

Browse filesBrowse files
committed
[Serializer] Add ability to collect denormalization errors
1 parent 49eafee commit 98b5fd3
Copy full SHA for 98b5fd3

13 files changed

+289
-10
lines changed
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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\Exception;
13+
14+
interface AggregableExceptionInterface extends ExceptionInterface
15+
{
16+
}
+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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\Exception;
13+
14+
class AggregatedException extends RuntimeException implements AggregableExceptionInterface
15+
{
16+
/**
17+
* @var ExceptionInterface[]
18+
*/
19+
private $exceptions;
20+
21+
public function __construct(array $exceptions, \Throwable $previous = null)
22+
{
23+
parent::__construct('Errors occurred during the denormalization process', 0, $previous);
24+
25+
$this->exceptions = $exceptions;
26+
}
27+
28+
/**
29+
* @return ExceptionInterface[]
30+
*/
31+
public function getExceptions(): array
32+
{
33+
return $this->exceptions;
34+
}
35+
}

‎src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
/**
1515
* @author Maxime VEBER <maxime.veber@nekland.fr>
1616
*/
17-
class MissingConstructorArgumentsException extends RuntimeException
17+
class MissingConstructorArgumentsException extends RuntimeException implements AggregableExceptionInterface
1818
{
1919
}

‎src/Symfony/Component/Serializer/Exception/NotNormalizableValueException.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Exception/NotNormalizableValueException.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
/**
1515
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
1616
*/
17-
class NotNormalizableValueException extends UnexpectedValueException
17+
class NotNormalizableValueException extends UnexpectedValueException implements AggregableExceptionInterface
1818
{
1919
}
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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\Exception;
13+
14+
class VariadicConstructorArgumentsException extends RuntimeException implements AggregableExceptionInterface
15+
{
16+
}

‎src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
+42-4Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14+
use Symfony\Component\Serializer\Exception\AggregableExceptionInterface;
15+
use Symfony\Component\Serializer\Exception\AggregatedException;
1416
use Symfony\Component\Serializer\Exception\CircularReferenceException;
1517
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1618
use Symfony\Component\Serializer\Exception\LogicException;
1719
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
1820
use Symfony\Component\Serializer\Exception\RuntimeException;
21+
use Symfony\Component\Serializer\Exception\VariadicConstructorArgumentsException;
1922
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
2023
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
2124
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
@@ -351,6 +354,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex
351354

352355
$constructorParameters = $constructor->getParameters();
353356

357+
$exceptions = [];
354358
$params = [];
355359
foreach ($constructorParameters as $constructorParameter) {
356360
$paramName = $constructorParameter->name;
@@ -361,12 +365,28 @@ protected function instantiateObject(array &$data, string $class, array &$contex
361365
if ($constructorParameter->isVariadic()) {
362366
if ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
363367
if (!\is_array($data[$paramName])) {
364-
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));
368+
$e = new VariadicConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because the variadic parameter "%s" can only accept an array.', $class, $constructorParameter->name));
369+
if (!($context[self::COLLECT_EXCEPTIONS] ?? false)) {
370+
throw $e;
371+
}
372+
373+
$exceptions[] = ['param' => $paramName, 'exception' => $e];
374+
unset($data[$key]);
375+
continue;
365376
}
366377

367378
$variadicParameters = [];
368379
foreach ($data[$paramName] as $parameterData) {
369-
$variadicParameters[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format);
380+
try {
381+
$variadicParameters[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format);
382+
} catch (AggregableExceptionInterface $e) {
383+
if (!($context[self::COLLECT_EXCEPTIONS] ?? false)) {
384+
throw $e;
385+
}
386+
387+
$exceptions[] = ['param' => $paramName, 'exception' => $e];
388+
continue;
389+
}
370390
}
371391

372392
$params = array_merge($params, $variadicParameters);
@@ -382,7 +402,15 @@ protected function instantiateObject(array &$data, string $class, array &$contex
382402
}
383403

384404
// Don't run set for a parameter passed to the constructor
385-
$params[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format);
405+
try {
406+
$params[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format);
407+
} catch (AggregableExceptionInterface $e) {
408+
if (!($context[self::COLLECT_EXCEPTIONS] ?? false)) {
409+
throw $e;
410+
}
411+
412+
$exceptions[] = ['param' => $paramName, 'exception' => $e];
413+
}
386414
unset($data[$key]);
387415
} elseif (\array_key_exists($key, $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
388416
$params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
@@ -391,10 +419,20 @@ protected function instantiateObject(array &$data, string $class, array &$contex
391419
} elseif ($constructorParameter->isDefaultValueAvailable()) {
392420
$params[] = $constructorParameter->getDefaultValue();
393421
} else {
394-
throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $constructorParameter->name));
422+
$e = new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $constructorParameter->name));
423+
424+
if (!($context[self::COLLECT_EXCEPTIONS] ?? false)) {
425+
throw $e;
426+
}
427+
428+
$exceptions[] = ['param' => $paramName, 'exception' => $e];
395429
}
396430
}
397431

432+
if (($context[self::COLLECT_EXCEPTIONS] ?? false) && !empty($exceptions)) {
433+
throw new AggregatedException($exceptions);
434+
}
435+
398436
if ($constructor->isConstructor()) {
399437
return $reflectionClass->newInstanceArgs($params);
400438
} else {

‎src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php
+21-4Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
use Symfony\Component\Serializer\Encoder\CsvEncoder;
1919
use Symfony\Component\Serializer\Encoder\JsonEncoder;
2020
use Symfony\Component\Serializer\Encoder\XmlEncoder;
21+
use Symfony\Component\Serializer\Exception\AggregableExceptionInterface;
22+
use Symfony\Component\Serializer\Exception\AggregatedException;
2123
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
2224
use Symfony\Component\Serializer\Exception\LogicException;
2325
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
@@ -311,6 +313,8 @@ public function denormalize($data, string $type, string $format = null, array $c
311313
$object = $this->instantiateObject($normalizedData, $type, $context, $reflectionClass, $allowedAttributes, $format);
312314
$resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object);
313315

316+
$exceptions = [];
317+
314318
foreach ($normalizedData as $attribute => $value) {
315319
if ($this->nameConverter) {
316320
$attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $context);
@@ -331,18 +335,31 @@ public function denormalize($data, string $type, string $format = null, array $c
331335
}
332336
}
333337

334-
$value = $this->validateAndDenormalize($resolvedClass, $attribute, $value, $format, $context);
335338
try {
336-
$this->setAttributeValue($object, $attribute, $value, $format, $context);
337-
} catch (InvalidArgumentException $e) {
338-
throw new NotNormalizableValueException(sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), $e->getCode(), $e);
339+
$value = $this->validateAndDenormalize($resolvedClass, $attribute, $value, $format, $context);
340+
try {
341+
$this->setAttributeValue($object, $attribute, $value, $format, $context);
342+
} catch (InvalidArgumentException $e) {
343+
throw new NotNormalizableValueException(sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), $e->getCode(), $e);
344+
}
345+
} catch (AggregableExceptionInterface $e) {
346+
if (!($context[self::COLLECT_EXCEPTIONS] ?? false)) {
347+
throw $e;
348+
}
349+
350+
$exceptions[] = ['param' => $attribute, 'exception' => $e];
351+
continue;
339352
}
340353
}
341354

342355
if (!empty($extraAttributes)) {
343356
throw new ExtraAttributesException($extraAttributes);
344357
}
345358

359+
if (($context[self::COLLECT_EXCEPTIONS] ?? false) && !empty($exceptions)) {
360+
throw new AggregatedException($exceptions);
361+
}
362+
346363
return $object;
347364
}
348365

‎src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Normalizer/DenormalizerInterface.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
*/
2525
interface DenormalizerInterface
2626
{
27+
/**
28+
* Collect denormalization exceptions instead of throwing out the first one.
29+
*/
30+
public const COLLECT_EXCEPTIONS = 'collect_exceptions';
31+
2732
/**
2833
* Denormalizes data back into an object of the given class.
2934
*
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\Serializer\Tests\Fixtures;
4+
5+
class CollectErrorsArrayDummy
6+
{
7+
/**
8+
* @var \DateTimeImmutable
9+
*/
10+
public $date;
11+
}
+31Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Symfony\Component\Serializer\Tests\Fixtures;
4+
5+
class CollectErrorsDummy
6+
{
7+
/**
8+
* @var int
9+
*/
10+
public $int;
11+
12+
/**
13+
* @var array
14+
*/
15+
public $array;
16+
17+
/**
18+
* @var CollectErrorsArrayDummy[]
19+
*/
20+
public $arrayOfObjects;
21+
22+
/**
23+
* @var CollectErrorsObjectDummy
24+
*/
25+
public $object;
26+
27+
/**
28+
* @var CollectErrorsVariadicObjectDummy
29+
*/
30+
public $variadicObject;
31+
}
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Symfony\Component\Serializer\Tests\Fixtures;
4+
5+
class CollectErrorsObjectDummy
6+
{
7+
public $foo;
8+
public $bar;
9+
public $baz;
10+
11+
public function __construct(int $foo, int $bar, int $baz)
12+
{
13+
$this->foo = $foo;
14+
$this->bar = $bar;
15+
$this->baz = $baz;
16+
}
17+
}
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Symfony\Component\Serializer\Tests\Fixtures;
4+
5+
class CollectErrorsVariadicObjectDummy
6+
{
7+
public $foo;
8+
9+
/** @var CollectErrorsVariadicObjectDummy[] */
10+
public $args;
11+
12+
public function __construct(int $foo, CollectErrorsVariadicObjectDummy ...$args)
13+
{
14+
$this->foo = $foo;
15+
$this->args = $args;
16+
}
17+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.