Description
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