symfony / symfony

The Symfony PHP framework
https://symfony.com
MIT License
29.65k stars 9.43k forks source link

[Serializer] `ObjectNormalizer` breaks when used with `PropertyInfoExtractorInterface` #54787

Closed Steveb-p closed 5 months ago

Steveb-p commented 5 months ago

Symfony version(s) affected

5.4.39, 6.4.7, 7.0.7

Description

In the following commit, a change was introduced to the ObjectNormalizer constructor signature. https://github.com/symfony/symfony/commit/900d034003875d16aa7b5a293be82a9fd932c681

Now, ObjectNormalizer requires PropertyInfoExtractorInterface to be passed as last argument (or a null value). This is aliased in framework to match property_info service. https://github.com/symfony/symfony/blob/c168c2c137acee438463fcde2df1685cf74ff623/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.php#L33

So, under normal circumstances in an application that uses Symfony framework, this is filled with PropertyInfoExtractorInterface (when autowiring, for example).

If null value is passed, ReflectionExtractor object is created in it's stead. https://github.com/symfony/symfony/blob/c168c2c137acee438463fcde2df1685cf74ff623/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php#L53

However, ReflectionExtractor is not a representation of PropertyInfoExtractorInterface. https://github.com/symfony/symfony/blob/c168c2c137acee438463fcde2df1685cf74ff623/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php#L39

On the following line: https://github.com/symfony/symfony/blob/c168c2c137acee438463fcde2df1685cf74ff623/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php#L177-L178 ...the property that can contain either PropertyInfoExtractorInterface or ReflectionExtractor is used - a method getWriteInfo() is called that exists only on ReflectionExtractor. This means that PropertyInfoExtractorInterface cannot be used and will cause a PHP error, which results in inability to denormalize that object.

Ergo, in case of our app, we cannot use the property_info service (nor it's caching), and must fall back to null.

How to reproduce

We have this error...

Caused by
 Error: Call to undefined method Symfony\Component\PropertyInfo\PropertyInfoExtractor::getWriteInfo()

...happen when ObjectNormalizer is asked to denormalize an object of the following type:

$objectState = $this->denormalizer->denormalize($data, ObjectState::class, $format, $context);
final class ObjectState
{
    /** @var string */
    public $identifier;

    /** @var string */
    public $groupIdentifier;

    private function __construct(string $identifier, string $groupIdentifier)
    {
        $this->identifier = $identifier;
        $this->groupIdentifier = $groupIdentifier;
    }
}

with data (partial extract):

action: assign_object_state
identifier: locked
groupIdentifier: ez_lock

The ObjectNormalizer service is defined as follows:

    foo.serializer.object_normalizer:
        class: Symfony\Component\Serializer\Normalizer\ObjectNormalizer
        arguments:
            $propertyTypeExtractor: '@property_info'
            $propertyInfoExtractor: '@property_info'
        tags:
            - serializer.normalizer # a different tag is used in our own code since we have a dedicated serializer, but you get the idea.

Possible Solution

ObjectNormalizer, in lieu of adding PropertyInfoExtractorInterface to ReflectionExtractor, should perform checks against method presence to better inform developers about what is actually going on and/or a fallback should exist in cases where we are not dealing with ReflectionExtractor instance.

Additional Context

No response

xabbuh commented 5 months ago

see #54804

Steveb-p commented 5 months ago

Thank you very much @xabbuh for looking into it :bow:

nexik commented 4 months ago

Will this be available in 7.0.* ?

xabbuh commented 4 months ago

Yes, bugfix will be part of the next patch releases for Symfony 5.4, 6.4 and 7.0.

alexandre-le-borgne commented 4 months ago

And for the 3rd time for me, rollback to v6.4.3