From ab913397b9aeee6d89277dded14fa8259dc1e46d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 29 Dec 2024 22:22:56 +0100 Subject: [PATCH 1/3] Fix exception thrown by YamlEncoder --- Encoder/YamlEncoder.php | 8 +++++++- Tests/Encoder/YamlEncoderTest.php | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index 223cd79333f..1013129db8d 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -11,8 +11,10 @@ namespace Symfony\Component\Serializer\Encoder; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Yaml\Dumper; +use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Parser; use Symfony\Component\Yaml\Yaml; @@ -85,7 +87,11 @@ public function decode(string $data, string $format, array $context = []): mixed { $context = array_merge($this->defaultContext, $context); - return $this->parser->parse($data, $context[self::YAML_FLAGS]); + try { + return $this->parser->parse($data, $context[self::YAML_FLAGS]); + } catch (ParseException $e) { + throw new NotEncodableValueException($e->getMessage(), $e->getCode(), $e); + } } public function supportsDecoding(string $format): bool diff --git a/Tests/Encoder/YamlEncoderTest.php b/Tests/Encoder/YamlEncoderTest.php index 33ee49f5d6b..f647fe4233c 100644 --- a/Tests/Encoder/YamlEncoderTest.php +++ b/Tests/Encoder/YamlEncoderTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Encoder\YamlEncoder; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Yaml\Yaml; /** @@ -81,4 +82,12 @@ public function testContext() $this->assertEquals(['foo' => $obj], $encoder->decode("foo: !php/object 'O:8:\"stdClass\":1:{s:3:\"bar\";i:2;}'", 'yaml')); $this->assertEquals(['foo' => null], $encoder->decode("foo: !php/object 'O:8:\"stdClass\":1:{s:3:\"bar\";i:2;}'", 'yaml', [YamlEncoder::YAML_FLAGS => 0])); } + + public function testInvalidYaml() + { + $encoder = new YamlEncoder(); + + $this->expectException(NotEncodableValueException::class); + $encoder->decode("\t", 'yaml'); + } } From ff34e9f1d46a8e918a8ddc2a4ff384eaca45ce7f Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 14 Jan 2025 14:22:11 +0100 Subject: [PATCH 2/3] [Serializer] [ObjectNormalizer] Filter int when using FILTER_BOOL --- Normalizer/AbstractObjectNormalizer.php | 4 +-- .../AbstractObjectNormalizerTest.php | 31 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index aad68f7ba04..1860425f9f3 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -569,7 +569,7 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass return (float) $data; } - if (LegacyType::BUILTIN_TYPE_BOOL === $builtinType && \is_string($data) && ($context[self::FILTER_BOOL] ?? false)) { + if (LegacyType::BUILTIN_TYPE_BOOL === $builtinType && (\is_string($data) || \is_int($data)) && ($context[self::FILTER_BOOL] ?? false)) { return filter_var($data, \FILTER_VALIDATE_BOOL, \FILTER_NULL_ON_FAILURE); } @@ -854,7 +854,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string return (float) $data; } - if (TypeIdentifier::BOOL === $typeIdentifier && \is_string($data) && ($context[self::FILTER_BOOL] ?? false)) { + if (TypeIdentifier::BOOL === $typeIdentifier && (\is_string($data) || \is_int($data)) && ($context[self::FILTER_BOOL] ?? false)) { return filter_var($data, \FILTER_VALIDATE_BOOL, \FILTER_NULL_ON_FAILURE); } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index b4f5c103ca7..27f3c208499 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1216,15 +1216,34 @@ public static function provideDenormalizeWithFilterBoolData(): array { return [ [['foo' => 'true'], true], + [['foo' => 'True'], true], + [['foo' => 'TRUE'], true], [['foo' => '1'], true], + [['foo' => 1], true], [['foo' => 'yes'], true], + [['foo' => 'Yes'], true], + [['foo' => 'YES'], true], + [['foo' => 'on'], true], + [['foo' => 'On'], true], + [['foo' => 'ON'], true], [['foo' => 'false'], false], + [['foo' => 'False'], false], + [['foo' => 'FALSE'], false], [['foo' => '0'], false], + [['foo' => 0], false], [['foo' => 'no'], false], + [['foo' => 'No'], false], + [['foo' => 'NO'], false], + [['foo' => 'off'], false], + [['foo' => 'Off'], false], + [['foo' => 'OFF'], false], [['foo' => ''], false], [['foo' => null], null], [['foo' => 'null'], null], [['foo' => 'something'], null], + [['foo' => 'foo'], null], + [['foo' => 1234567890], null], + [['foo' => -1234567890], null], ]; } @@ -1253,10 +1272,7 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string public function testTemplateTypeWhenAnObjectIsPassedToDenormalize() { - $normalizer = new class ( - classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), - propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()]) - ) extends AbstractObjectNormalizerDummy { + $normalizer = new class(classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()])) extends AbstractObjectNormalizerDummy { protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { return true; @@ -1279,10 +1295,7 @@ public function testDenormalizeTemplateType() $this->markTestSkipped('The PropertyInfo component before Symfony 7.1 does not support template types.'); } - $normalizer = new class ( - classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), - propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()]) - ) extends AbstractObjectNormalizerDummy { + $normalizer = new class(classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()])) extends AbstractObjectNormalizerDummy { protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { return true; @@ -1587,7 +1600,7 @@ class TruePropertyDummy class BoolPropertyDummy { - /** @var null|bool */ + /** @var bool|null */ public $foo; } From 6ad986f62276da4c8c69754decfaa445a89cb6e3 Mon Sep 17 00:00:00 2001 From: Valmonzo Date: Fri, 15 Nov 2024 16:13:35 +0100 Subject: [PATCH 3/3] [Serializer] fix default context in Serializer --- DependencyInjection/SerializerPass.php | 1 + Serializer.php | 8 +++--- .../SerializerPassTest.php | 7 +++-- Tests/SerializerTest.php | 26 +++++++++++++++++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index d0b0deb48cf..c2959ecdac3 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -56,6 +56,7 @@ public function process(ContainerBuilder $container) } $container->getParameterBag()->remove('serializer.default_context'); + $container->getDefinition('serializer')->setArgument('$defaultContext', $defaultContext); } if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { diff --git a/Serializer.php b/Serializer.php index 7044c2f207b..e17042097fe 100644 --- a/Serializer.php +++ b/Serializer.php @@ -84,10 +84,12 @@ class Serializer implements SerializerInterface, ContextAwareNormalizerInterface /** * @param array $normalizers * @param array $encoders + * @param array $defaultContext */ public function __construct( private array $normalizers = [], array $encoders = [], + private array $defaultContext = [], ) { foreach ($normalizers as $normalizer) { if ($normalizer instanceof SerializerAwareInterface) { @@ -163,12 +165,12 @@ public function normalize(mixed $data, ?string $format = null, array $context = return $data; } - if (\is_array($data) && !$data && ($context[self::EMPTY_ARRAY_AS_OBJECT] ?? false)) { + if (\is_array($data) && !$data && ($context[self::EMPTY_ARRAY_AS_OBJECT] ?? $this->defaultContext[self::EMPTY_ARRAY_AS_OBJECT] ?? false)) { return new \ArrayObject(); } if (is_iterable($data)) { - if ($data instanceof \Countable && ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && !\count($data)) { + if ($data instanceof \Countable && ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? $this->defaultContext[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && !\count($data)) { return new \ArrayObject(); } @@ -220,7 +222,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type)); } - if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) { + if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]) || isset($this->defaultContext[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) { unset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]); $context['not_normalizable_value_exceptions'] = []; $errors = &$context['not_normalizable_value_exceptions']; diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index eb77263f49f..b2f4fa7ad6a 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -77,9 +77,11 @@ public function testServicesAreOrderedAccordingToPriority() public function testBindSerializerDefaultContext() { + $context = ['enable_max_depth' => true]; + $container = new ContainerBuilder(); $container->setParameter('kernel.debug', false); - $container->register('serializer')->setArguments([null, null]); + $container->register('serializer')->setArguments([null, null, []]); $container->setParameter('serializer.default_context', ['enable_max_depth' => true]); $definition = $container->register('n1')->addTag('serializer.normalizer')->addTag('serializer.encoder'); @@ -87,7 +89,8 @@ public function testBindSerializerDefaultContext() $serializerPass->process($container); $bindings = $definition->getBindings(); - $this->assertEquals($bindings['array $defaultContext'], new BoundArgument(['enable_max_depth' => true], false)); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false)); + $this->assertEquals($context, $container->getDefinition('serializer')->getArgument('$defaultContext')); } public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData() diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 8f60ae1d442..8a8a54e9817 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1652,6 +1652,32 @@ public function testPartialDenormalizationWithInvalidVariadicParameter() DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, ]); } + + public function testEmptyArrayAsObjectDefaultContext() + { + $serializer = new Serializer( + defaultContext: [Serializer::EMPTY_ARRAY_AS_OBJECT => true], + ); + $this->assertEquals(new \ArrayObject(), $serializer->normalize([])); + } + + public function testPreserveEmptyObjectsAsDefaultContext() + { + $serializer = new Serializer( + defaultContext: [AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true], + ); + $this->assertEquals(new \ArrayObject(), $serializer->normalize(new \ArrayIterator())); + } + + public function testCollectDenormalizationErrorsDefaultContext() + { + $data = ['variadic' => ['a random string']]; + $serializer = new Serializer([new UidNormalizer(), new ObjectNormalizer()], [], [DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true]); + + $this->expectException(PartialDenormalizationException::class); + + $serializer->denormalize($data, DummyWithVariadicParameter::class); + } } class Model