diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 4fcae7d99cfc..5c88e4455e09 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -348,7 +348,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex } $constructorParameters = $constructor->getParameters(); - + $missingConstructorArguments = []; $params = []; foreach ($constructorParameters as $constructorParameter) { $paramName = $constructorParameter->name; @@ -401,7 +401,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params[] = null; } else { if (!isset($context['not_normalizable_value_exceptions'])) { - 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), 0, null, [$constructorParameter->name]); + $missingConstructorArguments[] = $constructorParameter->name; + continue; } $exception = NotNormalizableValueException::createForUnexpectedDataType( @@ -412,24 +413,26 @@ protected function instantiateObject(array &$data, string $class, array &$contex true ); $context['not_normalizable_value_exceptions'][] = $exception; - - return $reflectionClass->newInstanceWithoutConstructor(); } } - if ($constructor->isConstructor()) { - try { - return $reflectionClass->newInstanceArgs($params); - } catch (\TypeError $th) { - if (!isset($context['not_normalizable_value_exceptions'])) { - throw $th; - } + 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); + } - 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(); + } } unset($context['has_constructor']); diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Php80WithPromotedTypedConstructor.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Php80WithPromotedTypedConstructor.php index be3247450ba7..a7b79aa47dca 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Php80WithPromotedTypedConstructor.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Php80WithPromotedTypedConstructor.php @@ -13,7 +13,10 @@ final class Php80WithPromotedTypedConstructor { - public function __construct(public bool $bool) - { + public function __construct( + public bool $bool, + public string $string, + public int $int, + ) { } } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/WithTypedConstructor.php b/src/Symfony/Component/Serializer/Tests/Fixtures/WithTypedConstructor.php new file mode 100644 index 000000000000..734d1c83b24d --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/WithTypedConstructor.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +final class WithTypedConstructor +{ + /** + * @var string + */ + public $string; + /** + * @var bool + */ + public $bool; + /** + * @var int + */ + public $int; + + public function __construct(string $string, bool $bool, int $int) + { + $this->string = $string; + $this->bool = $bool; + $this->int = $int; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php index 306c571f9c59..f7e18241c721 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php @@ -62,9 +62,30 @@ public function testConstructorWithMissingData() ]; $normalizer = $this->getDenormalizerForConstructArguments(); + try { + $normalizer->denormalize($data, ConstructorArgumentsObject::class); + 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(['bar', 'baz'], $e->getMissingConstructorArguments()); + } + } + + public function testExceptionsAreCollectedForConstructorWithMissingData() + { + $data = [ + 'foo' => 10, + ]; + + $exceptions = []; + + $normalizer = $this->getDenormalizerForConstructArguments(); + $normalizer->denormalize($data, ConstructorArgumentsObject::class, null, [ + 'not_normalizable_value_exceptions' => &$exceptions, + ]); - $this->expectException(MissingConstructorArgumentsException::class); - $this->expectExceptionMessage('Cannot create an instance of "'.ConstructorArgumentsObject::class.'" from serialized data because its constructor requires parameter "bar" to be present.'); - $normalizer->denormalize($data, ConstructorArgumentsObject::class); + self::assertCount(2, $exceptions); + self::assertSame('Failed to create object because the class misses the "bar" property.', $exceptions[0]->getMessage()); + self::assertSame('Failed to create object because the class misses the "baz" property.', $exceptions[1]->getMessage()); } } diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 2141c0cf6d33..fdd98d0be5b5 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -67,6 +67,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Php74Full; use Symfony\Component\Serializer\Tests\Fixtures\Php80WithPromotedTypedConstructor; use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy; +use Symfony\Component\Serializer\Tests\Fixtures\WithTypedConstructor; use Symfony\Component\Serializer\Tests\Normalizer\TestDenormalizer; use Symfony\Component\Serializer\Tests\Normalizer\TestNormalizer; @@ -1196,6 +1197,85 @@ public function testCollectDenormalizationErrorsWithConstructor(?ClassMetadataFa 'useMessageForUser' => false, 'message' => 'The type of the "bool" attribute for class "Symfony\\Component\\Serializer\\Tests\\Fixtures\\Php80WithPromotedTypedConstructor" must be one of "bool" ("string" given).', ], + [ + 'currentType' => 'array', + 'expectedTypes' => [ + 'unknown', + ], + 'path' => null, + 'useMessageForUser' => true, + 'message' => 'Failed to create object because the class misses the "string" property.', + ], + [ + 'currentType' => 'array', + 'expectedTypes' => [ + 'unknown', + ], + 'path' => null, + 'useMessageForUser' => true, + 'message' => 'Failed to create object because the class misses the "int" property.', + ], + ]; + + $this->assertSame($expected, $exceptionsAsArray); + } + + public function testCollectDenormalizationErrorsWithInvalidConstructorTypes() + { + $json = '{"string": "some string", "bool": "bool", "int": true}'; + + $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); + + $serializer = new Serializer( + [new ObjectNormalizer(null, null, null, $extractor)], + ['json' => new JsonEncoder()] + ); + + try { + $serializer->deserialize($json, WithTypedConstructor::class, 'json', [ + DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, + ]); + + $this->fail(); + } catch (\Throwable $th) { + $this->assertInstanceOf(PartialDenormalizationException::class, $th); + } + + $this->assertInstanceOf(WithTypedConstructor::class, $object = $th->getData()); + + $this->assertSame('some string', $object->string); + $this->assertTrue($object->bool); + $this->assertSame(1, $object->int); + + $exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array { + return [ + 'currentType' => $e->getCurrentType(), + 'expectedTypes' => $e->getExpectedTypes(), + 'path' => $e->getPath(), + 'useMessageForUser' => $e->canUseMessageForUser(), + 'message' => $e->getMessage(), + ]; + }, $th->getErrors()); + + $expected = [ + [ + 'currentType' => 'string', + 'expectedTypes' => [ + 0 => 'bool', + ], + 'path' => 'bool', + 'useMessageForUser' => false, + 'message' => 'The type of the "bool" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\WithTypedConstructor" must be one of "bool" ("string" given).', + ], + [ + 'currentType' => 'bool', + 'expectedTypes' => [ + 0 => 'int', + ], + 'path' => 'int', + 'useMessageForUser' => false, + 'message' => 'The type of the "int" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\WithTypedConstructor" must be one of "int" ("bool" given).', + ], ]; $this->assertSame($expected, $exceptionsAsArray); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy