From a0f12b667a076c6647d7d8281c7a6e3a6bd68692 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 22 May 2023 23:05:13 -0700 Subject: [PATCH] fix(serializer): disable_type_enforcement with null values (#5593) fixes #5584 --- src/Serializer/AbstractItemNormalizer.php | 2 +- .../Issue5584/DtoWithNullValue.php | 23 ++++++++++ .../Serializer/AbstractItemNormalizerTest.php | 43 +++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 tests/Fixtures/TestBundle/ApiResource/Issue5584/DtoWithNullValue.php diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index db476438554..ffa7fa02546 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -753,7 +753,7 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value return $value; } - if (null === $value && $type->isNullable()) { + if (null === $value && ($type->isNullable() || ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false))) { return $value; } diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue5584/DtoWithNullValue.php b/tests/Fixtures/TestBundle/ApiResource/Issue5584/DtoWithNullValue.php new file mode 100644 index 00000000000..a2fb3f55a0c --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue5584/DtoWithNullValue.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5584; + +use ApiPlatform\Metadata\ApiResource; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; + +#[ApiResource(denormalizationContext: [AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true])] +final class DtoWithNullValue +{ + public \stdClass $dummy; +} diff --git a/tests/Serializer/AbstractItemNormalizerTest.php b/tests/Serializer/AbstractItemNormalizerTest.php index ca4aeccac38..1b04021cce4 100644 --- a/tests/Serializer/AbstractItemNormalizerTest.php +++ b/tests/Serializer/AbstractItemNormalizerTest.php @@ -21,6 +21,7 @@ use ApiPlatform\Metadata\Property\PropertyNameCollection; use ApiPlatform\Serializer\AbstractItemNormalizer; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5584\DtoWithNullValue; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritance; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritanceChild; @@ -1347,6 +1348,48 @@ public function testDenormalizePopulatingNonCloneableObject(): void $this->assertSame($dummy, $actual); $propertyAccessorProphecy->setValue($actual, 'name', 'bar')->shouldHaveBeenCalled(); } + + public function testDenormalizeObjectWithNullDisabledTypeEnforcement(): void + { + $data = [ + 'dummy' => null, + ]; + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(DtoWithNullValue::class, [])->willReturn(new PropertyNameCollection(['dummy'])); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, nullable: true)])->withDescription('')->withReadable(true)->withWritable(true)); + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->getResourceClass(null, DtoWithNullValue::class)->willReturn(DtoWithNullValue::class); + $resourceClassResolverProphecy->isResourceClass(DtoWithNullValue::class)->willReturn(true); + + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->willImplement(NormalizerInterface::class); + + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ + $propertyNameCollectionFactoryProphecy->reveal(), + $propertyMetadataFactoryProphecy->reveal(), + $iriConverterProphecy->reveal(), + $resourceClassResolverProphecy->reveal(), + $propertyAccessorProphecy->reveal(), + null, + null, + [], + null, + null, + ]); + $normalizer->setSerializer($serializerProphecy->reveal()); + + $context = [AbstractItemNormalizer::DISABLE_TYPE_ENFORCEMENT => true]; + $actual = $normalizer->denormalize($data, DtoWithNullValue::class, null, $context); + + $this->assertInstanceOf(DtoWithNullValue::class, $actual); + $this->assertEquals(new DtoWithNullValue(), $actual); + } } class ObjectWithBasicProperties