Skip to content

Commit

Permalink
[Serializer] Fix MissingConstructorArgumentsException returning missi…
Browse files Browse the repository at this point in the history
…ng argument one by one
  • Loading branch information
ktherage authored and nicolas-grekas committed Apr 21, 2023
1 parent f2b7403 commit 43d028d
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 88 deletions.
1 change: 0 additions & 1 deletion UPGRADE-6.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
1 change: 0 additions & 1 deletion src/Symfony/Component/Serializer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,37 @@
namespace Symfony\Component\Serializer\Exception;

/**
* @deprecated since Symfony 6.3, use {@see MissingConstructorArgumentException} instead
*
* @author Maxime VEBER <maxime.veber@nekland.fr>
*/
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;
}
}
35 changes: 20 additions & 15 deletions src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down Expand Up @@ -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(
Expand All @@ -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();
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -419,15 +419,15 @@ 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
{
$expectedTypes = [];
$isUnionType = \count($types) > 1;
$extraAttributesException = null;
$missingConstructorArgumentException = null;
$missingConstructorArgumentsException = null;
foreach ($types as $type) {
if (null === $data && $type->isNullable()) {
return null;
Expand Down Expand Up @@ -567,21 +567,21 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
}

$extraAttributesException ??= $e;
} catch (MissingConstructorArgumentException $e) {
} catch (MissingConstructorArgumentsException $e) {
if (!$isUnionType) {
throw $e;
}

$missingConstructorArgumentException ??= $e;
$missingConstructorArgumentsException ??= $e;
}
}

if ($extraAttributesException) {
throw $extraAttributesException;
}

if ($missingConstructorArgumentException) {
throw $missingConstructorArgumentException;
if ($missingConstructorArgumentsException) {
throw $missingConstructorArgumentsException;
}

if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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());
}
}
}

0 comments on commit 43d028d

Please sign in to comment.