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 0f398ce

Browse filesBrowse files
HypeMCnicolas-grekas
authored andcommitted
[Serializer] Fix collecting only first missing constructor argument
1 parent 7aaa92a commit 0f398ce
Copy full SHA for 0f398ce

File tree

5 files changed

+161
-19
lines changed
Filter options

5 files changed

+161
-19
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
+17-14Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex
348348
}
349349

350350
$constructorParameters = $constructor->getParameters();
351-
351+
$missingConstructorArguments = [];
352352
$params = [];
353353
foreach ($constructorParameters as $constructorParameter) {
354354
$paramName = $constructorParameter->name;
@@ -401,7 +401,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex
401401
$params[] = null;
402402
} else {
403403
if (!isset($context['not_normalizable_value_exceptions'])) {
404-
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]);
404+
$missingConstructorArguments[] = $constructorParameter->name;
405+
continue;
405406
}
406407

407408
$exception = NotNormalizableValueException::createForUnexpectedDataType(
@@ -412,24 +413,26 @@ protected function instantiateObject(array &$data, string $class, array &$contex
412413
true
413414
);
414415
$context['not_normalizable_value_exceptions'][] = $exception;
415-
416-
return $reflectionClass->newInstanceWithoutConstructor();
417416
}
418417
}
419418

420-
if ($constructor->isConstructor()) {
421-
try {
422-
return $reflectionClass->newInstanceArgs($params);
423-
} catch (\TypeError $th) {
424-
if (!isset($context['not_normalizable_value_exceptions'])) {
425-
throw $th;
426-
}
419+
if ($missingConstructorArguments) {
420+
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);
421+
}
427422

428-
return $reflectionClass->newInstanceWithoutConstructor();
429-
}
430-
} else {
423+
if (!$constructor->isConstructor()) {
431424
return $constructor->invokeArgs(null, $params);
432425
}
426+
427+
try {
428+
return $reflectionClass->newInstanceArgs($params);
429+
} catch (\TypeError $e) {
430+
if (!isset($context['not_normalizable_value_exceptions'])) {
431+
throw $e;
432+
}
433+
434+
return $reflectionClass->newInstanceWithoutConstructor();
435+
}
433436
}
434437

435438
unset($context['has_constructor']);

‎src/Symfony/Component/Serializer/Tests/Fixtures/Php80WithPromotedTypedConstructor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Fixtures/Php80WithPromotedTypedConstructor.php
+5-2Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313

1414
final class Php80WithPromotedTypedConstructor
1515
{
16-
public function __construct(public bool $bool)
17-
{
16+
public function __construct(
17+
public bool $bool,
18+
public string $string,
19+
public int $int,
20+
) {
1821
}
1922
}
+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\Tests\Fixtures;
13+
14+
final class WithTypedConstructor
15+
{
16+
/**
17+
* @var string
18+
*/
19+
public $string;
20+
/**
21+
* @var bool
22+
*/
23+
public $bool;
24+
/**
25+
* @var int
26+
*/
27+
public $int;
28+
29+
public function __construct(string $string, bool $bool, int $int)
30+
{
31+
$this->string = $string;
32+
$this->bool = $bool;
33+
$this->int = $int;
34+
}
35+
}

‎src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php
+24-3Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,30 @@ public function testConstructorWithMissingData()
6262
];
6363

6464
$normalizer = $this->getDenormalizerForConstructArguments();
65+
try {
66+
$normalizer->denormalize($data, ConstructorArgumentsObject::class);
67+
self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentsException::class));
68+
} catch (MissingConstructorArgumentsException $e) {
69+
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());
70+
self::assertSame(['bar', 'baz'], $e->getMissingConstructorArguments());
71+
}
72+
}
73+
74+
public function testExceptionsAreCollectedForConstructorWithMissingData()
75+
{
76+
$data = [
77+
'foo' => 10,
78+
];
79+
80+
$exceptions = [];
81+
82+
$normalizer = $this->getDenormalizerForConstructArguments();
83+
$normalizer->denormalize($data, ConstructorArgumentsObject::class, null, [
84+
'not_normalizable_value_exceptions' => &$exceptions,
85+
]);
6586

66-
$this->expectException(MissingConstructorArgumentsException::class);
67-
$this->expectExceptionMessage('Cannot create an instance of "'.ConstructorArgumentsObject::class.'" from serialized data because its constructor requires parameter "bar" to be present.');
68-
$normalizer->denormalize($data, ConstructorArgumentsObject::class);
87+
self::assertCount(2, $exceptions);
88+
self::assertSame('Failed to create object because the class misses the "bar" property.', $exceptions[0]->getMessage());
89+
self::assertSame('Failed to create object because the class misses the "baz" property.', $exceptions[1]->getMessage());
6990
}
7091
}

‎src/Symfony/Component/Serializer/Tests/SerializerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/SerializerTest.php
+80Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
use Symfony\Component\Serializer\Tests\Fixtures\Php74Full;
6868
use Symfony\Component\Serializer\Tests\Fixtures\Php80WithPromotedTypedConstructor;
6969
use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy;
70+
use Symfony\Component\Serializer\Tests\Fixtures\WithTypedConstructor;
7071
use Symfony\Component\Serializer\Tests\Normalizer\TestDenormalizer;
7172
use Symfony\Component\Serializer\Tests\Normalizer\TestNormalizer;
7273

@@ -1196,6 +1197,85 @@ public function testCollectDenormalizationErrorsWithConstructor(?ClassMetadataFa
11961197
'useMessageForUser' => false,
11971198
'message' => 'The type of the "bool" attribute for class "Symfony\\Component\\Serializer\\Tests\\Fixtures\\Php80WithPromotedTypedConstructor" must be one of "bool" ("string" given).',
11981199
],
1200+
[
1201+
'currentType' => 'array',
1202+
'expectedTypes' => [
1203+
'unknown',
1204+
],
1205+
'path' => null,
1206+
'useMessageForUser' => true,
1207+
'message' => 'Failed to create object because the class misses the "string" property.',
1208+
],
1209+
[
1210+
'currentType' => 'array',
1211+
'expectedTypes' => [
1212+
'unknown',
1213+
],
1214+
'path' => null,
1215+
'useMessageForUser' => true,
1216+
'message' => 'Failed to create object because the class misses the "int" property.',
1217+
],
1218+
];
1219+
1220+
$this->assertSame($expected, $exceptionsAsArray);
1221+
}
1222+
1223+
public function testCollectDenormalizationErrorsWithInvalidConstructorTypes()
1224+
{
1225+
$json = '{"string": "some string", "bool": "bool", "int": true}';
1226+
1227+
$extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]);
1228+
1229+
$serializer = new Serializer(
1230+
[new ObjectNormalizer(null, null, null, $extractor)],
1231+
['json' => new JsonEncoder()]
1232+
);
1233+
1234+
try {
1235+
$serializer->deserialize($json, WithTypedConstructor::class, 'json', [
1236+
DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
1237+
]);
1238+
1239+
$this->fail();
1240+
} catch (\Throwable $th) {
1241+
$this->assertInstanceOf(PartialDenormalizationException::class, $th);
1242+
}
1243+
1244+
$this->assertInstanceOf(WithTypedConstructor::class, $object = $th->getData());
1245+
1246+
$this->assertSame('some string', $object->string);
1247+
$this->assertTrue($object->bool);
1248+
$this->assertSame(1, $object->int);
1249+
1250+
$exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array {
1251+
return [
1252+
'currentType' => $e->getCurrentType(),
1253+
'expectedTypes' => $e->getExpectedTypes(),
1254+
'path' => $e->getPath(),
1255+
'useMessageForUser' => $e->canUseMessageForUser(),
1256+
'message' => $e->getMessage(),
1257+
];
1258+
}, $th->getErrors());
1259+
1260+
$expected = [
1261+
[
1262+
'currentType' => 'string',
1263+
'expectedTypes' => [
1264+
0 => 'bool',
1265+
],
1266+
'path' => 'bool',
1267+
'useMessageForUser' => false,
1268+
'message' => 'The type of the "bool" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\WithTypedConstructor" must be one of "bool" ("string" given).',
1269+
],
1270+
[
1271+
'currentType' => 'bool',
1272+
'expectedTypes' => [
1273+
0 => 'int',
1274+
],
1275+
'path' => 'int',
1276+
'useMessageForUser' => false,
1277+
'message' => 'The type of the "int" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\WithTypedConstructor" must be one of "int" ("bool" given).',
1278+
],
11991279
];
12001280

12011281
$this->assertSame($expected, $exceptionsAsArray);

0 commit comments

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