From b150120cdb63c285f55aaade8ccc6dbdf5dae76a Mon Sep 17 00:00:00 2001 From: Jeroeny Date: Thu, 19 Oct 2023 16:18:46 +0200 Subject: [PATCH 01/10] Add `AbstractObjectNormalizer::ENABLE_TYPE_CONVERSION` for scalar type transformation --- src/Symfony/Component/Serializer/CHANGELOG.md | 5 +++ ...AbstractObjectNormalizerContextBuilder.php | 8 +++++ .../Normalizer/AbstractObjectNormalizer.php | 13 +++++++- ...ractObjectNormalizerContextBuilderTest.php | 3 ++ .../AbstractObjectNormalizerTest.php | 32 +++++++++++++++++-- 5 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index b329cf1542334..4d4bfbc74c400 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + +* Add `AbstractObjectNormalizer::ENABLE_TYPE_CONVERSION` for scalar type transformation + 7.0 --- diff --git a/src/Symfony/Component/Serializer/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php b/src/Symfony/Component/Serializer/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php index a27f00c5ba80c..19a2e6e794beb 100644 --- a/src/Symfony/Component/Serializer/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php +++ b/src/Symfony/Component/Serializer/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php @@ -61,6 +61,14 @@ public function withDisableTypeEnforcement(?bool $disableTypeEnforcement): stati return $this->with(AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT, $disableTypeEnforcement); } + /** + * Configures whether to convert scalar types to the expected type, if their value is compatible. + */ + public function withEnableTypeConversion(?bool $enableTypeConversion): static + { + return $this->with(AbstractObjectNormalizer::ENABLE_TYPE_CONVERSION, $enableTypeConversion); + } + /** * Configures whether fields with the value `null` should be output during normalization. */ diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 4ca328d591beb..b36058af7ac9b 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -57,6 +57,13 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer */ public const DISABLE_TYPE_ENFORCEMENT = 'disable_type_enforcement'; + /** + * While denormalizing, convert scalar types to the expected type. + * + * Enabled by default for the XML and CSV format, because all basic datatypes are represented as strings. + */ + public const ENABLE_TYPE_CONVERSION = 'enable_type_conversion'; + /** * Flag to control whether fields with the value `null` should be output * when normalizing or omitted. @@ -294,6 +301,10 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a return null; } + if (!isset($context[self::ENABLE_TYPE_CONVERSION]) && !isset($this->defaultContext[self::ENABLE_TYPE_CONVERSION]) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) { + $context[self::ENABLE_TYPE_CONVERSION] = true; + } + $allowedAttributes = $this->getAllowedAttributes($type, $context, true); $normalizedData = $this->prepareForDenormalization($data); $extraAttributes = []; @@ -438,7 +449,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri // if a value is meant to be a string, float, int or a boolean value from the serialized representation. // That's why we have to transform the values, if one of these non-string basic datatypes is expected. $builtinType = $type->getBuiltinType(); - if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) { + if (\is_string($data) && ($context[self::ENABLE_TYPE_CONVERSION] ?? $this->defaultContext[self::ENABLE_TYPE_CONVERSION] ?? false)) { if ('' === $data) { if (Type::BUILTIN_TYPE_ARRAY === $builtinType) { return []; diff --git a/src/Symfony/Component/Serializer/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php b/src/Symfony/Component/Serializer/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php index 410f2972b0258..1a12c1a8d4653 100644 --- a/src/Symfony/Component/Serializer/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php @@ -39,6 +39,7 @@ public function testWithers(array $values) ->withEnableMaxDepth($values[AbstractObjectNormalizer::ENABLE_MAX_DEPTH]) ->withDepthKeyPattern($values[AbstractObjectNormalizer::DEPTH_KEY_PATTERN]) ->withDisableTypeEnforcement($values[AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT]) + ->withEnableTypeConversion($values[AbstractObjectNormalizer::ENABLE_TYPE_CONVERSION]) ->withSkipNullValues($values[AbstractObjectNormalizer::SKIP_NULL_VALUES]) ->withSkipUninitializedValues($values[AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES]) ->withMaxDepthHandler($values[AbstractObjectNormalizer::MAX_DEPTH_HANDLER]) @@ -59,6 +60,7 @@ public static function withersDataProvider(): iterable AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true, AbstractObjectNormalizer::DEPTH_KEY_PATTERN => '%s_%s', AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => false, + AbstractObjectNormalizer::ENABLE_TYPE_CONVERSION => false, AbstractObjectNormalizer::SKIP_NULL_VALUES => true, AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => false, AbstractObjectNormalizer::MAX_DEPTH_HANDLER => static function (): void {}, @@ -71,6 +73,7 @@ public static function withersDataProvider(): iterable AbstractObjectNormalizer::ENABLE_MAX_DEPTH => null, AbstractObjectNormalizer::DEPTH_KEY_PATTERN => null, AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => null, + AbstractObjectNormalizer::ENABLE_TYPE_CONVERSION => null, AbstractObjectNormalizer::SKIP_NULL_VALUES => null, AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => null, AbstractObjectNormalizer::MAX_DEPTH_HANDLER => null, diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index 4ebe11d915767..aedc8f3ab18df 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -627,7 +627,10 @@ public function getTypeForMappedObject($object): ?string $this->assertInstanceOf(AbstractDummySecondChild::class, $denormalizedData); } - public function testDenormalizeBasicTypePropertiesFromXml() + /** + * @dataProvider getUntypedContexts + */ + public function testDenormalizeBasicTypePropertiesConversion(string $format, array $context = []) { $denormalizer = $this->getDenormalizerForObjectWithBasicProperties(); @@ -648,7 +651,8 @@ public function testDenormalizeBasicTypePropertiesFromXml() 'floatNegInf' => '-INF', ], ObjectWithBasicProperties::class, - 'xml' + $format, + $context ); $this->assertInstanceOf(ObjectWithBasicProperties::class, $objectWithBooleanProperties); @@ -672,6 +676,30 @@ public function testDenormalizeBasicTypePropertiesFromXml() $this->assertEquals(-\INF, $objectWithBooleanProperties->floatNegInf); } + public function testDenormalizeBasicTypePropertiesThrows() + { + $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessageMatches('/must be one of "bool" \("string" given\)/'); + $denormalizer = $this->getDenormalizerForObjectWithBasicProperties(); + $denormalizer->denormalize( + [ + 'boolTrue1' => 'true', + ], + ObjectWithBasicProperties::class, + 'other', + [AbstractObjectNormalizer::ENABLE_TYPE_CONVERSION => false] + ); + } + + public function getUntypedContexts() + { + return [ + ['xml', []], + ['csv', []], + ['other', [AbstractObjectNormalizer::ENABLE_TYPE_CONVERSION => true]], + ]; + } + private function getDenormalizerForObjectWithBasicProperties() { $extractor = $this->createMock(PhpDocExtractor::class); From 18506083ce4704eccc89d2370e4a0800bd157703 Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:22:13 +0100 Subject: [PATCH 02/10] Update src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php Co-authored-by: Mathias Arlaud --- .../Serializer/Normalizer/AbstractObjectNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index b36058af7ac9b..6cbb7a94eac89 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -60,7 +60,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer /** * While denormalizing, convert scalar types to the expected type. * - * Enabled by default for the XML and CSV format, because all basic datatypes are represented as strings. + * If not defined, it will be enabled for XML and CSV format, because all basic datatypes are represented as strings. */ public const ENABLE_TYPE_CONVERSION = 'enable_type_conversion'; From 5c6caaa38366c9b4fc4c28f62dd958459ee16531 Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:23:35 +0100 Subject: [PATCH 03/10] Apply suggestions from code review --- .../Serializer/Normalizer/AbstractObjectNormalizer.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 6cbb7a94eac89..ffc365c65e2ef 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -281,6 +281,8 @@ abstract protected function extractAttributes(object $object, ?string $format = /** * Gets the attribute value. + * + * @return mixed */ abstract protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed; @@ -288,7 +290,9 @@ public function supportsDenormalization(mixed $data, string $type, ?string $form { return class_exists($type) || (interface_exists($type, false) && null !== $this->classDiscriminatorResolver?->getMappingForClass($type)); } - + /** + * @return mixed + */ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (!isset($context['cache_key'])) { From a25059c2283ba9ff9ea7f1cf7a6347cf463a7bce Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:24:28 +0100 Subject: [PATCH 04/10] Update src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php Co-authored-by: Mathias Arlaud --- .../Tests/Normalizer/AbstractObjectNormalizerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index aedc8f3ab18df..d3a8d9928e346 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -676,7 +676,7 @@ public function testDenormalizeBasicTypePropertiesConversion(string $format, arr $this->assertEquals(-\INF, $objectWithBooleanProperties->floatNegInf); } - public function testDenormalizeBasicTypePropertiesThrows() + public function testDenormalizeBasicTypePropertiesThrowsWithoutTypeConversion() { $this->expectException(NotNormalizableValueException::class); $this->expectExceptionMessageMatches('/must be one of "bool" \("string" given\)/'); From c683008094d756b9a3de9d54680f5a74b9d17df6 Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:24:34 +0100 Subject: [PATCH 05/10] Update src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php Co-authored-by: Mathias Arlaud --- .../Tests/Normalizer/AbstractObjectNormalizerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index d3a8d9928e346..cbf273b0f3b99 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -691,7 +691,7 @@ public function testDenormalizeBasicTypePropertiesThrowsWithoutTypeConversion() ); } - public function getUntypedContexts() + public function denormalizeBasicTypePropertiesConversionDataProvider() { return [ ['xml', []], From cd6bf70ba89dd73f4d9cc9aea0878487a2c06b15 Mon Sep 17 00:00:00 2001 From: Jeroeny Date: Thu, 16 Nov 2023 10:30:19 +0100 Subject: [PATCH 06/10] Set test dataprovider --- .../Tests/Normalizer/AbstractObjectNormalizerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index cbf273b0f3b99..befa298ec1c62 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -628,7 +628,7 @@ public function getTypeForMappedObject($object): ?string } /** - * @dataProvider getUntypedContexts + * @dataProvider denormalizeBasicTypePropertiesConversionDataProvider */ public function testDenormalizeBasicTypePropertiesConversion(string $format, array $context = []) { @@ -691,7 +691,7 @@ public function testDenormalizeBasicTypePropertiesThrowsWithoutTypeConversion() ); } - public function denormalizeBasicTypePropertiesConversionDataProvider() + public function denormalizeBasicTypePropertiesConversionDataProvider(): array { return [ ['xml', []], From 27a414d73f465500922f190961687beddaa80553 Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:37:58 +0100 Subject: [PATCH 07/10] Apply suggestions from code review --- .../Serializer/Normalizer/AbstractObjectNormalizer.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index ffc365c65e2ef..e1028238349ba 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -281,8 +281,6 @@ abstract protected function extractAttributes(object $object, ?string $format = /** * Gets the attribute value. - * - * @return mixed */ abstract protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed; @@ -290,9 +288,6 @@ public function supportsDenormalization(mixed $data, string $type, ?string $form { return class_exists($type) || (interface_exists($type, false) && null !== $this->classDiscriminatorResolver?->getMappingForClass($type)); } - /** - * @return mixed - */ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (!isset($context['cache_key'])) { From f55315c91d637f672c067e288e35c60f5ac9a754 Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:39:01 +0100 Subject: [PATCH 08/10] Update src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php Co-authored-by: Mathias Arlaud --- .../Serializer/Normalizer/AbstractObjectNormalizer.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index e1028238349ba..53544ef26b502 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -300,9 +300,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a return null; } - if (!isset($context[self::ENABLE_TYPE_CONVERSION]) && !isset($this->defaultContext[self::ENABLE_TYPE_CONVERSION]) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) { - $context[self::ENABLE_TYPE_CONVERSION] = true; - } + $context[self::ENABLE_TYPE_CONVERSION] = $context[self::ENABLE_TYPE_CONVERSION] ?? (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format); $allowedAttributes = $this->getAllowedAttributes($type, $context, true); $normalizedData = $this->prepareForDenormalization($data); From 67c1c501ddf02dcbab023de0890e243aa49a8a94 Mon Sep 17 00:00:00 2001 From: Jeroeny Date: Thu, 16 Nov 2023 10:46:40 +0100 Subject: [PATCH 09/10] Fix newline --- .../Component/Serializer/Normalizer/AbstractObjectNormalizer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 53544ef26b502..eeb56cb2f6f1c 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -288,6 +288,7 @@ public function supportsDenormalization(mixed $data, string $type, ?string $form { return class_exists($type) || (interface_exists($type, false) && null !== $this->classDiscriminatorResolver?->getMappingForClass($type)); } + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (!isset($context['cache_key'])) { From d4397a18ae3196cd9cdd4a30f86e92d945f25a41 Mon Sep 17 00:00:00 2001 From: Jeroen <1517978+Jeroeny@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:15:55 +0100 Subject: [PATCH 10/10] Update src/Symfony/Component/Serializer/CHANGELOG.md Co-authored-by: Oskar Stark --- src/Symfony/Component/Serializer/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 4d4bfbc74c400..64bb1e8952997 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 7.1 --- -* Add `AbstractObjectNormalizer::ENABLE_TYPE_CONVERSION` for scalar type transformation + * Add `AbstractObjectNormalizer::ENABLE_TYPE_CONVERSION` for scalar type transformation 7.0 ---