Skip to content

BC introduced in 3.1.15 when denormalizing a variable in a type than already been denormalized previously #6024

Closed
@MariusJam

Description

@MariusJam

API Platform version(s) affected: ^3.1.15

Description
Hi,

It took me ages to find the problem on this one but a part of our app is broken as soon as we upgrade to ^3.1.15. The BC has been indroduced with this PR : #5663 and is linked to the modifications in src/Serializer/AbstractItemNormalizer.php.

The problem happens in a very specific case: if you try to denormalize manually an item of a type that has been already previously denormalized, the denormalization doesn't hydrate correctly the object.

How to reproduce

With :

<?php

namespace App\ApiResource;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\NotExposed;
use ApiPlatform\Metadata\Post;
use App\State\Processor\DummyProcessor;
use Symfony\Component\Serializer\Annotation\Groups;

#[ApiResource(operations: [
    new NotExposed(),
    new Post(denormalizationContext: ['groups' => ['dummy:write']], processor: DummyProcessor::class
    ),
])]
class Dummy
{
    public string $bar;

    #[ApiProperty(writableLink: true)]
    #[Groups(['dummy:write'])]
    public Foo $foo;
}
<?php

namespace App\ApiResource;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use Symfony\Component\Serializer\Annotation\Groups;

#[ApiResource(operations: [new Get(normalizationContext: ['groups' => ['foo:read']],)])]
class Foo
{
    #[Groups(['foo:read'])]
    public string $bar;

    #[Groups(['dummy:write', 'foo:read'])]
    public string $name;
}
<?php

namespace App\State\Processor;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\ApiResource\Dummy;
use App\ApiResource\Foo;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

class DummyProcessor implements ProcessorInterface
{
    public function __construct(private readonly DenormalizerInterface $denormalizer)
    {
    }

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
    {
        /**
         * In real context, $foo would be retrieved via an API call using $data->foo->name
         * i.e. $foo = response from GET https://external-api.org/foos?name={$data->foo->name}
         */
        $foo = ['bar' => 'barFromExternalApi'];

        $foo = $this->denormalizer->denormalize($foo, Foo::class);

        $dummy = new Dummy();
        $dummy->bar = $foo->bar;

        return $dummy;
    }
}

If you do a POST on /dummies with the following body:

{
	"foo": {
		"name": "FooName"
	}
}

In v3.1.14, you correctly received a 201 response with the following body:

{
	"@context": "\/contexts\/Dummy",
	"@id": "\/dummies",
	"@type": "Dummy",
	"bar": "barFromExternalApi"
}

In v3.1.15, the denormalization in DummyProcessor does not hydrate correctly the Foo object and you get an Exception : "Typed property App\ApiResource\Foo::$bar must not be accessed before initialization"

Possible Solution

No real idea so far

Additional Context

I haven't been able to find the exact problem but this is linked to the $operationCacheKey used in AbstractItemNormalizer::getFactoryOptions

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions