Skip to content

[Serializer] Error when normalizing object with uninitialized typed properties when normalizer is loaded with class metadata factory. #40578

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
vuryss opened this issue Mar 25, 2021 · 8 comments · Fixed by #41615

Comments

@vuryss
Copy link
Contributor

vuryss commented Mar 25, 2021

Symfony version(s) affected: 5.2.4

Description
When using simple normalization - the fix introduced with this pull request: #38900 is working correctly. This is because the property metadata is read with reflection during the process and if the property is not initialized the above PR does not include it.

But when we're using groups to perform normalization, then we have to supply a class metadata factory that reads those. After using the factory, however, the typed properties are not checked whether they are initialized or not and are directly included.
This results in the same error that the above PR is trying to fix, namely:

PHP Fatal error:  Uncaught Error: Typed property DataClass::$unInitialized must not be accessed before initialization in /path/to/vendor/symfony/property-access/PropertyAccessor.php:438

How to reproduce

class DataClass
{
    #[Groups(['foo'])]
    public string $unInitialized;

    #[Groups(['foo'])]
    public string $initialized = 'value';

    #[Groups(['bar'])]
    public string $initialized2 = 'value';
}

$object = new DataClass();

$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new ObjectNormalizer($classMetadataFactory);
$normalizer->normalize($object, context: ['groups' => 'foo']);

Possible Solution

May be include whether the property is initialized in the metadata? But that would result in an issue when it's cached.
May be when the property accessor tries to read it and it founds that it's not initialized it can be silently excluded from the result?

Additional context
I'm using PHP8, found that bug when I was using API Platform, but it ultimately goes down to how the normalizer work in Symfony.

@dshiryaev-plesk
Copy link

Workaround: create constructor for your entity class.

@vuryss
Copy link
Contributor Author

vuryss commented Mar 25, 2021

Workaround: create constructor for your entity class.

That's not a solution, cause I want them to be without a value until there is actually something to store. I want to be able to avoid the NULLs all over the place. Or the empty initial value too.

If the field is not set - it should simply be ignored and not throw an error, at least this sound logical - cause this is how it works without the mappings.

@Tjeerd
Copy link

Tjeerd commented Mar 26, 2021

A possible 'fix' I mentioned over at api-platform: api-platform/core#3974 (comment), but, as said there, I have a very hard time determining if this is just a 'fix' for the issue at hand, or a constructive fix for the Serializer/Normalizer as a whole?

@ro0NL
Copy link
Contributor

ro0NL commented Mar 26, 2021

IIUC using AbstractNormalizer::IGNORED_ATTRIBUTES could work per context?

from a mapping POV, should it work like ignore: {null: false, uninitialized: true}?

@vuryss
Copy link
Contributor Author

vuryss commented Apr 7, 2021

I don't know how that could work with mapping directly, because field initialization is dynamic during execution and mappings are static and cached most of the time.

@ro0NL
Copy link
Contributor

ro0NL commented Apr 13, 2021

the mapping would simply tell us to ignore must not be accessed before initialization at runtime. Or value null explicitly.

@echantigny
Copy link

From what I understand, this seems to still be an issue when properties are declared with their type instead of annotations

This WORKS:

/*
 * @var string|null
 */
public $firstName;

This DOESN'T and still gives the error.
public ?string $firstName;

@Sorenjin
Copy link

Sorenjin commented Jul 7, 2022

Error happens if DEEP_OBJECT_TO_POPULATE is set.
\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::denormalize:

            if ($attributeContext[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) {
                try {
                    $attributeContext[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
                } catch (NoSuchPropertyException $e) {
                }
            }

javiereguiluz added a commit to javiereguiluz/symfony-docs that referenced this issue Oct 26, 2022
…ED_VALUES option (vuryss, ivannemets-sravniru)

This PR was merged into the 5.4 branch.

Discussion
----------

[Serializer] Documenting the new SKIP_UNINITIALIZED_VALUES option

Documents changes introduced here: symfony/symfony#41615
Original issue: symfony/symfony#40578
Docs issue: symfony#15785

Commits
-------

08599e8 Update serializer.rst
034f0f6 [Serializer] Documenting the new SKIP_UNINITIALIZED_VALUES option
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
7 participants