From f8a339d7f59d62a5987cbd90181e72cd0f138e49 Mon Sep 17 00:00:00 2001 From: Andrii Popov Date: Mon, 7 Oct 2019 21:36:31 +0300 Subject: [PATCH 1/5] add stdClass support to serializer --- .../Serializer/Normalizer/AbstractObjectNormalizer.php | 2 +- src/Symfony/Component/Serializer/Serializer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 1f0c33d77d4f3..81b5788e80561 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -131,7 +131,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory */ public function supportsNormalization($data, $format = null) { - return \is_object($data) && !$data instanceof \Traversable; + return \is_object($data) && !$data instanceof \Traversable && !$data instanceof \stdClass; } /** diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index 7a6c6edb38b54..762e518e7ae42 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -156,7 +156,7 @@ public function normalize($data, $format = null, array $context = []) return $data; } - if (\is_array($data) || $data instanceof \Traversable) { + if (\is_array($data) || $data instanceof \Traversable || $data instanceof \stdClass) { $normalized = []; foreach ($data as $key => $val) { $normalized[$key] = $this->normalize($val, $format, $context); From 2cd4cd7e54725792e60ab6baabca57e3a5d08925 Mon Sep 17 00:00:00 2001 From: Andrii Popov Date: Mon, 7 Oct 2019 21:36:53 +0300 Subject: [PATCH 2/5] update tests --- .../Serializer/Tests/Fixtures/EmptyDummy.php | 17 ++++++++++++ .../Serializer/Tests/SerializerTest.php | 27 ++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/EmptyDummy.php diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/EmptyDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/EmptyDummy.php new file mode 100644 index 0000000000000..62f601b908ca5 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/EmptyDummy.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +class EmptyDummy +{ + +} diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 9864c4e940360..ff7568fd68b89 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -42,6 +42,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberTwo; +use Symfony\Component\Serializer\Tests\Fixtures\EmptyDummy; use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy; use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy; use Symfony\Component\Serializer\Tests\Normalizer\TestDenormalizer; @@ -82,7 +83,7 @@ public function testNormalizeNoMatch() { $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException'); $serializer = new Serializer([$this->getMockBuilder('Symfony\Component\Serializer\Normalizer\CustomNormalizer')->getMock()]); - $serializer->normalize(new \stdClass(), 'xml'); + $serializer->normalize(new EmptyDummy(), 'xml'); } public function testNormalizeTraversable() @@ -103,7 +104,7 @@ public function testNormalizeOnDenormalizer() { $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException'); $serializer = new Serializer([new TestDenormalizer()], []); - $this->assertTrue($serializer->normalize(new \stdClass(), 'json')); + $this->assertTrue($serializer->normalize(new EmptyDummy(), 'json')); } public function testDenormalizeNoMatch() @@ -189,6 +190,26 @@ public function testSerializeScalar() $this->assertEquals('"foo"', $result); } + public function testSerializeStdClass() + { + $std = new \stdClass(); + $std->foo = ['bar', 'baz']; + $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $result = $serializer->serialize($std, 'json'); + $this->assertEquals('{"foo":["bar","baz"]}', $result); + } + + public function testSerializeArrayStdClass() + { + $stdFoo = new \stdClass(); + $stdFoo->foo = 'bar'; + $stdBar = new \stdClass(); + $stdBar->bar = 'baz'; + $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); + $result = $serializer->serialize([$stdFoo, $stdBar], 'json'); + $this->assertEquals('[{"foo":"bar"},{"bar":"baz"}]', $result); + } + public function testSerializeArrayOfScalars() { $serializer = new Serializer([], ['json' => new JsonEncoder()]); @@ -200,7 +221,7 @@ public function testSerializeArrayOfScalars() public function testSerializeEmpty() { $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]); - $data = ['foo' => new \stdClass()]; + $data = ['foo' => new EmptyDummy()]; //Old buggy behaviour $result = $serializer->serialize($data, 'json'); From 6dc50b95c903b5670760ee0106b009b68aa56338 Mon Sep 17 00:00:00 2001 From: Andrii Popov Date: Mon, 7 Oct 2019 21:37:11 +0300 Subject: [PATCH 3/5] updated changelog --- src/Symfony/Component/Serializer/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 7f213186e3c7f..207bdbc04ee57 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * deprecated the `XmlEncoder::TYPE_CASE_ATTRIBUTES` constant, use `XmlEncoder::TYPE_CAST_ATTRIBUTES` instead * added option to output a UTF-8 BOM in CSV encoder via `CsvEncoder::OUTPUT_UTF8_BOM_KEY` context option * added `ProblemNormalizer` to normalize errors according to the API Problem spec (RFC 7807) + * added support for serializing `stdClass` objects 4.3.0 ----- From 0113f40493b2487b9f1119d1aaccecf621718714 Mon Sep 17 00:00:00 2001 From: Andrii Popov Date: Wed, 9 Oct 2019 22:16:24 +0300 Subject: [PATCH 4/5] move stdclass objects normalization to abstract object normalizer --- .../Normalizer/AbstractObjectNormalizer.php | 29 ++++++++++++++++++- .../Component/Serializer/Serializer.php | 2 +- .../Serializer/Tests/SerializerTest.php | 4 +-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 81b5788e80561..f6b73c25e6855 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -131,7 +131,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory */ public function supportsNormalization($data, $format = null) { - return \is_object($data) && !$data instanceof \Traversable && !$data instanceof \stdClass; + return \is_object($data) && !$data instanceof \Traversable; } /** @@ -139,6 +139,10 @@ public function supportsNormalization($data, $format = null) */ public function normalize($object, $format = null, array $context = []) { + if (\stdClass::class === get_class($object)) { + return $this->normalizeStdClassObject($object, $format, $context); + } + if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); } @@ -630,4 +634,27 @@ private function getCacheKey(?string $format, array $context) return false; } } + + /** + * @param \stdClass $object + * @param string|null $format + * @param array $context + * + * @return mixed + * + * @throws LogicException + */ + private function normalizeStdClassObject(\stdClass $object, $format = null, array $context = []) + { + $normalized = []; + foreach ($object as $key => $val) { + if (!$this->serializer instanceof NormalizerInterface) { + throw new LogicException(sprintf('Cannot normalize value for key "%s" because the injected serializer is not a normalizer', $key)); + } + + $normalized[$key] = $this->serializer->normalize($val, $format, $context); + } + + return $normalized; + } } diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index 762e518e7ae42..7a6c6edb38b54 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -156,7 +156,7 @@ public function normalize($data, $format = null, array $context = []) return $data; } - if (\is_array($data) || $data instanceof \Traversable || $data instanceof \stdClass) { + if (\is_array($data) || $data instanceof \Traversable) { $normalized = []; foreach ($data as $key => $val) { $normalized[$key] = $this->normalize($val, $format, $context); diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index ff7568fd68b89..42d3581122896 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -83,7 +83,7 @@ public function testNormalizeNoMatch() { $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException'); $serializer = new Serializer([$this->getMockBuilder('Symfony\Component\Serializer\Normalizer\CustomNormalizer')->getMock()]); - $serializer->normalize(new EmptyDummy(), 'xml'); + $serializer->normalize(new \stdClass(), 'xml'); } public function testNormalizeTraversable() @@ -104,7 +104,7 @@ public function testNormalizeOnDenormalizer() { $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException'); $serializer = new Serializer([new TestDenormalizer()], []); - $this->assertTrue($serializer->normalize(new EmptyDummy(), 'json')); + $this->assertTrue($serializer->normalize(new \stdClass(), 'json')); } public function testDenormalizeNoMatch() From 7a06dbf67a0ff151c74f35731d28417a695d2702 Mon Sep 17 00:00:00 2001 From: Andrii Popov Date: Sun, 8 Dec 2019 21:22:41 +0200 Subject: [PATCH 5/5] sync with base branch --- .../Normalizer/AbstractObjectNormalizer.php | 8 +++----- .../Normalizer/AbstractObjectNormalizerTest.php | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index f6b73c25e6855..2211ef5f41016 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -139,7 +139,7 @@ public function supportsNormalization($data, $format = null) */ public function normalize($object, $format = null, array $context = []) { - if (\stdClass::class === get_class($object)) { + if (\stdClass::class === \get_class($object)) { return $this->normalizeStdClassObject($object, $format, $context); } @@ -636,11 +636,9 @@ private function getCacheKey(?string $format, array $context) } /** - * @param \stdClass $object * @param string|null $format - * @param array $context * - * @return mixed + * @return array * * @throws LogicException */ @@ -649,7 +647,7 @@ private function normalizeStdClassObject(\stdClass $object, $format = null, arra $normalized = []; foreach ($object as $key => $val) { if (!$this->serializer instanceof NormalizerInterface) { - throw new LogicException(sprintf('Cannot normalize value for key "%s" because the injected serializer is not a normalizer', $key)); + throw new LogicException(sprintf('Cannot normalize value for attribute "%s" because the injected serializer is not a normalizer', $key)); } $normalized[$key] = $this->serializer->normalize($val, $format, $context); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index 711f51ee9864b..e03c6963932db 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -34,6 +34,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild; use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux; +use Symfony\Component\Serializer\Tests\Fixtures\EmptyDummy; class AbstractObjectNormalizerTest extends TestCase { @@ -252,6 +253,18 @@ public function testNormalizeEmptyObject() $normalizedData = $normalizer->normalize(new EmptyDummy(), 'any', ['preserve_empty_objects' => true]); $this->assertEquals(new \ArrayObject(), $normalizedData); } + + public function testNormalizeStdClassObjectLogicException() + { + $this->expectException('Symfony\Component\Serializer\Exception\LogicException'); + $this->expectExceptionMessage('Cannot normalize value for attribute "foo" because the injected serializer is not a normalizer'); + + $std = new \stdClass(); + $std->foo = 'bar'; + $normalizer = new ObjectNormalizer(); + + $normalizer->normalize($std); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -291,10 +304,6 @@ class Dummy public $baz; } -class EmptyDummy -{ -} - class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer { public function __construct()