8000 Fix denormalizing empty string into object|null parameter · symfony/symfony@8724c59 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8724c59

Browse files
committed
Fix denormalizing empty string into object|null parameter
1 parent eee7e8f commit 8724c59

File tree

6 files changed

+159
-5
lines changed

6 files changed

+159
-5
lines changed

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -434,11 +434,16 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
434434
$isUnionType = \count($types) > 1;
435435
$extraAttributesException = null;
436436
$missingConstructorArgumentsException = null;
437+
$hasNonObjectType = false;
438+
$isUnionTypeOrNullable = $isUnionType;
439+
437440
foreach ($types as $type) {
438441
if (null === $data && $type->isNullable()) {
439442
return null;
440443
}
441444

445+
$isUnionTypeOrNullable = $isUnionTypeOrNullable ?: $type->isNullable();
446+
$hasNonObjectType = $hasNonObjectType ?: Type::BUILTIN_TYPE_OBJECT !== $type->getBuiltinType();
442447
$collectionValueType = $type->isCollection() ? $type->getCollectionValueTypes()[0] ?? null : null;
443448

444449
// Fix a collection that contains the only one element
@@ -456,9 +461,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
456461
// In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
457462
// if a value is meant to be a string, float, int or a boolean value from the serialized representation.
458463
// That's why we have to transform the values, if one of these non-string basic datatypes is expected.
464+
$builtinType = $type->getBuiltinType();
459465
if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) {
460466
if ('' === $data) {
461-
if (Type::BUILTIN_TYPE_ARRAY === $builtinType = $type->getBuiltinType()) {
467+
if (Type::BUILTIN_TYPE_ARRAY === $builtinType) {
462468
return [];
463469
}
464470

@@ -467,7 +473,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
467473
}
468474
}
469475

470-
switch ($builtinType ?? $type->getBuiltinType()) {
476+
switch ($builtinType) {
471477
case Type::BUILTIN_TYPE_BOOL:
472478
// according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
473479
if ('false' === $data || '0' === $data) {
@@ -564,24 +570,28 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
564570
return $data;
565571
}
566572
} catch (NotNor E864 malizableValueException|InvalidArgumentException $e) {
567-
if (!$isUnionType) {
573+
if (!$isUnionTypeOrNullable) {
568574
throw $e;
569575
}
570576
} catch (ExtraAttributesException $e) {
571-
if (!$isUnionType) {
577+
if (!$isUnionTypeOrNullable) {
572578
throw $e;
573579
}
574580

575581
$extraAttributesException ??= $e;
576582
} catch (MissingConstructorArgumentsException $e) {
577-
if (!$isUnionType) {
583+
if (!$isUnionTypeOrNullable) {
578584
throw $e;
579585
}
580586

581587
$missingConstructorArgumentsException ??= $e;
582588
}
583589
}
584590

591+
if ($data === '' && $isUnionTypeOrNullable && !$hasNonObjectType && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) {
592+
return null;
593+
}
594+
585595
if ($extraAttributesException) {
586596
throw $extraAttributesException;
587597
}
@@ -590,6 +600,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
590600
throw $missingConstructorArgumentsException;
591601
}
592602

603+
if (!$isUnionType && isset($e)) {
604+
throw $e;
605+
}
606+
593607
if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) {
594608
return $data;
595609
}
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+
/**
15+
* @author Jeroen <github.com/Jeroeny>
16+
*/
17+
class DummyWithNotNormalizable
18+
{
19+
public function __construct(public NotNormalizableDummy|null $value)
20+
{
21+
}
22+
}
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+
/**
15+
* @author Jeroen <github.com/Jeroeny>
16+
*/
17+
class DummyWithObjectOrBool
18+
{
19+
public function __construct(public Php80WithPromotedTypedConstructor|bool $value)
20+
{
21+
}
22+
}
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+
/**
15+
* @author Jeroen <github.com/Jeroeny>
16+
*/
17+
class DummyWithObjectOrNull
18+
{
19+
public function __construct(public Php80WithPromotedTypedConstructor|null $value)
20+
{
21+
}
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
15+
use Symfony\Component\Serializer\Normalizer\DenormalizableInterface;
16+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
17+
18+
/**
19+
* @author Jeroen <github.com/Jeroeny>
20+
*/
21+
class NotNormalizableDummy implements DenormalizableInterface
22+
{
23+
public function __construct()
24+
{
25+
}
26+
27+
public function denormalize(DenormalizerInterface $denormalizer, float|int|bool|array|string $data, string $format = null, array $context = [])
28+
{
29+
throw new NotNormalizableValueException();
30+
}
31+
}

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
1717
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
1818
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
19+
use Symfony\Component\Serializer\Encoder\CsvEncoder;
1920
use Symfony\Component\Serializer\Encoder\JsonEncoder;
2021
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
2122
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
2223
use Symfony\Component\Serializer\Exception\LogicException;
24+
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
2325
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
2426
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
2527
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
@@ -57,6 +59,9 @@
5759
use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberTwo;
5860
use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumConstructor;
5961
use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty;
62+
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithNotNormalizable;
63+
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithObjectOrBool;
64+
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithObjectOrNull;
6065
use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy;
6166
use Symfony\Component\Serializer\Tests\Fixtures\FooImplementationDummy;
6267
use Symfony\Component\Serializer\Tests\Fixtures\FooInterfaceDummyDenormalizer;
@@ -842,6 +847,44 @@ public function testTrueBuiltInTypes()
842847
$this->assertEquals(new TrueBuiltInDummy(), $actual);
843848
}
844849

850+
public function testDeserializeUntypedFormat()
851+
{
852+
$serializer = new Serializer([new ObjectNormalizer(propertyTypeExtractor: new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))], ['csv' => new CsvEncoder()]);
853+
$actual = $serializer->deserialize('value'.PHP_EOL.',',DummyWithObjectOrNull::class, 'csv', [CsvEncoder::AS_COLLECTION_KEY => false]);
854+
855+
$this->assertEquals(new DummyWithObjectOrNull(null), $actual);
856+
}
857+
858+
public function testDenormalizeUntypedFormat()
859+
{
860+
$serializer = new Serializer([new ObjectNormalizer(propertyTypeExtractor: new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]);
861+
$actual = $serializer->denormalize(['value' => ''],DummyWithObjectOrNull::class, 'xml');
862+
863+
$this->assertEquals(new DummyWithObjectOrNull(null), $actual);
864+
}
865+
866+
public function testDenormalizeUntypedFormatNotNormalizable()
867+
{
868+
$this->expectException(NotNormalizableValueException::class);
869+
$serializer = new Serializer([new CustomNormalizer(), new ObjectNormalizer(propertyTypeExtractor: new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]);
870+
$serializer->denormalize(['value' => 'test'],DummyWithNotNormalizable::class, 'xml');
871+
}
872+
873+
public function testDenormalizeUntypedFormatMissingArg()
874+
{
875+
$this->expectException(MissingConstructorArgumentsException::class);
876+
$serializer = new Serializer([new ObjectNormalizer(propertyTypeExtractor: new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]);
877+
$serializer->denormalize(['value' => 'invalid'],DummyWithObjectOrNull::class, 'xml');
878+
}
879+
880+
public function testDenormalizeUntypedFormatScalar()
881+
{
882+
$serializer = new Serializer([new ObjectNormalizer(propertyTypeExtractor: new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]);
883+
$actual = $serializer->denormalize(['value' => 'false'],DummyWithObjectOrBool::class, 'xml');
884+
885+
$this->assertEquals(new DummyWithObjectOrBool(false), $actual);
886+
}
887+
845888
private function serializerWithClassDiscriminator()
846889
{
847890
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());

0 commit comments

Comments
 (0)
0