Skip to content

Commit a898923

Browse files
committed
[Serializer] Fix partial denormalization with missing constructor arguments
1 parent 1de8768 commit a898923

File tree

3 files changed

+85
-7
lines changed

3 files changed

+85
-7
lines changed

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -378,32 +378,32 @@ protected function instantiateObject(array &$data, string $class, array &$contex
378378
} elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
379379
$parameterData = $data[$key];
380380
if (null === $parameterData && $constructorParameter->allowsNull()) {
381-
$params[] = null;
381+
$params[$paramName] = null;
382382
$unsetKeys[] = $key;
383383

384384
continue;
385385
}
386386

387387
try {
388-
$params[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format);
388+
$params[$paramName] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format);
389389
} catch (NotNormalizableValueException $exception) {
390390
if (!isset($context['not_normalizable_value_exceptions'])) {
391391
throw $exception;
392392
}
393393

394394
$context['not_normalizable_value_exceptions'][] = $exception;
395-
$params[] = $parameterData;
395+
$params[$paramName] = $parameterData;
396396
}
397397

398398
$unsetKeys[] = $key;
399399
} elseif (\array_key_exists($key, $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
400-
$params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
400+
$params[$paramName] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
401401
} elseif (\array_key_exists($key, $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
402-
$params[] = $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
402+
$params[$paramName] = $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
403403
} elseif ($constructorParameter->isDefaultValueAvailable()) {
404-
$params[] = $constructorParameter->getDefaultValue();
404+
$params[$paramName] = $constructorParameter->getDefaultValue();
405405
} elseif ($constructorParameter->hasType() && $constructorParameter->getType()->allowsNull()) {
406-
$params[] = null;
406+
$params[$paramName] = null;
407407
} else {
408408
if (!isset($context['not_normalizable_value_exceptions'])) {
409409
$missingConstructorArguments[] = $constructorParameter->name;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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 Php80WithOptionalConstructorParameter
15+
{
16+
public function __construct(
17+
public string $one,
18+
public string $two,
19+
public ?string $three = null,
20+
) {
21+
}
22+
}

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy;
7070
use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy;
7171
use Symfony\Component\Serializer\Tests\Fixtures\Php74Full;
72+
use Symfony\Component\Serializer\Tests\Fixtures\Php80WithOptionalConstructorParameter;
7273
use Symfony\Component\Serializer\Tests\Fixtures\Php80WithPromotedTypedConstructor;
7374
use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy;
7475
use Symfony\Component\Serializer\Tests\Fixtures\WithTypedConstructor;
@@ -1433,6 +1434,61 @@ public static function provideCollectDenormalizationErrors()
14331434
[new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()))],
14341435
];
14351436
}
1437+
1438+
/**
1439+
* @requires PHP 8
1440+
*/
1441+
public function testPartialDenormalizationWithMissingConstructorTypes()
1442+
{
1443+
$json = '{"one": "one string", "three": "three string"}';
1444+
1445+
$extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]);
1446+
1447+
$serializer = new Serializer(
1448+
[new ObjectNormalizer(null, null, null, $extractor)],
1449+
['json' => new JsonEncoder()]
1450+
);
1451+
1452+
try {
1453+
$serializer->deserialize($json, Php80WithOptionalConstructorParameter::class, 'json', [
1454+
DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
1455+
]);
1456+
1457+
$this->fail();
1458+
} catch (\Throwable $th) {
1459+
$this->assertInstanceOf(PartialDenormalizationException::class, $th);
1460+
}
1461+
1462+
$this->assertInstanceOf(Php80WithOptionalConstructorParameter::class, $object = $th->getData());
1463+
1464+
$this->assertSame('one string', $object->one);
1465+
$this->assertFalse(isset($object->two));
1466+
$this->assertSame('three string', $object->three);
1467+
1468+
$exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array {
1469+
return [
1470+
'currentType' => $e->getCurrentType(),
1471+
'expectedTypes' => $e->getExpectedTypes(),
1472+
'path' => $e->getPath(),
1473+
'useMessageForUser' => $e->canUseMessageForUser(),
1474+
'message' => $e->getMessage(),
1475+
];
1476+
}, $th->getErrors());
1477+
1478+
$expected = [
1479+
[
1480+
'currentType' => 'array',
1481+
'expectedTypes' => [
1482+
'unknown',
1483+
],
1484+
'path' => null,
1485+
'useMessageForUser' => true,
1486+
'message' => 'Failed to create object because the class misses the "two" property.',
1487+
],
1488+
];
1489+
1490+
$this->assertSame($expected, $exceptionsAsArray);
1491+
}
14361492
}
14371493

14381494
class Model

0 commit comments

Comments
 (0)