Description
Symfony version(s) affected
6.3.x,5.4.x
Description
The AbstractNormalizer::instantiateObject
unsets the corresponding &$data
value for a constructor argument if it believes it will be set by constructor invocation so that no set
is called later on. However, if constructor invocation fails as it is missing arguments, the value won't be set again at a later stage, even if the object could be instantiated partially when setting the property later on.
How to reproduce
If we have an example object
class ExampleObject
{
public function __construct(public readonly string $propertyMissing, public readonly string $propertyGiven)
{
}
}
I would expect the following test to pass:
try {
$exampleObject = $serializer->denormalize(['propertyGiven' => 'foo'],
ExampleObject::class,
null,
[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true]);
} catch (PartialDenormalizationException $e) {
$exampleObject = $e->getData();
}
self::assertInstanceOf(ExampleObject::class, $exampleObject);
self::assertEquals('foo', $exampleObject->propertyGiven);
however it says: $propertyGiven must not be accessed before initialization
as there was no attempt to set it later on after the constructor instantiation failed.
Possible Solution
Remember the properties that have to be unset if the constructor instantiation succeeds and only unset them if there are no missing parameters and the instantiation actually worked.
Additional Context
I realized this as the behaviour changed with commit c54cfbb6f3129c41ea5b26b4e3115919132ab726 as it now collects all missing arguments. Before it returned early and the $data
values for later constructor arguments were not unset. So this is kind of a regression, although before it just worked "accidentially" in my use case as the first argument was already missing for my data.