From 43d028d5ba2bc6cd4dd28ea1ac6823437572a1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?THERAGE=20K=C3=A9vin?= Date: Mon, 27 Mar 2023 14:28:52 +0200 Subject: [PATCH] [Serializer] Fix MissingConstructorArgumentsException returning missing argument one by one --- UPGRADE-6.3.md | 1 - src/Symfony/Component/Serializer/CHANGELOG.md | 1 - .../MissingConstructorArgumentException.php | 41 ------------------- .../MissingConstructorArgumentsException.php | 34 +++++++-------- .../Normalizer/AbstractNormalizer.php | 35 +++++++++------- .../Normalizer/AbstractObjectNormalizer.php | 14 +++---- .../ConstructorArgumentsTestTrait.php | 11 +++-- 7 files changed, 49 insertions(+), 88 deletions(-) delete mode 100644 src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentException.php diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index a05514396074c..5600e12873e47 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -133,7 +133,6 @@ Validator Serializer ---------- - * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` * Deprecate `CacheableSupportsMethodInterface` in favor of the new `getSupportedTypes(?string $format)` methods * The following Normalizer classes will become final in 7.0: * `ConstraintViolationListNormalizer` diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 7c2dd31143551..692226968f2c0 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -9,7 +9,6 @@ CHANGELOG * Add `UnsupportedFormatException` which is thrown when there is no decoder for a given format * Add method `getSupportedTypes(?string $format)` to `NormalizerInterface` and `DenormalizerInterface` * Make `ProblemNormalizer` give details about `ValidationFailedException` and `PartialDenormalizationException` - * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` * Deprecate `CacheableSupportsMethodInterface` in favor of the new `getSupportedTypes(?string $format)` methods * The following Normalizer classes will become final in 7.0: * `ConstraintViolationListNormalizer` diff --git a/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentException.php b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentException.php deleted file mode 100644 index 3fdfaf605869e..0000000000000 --- a/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentException.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Exception; - -class MissingConstructorArgumentException extends MissingConstructorArgumentsException -{ - private string $class; - private string $missingArgument; - - /** - * @param class-string $class - */ - public function __construct(string $class, string $missingArgument, int $code = 0, \Throwable $previous = null) - { - $this->class = $class; - $this->missingArgument = $missingArgument; - - $message = sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $missingArgument); - - parent::__construct($message, $code, $previous, [$missingArgument]); - } - - public function getClass(): string - { - return $this->class; - } - - public function getMissingArgument(): string - { - return $this->missingArgument; - } -} diff --git a/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php index a5a71d00cf62a..ee8fcb5dc22e5 100644 --- a/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php +++ b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php @@ -12,37 +12,37 @@ namespace Symfony\Component\Serializer\Exception; /** - * @deprecated since Symfony 6.3, use {@see MissingConstructorArgumentException} instead - * * @author Maxime VEBER */ class MissingConstructorArgumentsException extends RuntimeException { /** - * @var string[] + * @param string[] $missingArguments + * @param class-string|null $class */ - private $missingArguments; - - public function __construct(string $message, int $code = 0, \Throwable $previous = null, array $missingArguments = []) - { - if (!$this instanceof MissingConstructorArgumentException) { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s" class is deprecated, use "%s" instead.', __CLASS__, MissingConstructorArgumentException::class); - } - - $this->missingArguments = $missingArguments; - + public function __construct( + string $message, + int $code = 0, + \Throwable $previous = null, + private array $missingArguments = [], + private ?string $class = null, + ) { parent::__construct($message, $code, $previous); } /** - * @deprecated since Symfony 6.3, use {@see MissingConstructorArgumentException::getMissingArgument()} instead - * * @return string[] */ public function getMissingConstructorArguments(): array { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "%s::getMissingArgument()" instead.', __METHOD__, MissingConstructorArgumentException::class); - return $this->missingArguments; } + + /** + * @return class-string|null + */ + public function getClass(): ?string + { + return $this->class; + } } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 856aefbe985bc..61b14a1a8008f 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -14,7 +14,7 @@ use Symfony\Component\Serializer\Exception\CircularReferenceException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\LogicException; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; @@ -313,7 +313,7 @@ protected function getConstructor(array &$data, string $class, array &$context, * @return object * * @throws RuntimeException - * @throws MissingConstructorArgumentException + * @throws MissingConstructorArgumentsException */ protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { @@ -332,7 +332,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex } $constructorParameters = $constructor->getParameters(); - + $missingConstructorArguments = []; $params = []; foreach ($constructorParameters as $constructorParameter) { $paramName = $constructorParameter->name; @@ -386,7 +386,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params[] = null; } else { if (!isset($context['not_normalizable_value_exceptions'])) { - throw new MissingConstructorArgumentException($class, $constructorParameter->name); + $missingConstructorArguments[] = $constructorParameter->name; + continue; } $exception = NotNormalizableValueException::createForUnexpectedDataType( @@ -402,19 +403,23 @@ protected function instantiateObject(array &$data, string $class, array &$contex } } - if ($constructor->isConstructor()) { - try { - return $reflectionClass->newInstanceArgs($params); - } catch (\TypeError $e) { - if (!isset($context['not_normalizable_value_exceptions'])) { - throw $e; - } + if ($missingConstructorArguments) { + throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$%s".', $class, implode('", "$', $missingConstructorArguments)), 0, null, $missingConstructorArguments, $class); + } - return $reflectionClass->newInstanceWithoutConstructor(); - } - } else { + if (!$constructor->isConstructor()) { return $constructor->invokeArgs(null, $params); } + + try { + return $reflectionClass->newInstanceArgs($params); + } catch (\TypeError $e) { + if (!isset($context['not_normalizable_value_exceptions'])) { + throw $e; + } + + return $reflectionClass->newInstanceWithoutConstructor(); + } } return new $class(); @@ -438,7 +443,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara } } catch (\ReflectionException $e) { throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e); - } catch (MissingConstructorArgumentException $e) { + } catch (MissingConstructorArgumentsException $e) { if (!$parameter->getType()->allowsNull()) { throw $e; } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 88e3c3ea120bd..16b2bb70af3fc 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -22,7 +22,7 @@ use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\LogicException; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; @@ -419,7 +419,7 @@ abstract protected function setAttributeValue(object $object, string $attribute, * * @throws NotNormalizableValueException * @throws ExtraAttributesException - * @throws MissingConstructorArgumentException + * @throws MissingConstructorArgumentsException * @throws LogicException */ private function validateAndDenormalize(array $types, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed @@ -427,7 +427,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri $expectedTypes = []; $isUnionType = \count($types) > 1; $extraAttributesException = null; - $missingConstructorArgumentException = null; + $missingConstructorArgumentsException = null; foreach ($types as $type) { if (null === $data && $type->isNullable()) { return null; @@ -567,12 +567,12 @@ private function validateAndDenormalize(array $types, string $currentClass, stri } $extraAttributesException ??= $e; - } catch (MissingConstructorArgumentException $e) { + } catch (MissingConstructorArgumentsException $e) { if (!$isUnionType) { throw $e; } - $missingConstructorArgumentException ??= $e; + $missingConstructorArgumentsException ??= $e; } } @@ -580,8 +580,8 @@ private function validateAndDenormalize(array $types, string $currentClass, stri throw $extraAttributesException; } - if ($missingConstructorArgumentException) { - throw $missingConstructorArgumentException; + if ($missingConstructorArgumentsException) { + throw $missingConstructorArgumentsException; } if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) { diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php index 9489136be2cb9..928ded512e935 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer\Features; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Tests\Fixtures\NotSerializedConstructorArgumentDummy; @@ -62,14 +62,13 @@ public function testConstructorWithMissingData() ]; $normalizer = $this->getDenormalizerForConstructArguments(); - try { $normalizer->denormalize($data, ConstructorArgumentsObject::class); - self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentException::class)); - } catch (MissingConstructorArgumentException $e) { - self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "bar" to be present.', ConstructorArgumentsObject::class), $e->getMessage()); + self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentsException::class)); + } catch (MissingConstructorArgumentsException $e) { + self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$bar", "$baz".', ConstructorArgumentsObject::class), $e->getMessage()); self::assertSame(ConstructorArgumentsObject::class, $e->getClass()); - self::assertSame('bar', $e->getMissingArgument()); + self::assertSame(['bar', 'baz'], $e->getMissingConstructorArguments()); } } }