Skip to content

Commit

Permalink
bug #51369 [Serializer] Fix deserializing object collection propertie…
Browse files Browse the repository at this point in the history
…s (X-Coder264)

This PR was merged into the 6.3 branch.

Discussion
----------

[Serializer] Fix deserializing object collection properties

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #51261
| License       | MIT
| Doc PR        | -

On Symfony <= 6.2 serializer versions the `supportsDenormalization` was always called in `\Symfony\Component\Serializer\Serializer::getDenormalizer`.

When the `getSupportedTypes` logic was introduced for Symfony 6.3 serializer some `if` statement conditions were introduced and if they are `true` the `supportsDenormalization` logic is not called at all anymore. Those conditions currently prevent a legit use-case that worked prior to 6.3 from calling the `supportsDenormalization` method - the case being when the denormalizer is supposed to denormalize a collection/array of objects. This use-case still works even on the 6.3 serializer as long as the denormalizer does not implement the new `getSupportedTypes` method, but that is triggering a deprecation.

This PR aims to fix that so that denormalizing an array of objects still works even when using the new `getSupportedTypes` method in a denormalizer.

cc `@tucksaun` `@nicolas`-grekas

Commits
-------

32dc13432f [Serializer] Fix deserializing object collection properties
  • Loading branch information
nicolas-grekas committed Aug 23, 2023
2 parents b88affa + 8fe0b33 commit f009c34
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Serializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -359,9 +359,12 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar

$supportedTypes = $normalizer->getSupportedTypes($format);

$doesClassRepresentCollection = str_ends_with($class, '[]');

foreach ($supportedTypes as $supportedType => $isCacheable) {
if (\in_array($supportedType, ['*', 'object'], true)
|| $class !== $supportedType && ('object' !== $genericType || !is_subclass_of($class, $supportedType))
&& !($doesClassRepresentCollection && str_ends_with($supportedType, '[]') && is_subclass_of(strstr($class, '[]', true), strstr($supportedType, '[]', true)))
) {
continue;
}
Expand Down
16 changes: 16 additions & 0 deletions Tests/Fixtures/FooDummyInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;

interface FooDummyInterface
{
}
20 changes: 20 additions & 0 deletions Tests/Fixtures/FooImplementationDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;

final class FooImplementationDummy implements FooDummyInterface
{
/**
* @var string
*/
public $name;
}
50 changes: 50 additions & 0 deletions Tests/Fixtures/FooInterfaceDummyDenormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;

use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

final class FooInterfaceDummyDenormalizer implements DenormalizerInterface
{
public function denormalize(mixed $data, string $type, string $format = null, array $context = []): array
{
$result = [];
foreach ($data as $foo) {
$fooDummy = new FooImplementationDummy();
$fooDummy->name = $foo['name'];
$result[] = $fooDummy;
}

return $result;
}

public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
{
if (str_ends_with($type, '[]')) {
$className = substr($type, 0, -2);
$classImplements = class_implements($className);
\assert(\is_array($classImplements));

return class_exists($className) && \in_array(FooDummyInterface::class, $classImplements, true);
}

return false;
}

/**
* @return array<string, bool>
*/
public function getSupportedTypes(?string $format): array
{
return [FooDummyInterface::class.'[]' => false];
}
}
25 changes: 25 additions & 0 deletions Tests/Fixtures/ObjectCollectionPropertyDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;

final class ObjectCollectionPropertyDummy
{
/**
* @var FooImplementationDummy[]
*/
public $foo;

public function getFoo(): array
{
return $this->foo;
}
}
18 changes: 18 additions & 0 deletions Tests/SerializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@
use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumConstructor;
use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty;
use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy;
use Symfony\Component\Serializer\Tests\Fixtures\FooImplementationDummy;
use Symfony\Component\Serializer\Tests\Fixtures\FooInterfaceDummyDenormalizer;
use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy;
use Symfony\Component\Serializer\Tests\Fixtures\ObjectCollectionPropertyDummy;
use Symfony\Component\Serializer\Tests\Fixtures\Php74Full;
use Symfony\Component\Serializer\Tests\Fixtures\Php80WithPromotedTypedConstructor;
use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy;
Expand Down Expand Up @@ -711,6 +714,21 @@ public function testDeserializeInconsistentScalarArray()
$serializer->deserialize('["42"]', 'int[]', 'json');
}

public function testDeserializeOnObjectWithObjectCollectionProperty()
{
$serializer = new Serializer([new FooInterfaceDummyDenormalizer(), new ObjectNormalizer(null, null, null, new PhpDocExtractor())], [new JsonEncoder()]);

$obj = $serializer->deserialize('{"foo":[{"name":"bar"}]}', ObjectCollectionPropertyDummy::class, 'json');
$this->assertInstanceOf(ObjectCollectionPropertyDummy::class, $obj);

$fooDummyObjects = $obj->getFoo();
$this->assertCount(1, $fooDummyObjects);

$fooDummyObject = $fooDummyObjects[0];
$this->assertInstanceOf(FooImplementationDummy::class, $fooDummyObject);
$this->assertSame('bar', $fooDummyObject->name);
}

public function testDeserializeWrappedScalar()
{
$serializer = new Serializer([new UnwrappingDenormalizer()], ['json' => new JsonEncoder()]);
Expand Down

0 comments on commit f009c34

Please sign in to comment.