10000 [Serializer] Fix denormalize constructor arguments · symfony/symfony@7be2684 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7be2684

Browse files
committed
[Serializer] Fix denormalize constructor arguments
1 parent 0a30c9b commit 7be2684

File tree

3 files changed

+41
-14
lines changed

3 files changed

+41
-14
lines changed

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

+18-8
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,10 @@ protected function instantiateObject(array &$data, string $class, array &$contex
350350
$constructorParameters = $constructor->getParameters();
351351
$missingConstructorArguments = [];
352352
$params = [];
353+
$constructorParamKeys = [];
353354
foreach ($constructorParameters as $constructorParameter) {
354355
$paramName = $constructorParameter->name;
355-
$key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName;
356+
$constructorParamKeys[] = $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName;
356357

357358
$allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes);
358359
$ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
@@ -368,18 +369,14 @@ protected function instantiateObject(array &$data, string $class, array &$contex
368369
}
369370

370371
$params = array_merge($params, $variadicParameters);
371-
unset($data[$key]);
372372
}
373373
} elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
374374
$parameterData = $data[$key];
375375
if (null === $parameterData && $constructorParameter->allowsNull()) {
376376
$params[] = null;
377-
// Don't run set for a parameter passed to the constructor
378-
unset($data[$key]);
379377
continue;
380378
}
381379

382-
// Don't run set for a parameter passed to the constructor
383380
try {
384381
$params[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format);
385382
} catch (NotNormalizableValueException $exception) {
@@ -390,7 +387,6 @@ protected function instantiateObject(array &$data, string $class, array &$contex
390387
$context['not_normalizable_value_exceptions'][] = $exception;
391388
$params[] = $parameterData;
392389
}
393-
unset($data[$key]);
394390
} elseif (\array_key_exists($key, $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
395391
$params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
396392
} elseif (\array_key_exists($key, $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
@@ -421,11 +417,25 @@ protected function instantiateObject(array &$data, string $class, array &$contex
421417
}
422418

423419
if (!$constructor->isConstructor()) {
424-
return $constructor->invokeArgs(null, $params);
420+
$instance = $constructor->invokeArgs(null, $params);
421+
422+
// do not set a parameter that has been set in the constructor
423+
foreach ($constructorParamKeys as $key) {
424+
unset($data[$key]);
425+
}
426+
427+
return $instance;
425428
}
426429

427430
try {
428-
return $reflectionClass->newInstanceArgs($params);
431+
$instance = $reflectionClass->newInstanceArgs($params);
432+
433+
// do not set a parameter that has been set in the constructor
434+
foreach ($constructorParamKeys as $key) {
435+
unset($data[$key]);
436+
}
437+
438+
return $instance;
429439
} catch (\TypeError $e) {
430440
if (!isset($context['not_normalizable_value_exceptions'])) {
431441
throw $e;

src/Symfony/Component/Serializer/Serializer.php

+17-1
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,24 @@ public function denormalize($data, string $type, string $format = null, array $c
228228
$context['not_normalizable_value_exceptions'] = [];
229229
$errors = &$context['not_normalizable_value_exceptions'];
230230
$denormalized = $normalizer->denormalize($data, $type, $format, $context);
231+
231232
if ($errors) {
232-
throw new PartialDenormalizationException($denormalized, $errors);
233+
// merge errors so that one path has only one error
234+
$uniqueErrors = [];
235+
foreach ($errors as $i => $error) {
236+
if (isset($uniqueErrors[$error->getPath()])) {
237+
continue;
238+
}
239+
240+
if (null !== $error->getPath()) {
241+
$uniqueErrors[$error->getPath()] = $error;
242+
continue;
243+
}
244+
245+
$uniqueErrors[$i] = $error;
246+
}
247+
248+
throw new PartialDenormalizationException($denormalized, array_values($uniqueErrors));
233249
}
234250

235251
return $denormalized;

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

+6-5
Original file line numberDiff line numberDiff line change
@@ -58,23 +58,24 @@ public function testMetadataAwareNameConvertorWithNotSerializedConstructorParame
5858
public function testConstructorWithMissingData()
5959
{
6060
$data = [
61-
'foo' => 10,
61+
'bar' => 10,
6262
];
6363

6464
$normalizer = $this->getDenormalizerForConstructArguments();
6565
try {
6666
$normalizer->denormalize($data, ConstructorArgumentsObject::class);
6767
self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentsException::class));
6868
} 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());
69+
self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$foo", "$baz".', ConstructorArgumentsObject::class), $e->getMessage());
70+
self::assertSame(ConstructorArgumentsObject::class, $e->getClass());
71+
self::assertSame(['foo', 'baz'], $e->getMissingConstructorArguments());
7172
}
7273
}
7374

7475
public function testExceptionsAreCollectedForConstructorWithMissingData()
7576
{
7677
$data = [
77-
'foo' => 10,
78+
'bar' => 10,
7879
];
7980

8081
$exceptions = [];
@@ -85,7 +86,7 @@ public function testExceptionsAreCollectedForConstructorWithMissingData()
8586
]);
8687

8788
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 "foo" property.', $exceptions[0]->getMessage());
8990
self::assertSame('Failed to create object because the class misses the "baz" property.', $exceptions[1]->getMessage());
9091
}
9192
}

0 commit comments

Comments
 (0)
0