Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Allow AbstractNormalizer to use null for nullable constructor parameters without default value #40511

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
pounard opened this issue Mar 18, 2021 · 3 comments · Fixed by #40522
Closed

Comments

@pounard
Copy link
Contributor

pounard commented Mar 18, 2021

Symfony version(s) affected: All versions until 5.1 (but I guess 5.2 is too)

Description

Serializer component AbstractNormalizer attemps to guess constructor parameters, and falls back using default values when possible, which is good. Yet, it misses one use case: when you specify nullable non-optional parameter, case in which null is a valid value, not the default one, yet still valid.

It's a matter a choice, either we consider that the incoming data should explicitly set null here, or we consider that it might come from a dynamic language such as JavaScript that may remove undefined values from sent JSON: case in which we might want to fix that and consider that undefined is null.

How to reproduce

Simply write a class with a constructor as such:

class Foo
{
    public function __construct(string $foo, ?string $bar)
    {
    }
}

And attempt to denormalize using this incoming array:

$input = [
    'foo' => 'any value',
];

In this specific case, null is an allowed value for $bar, and value is not set in $input, why not simply give it null ? Actual behaviour is that the exception for missing constructor argument is raised and prevents the object from being denormalized.

Possible Solution

Easy two-line fix, in AbstractNormalizer::instantiateObject() as such:

Replace:

                } elseif (\array_key_exists($key, $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
                    $params[] = $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
                } elseif ($constructorParameter->isDefaultValueAvailable()) {
                    $params[] = $constructorParameter->getDefaultValue();
                } else {
                    throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $constructorParameter->name));
                }

Using:

                } elseif (\array_key_exists($key, $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
                    $params[] = $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
                } elseif ($constructorParameter->isDefaultValueAvailable()) {
                    $params[] = $constructorParameter->getDefaultValue();
                } elseif ($constructorParameter->hasType() && $constructorParameter->getType()->allowsNull()) {
                    $params[] = null;
                } else {
                    throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $constructorParameter->name));
                }

I just added those two lines:

                } elseif ($constructorParameter->hasType() && $constructorParameter->getType()->allowsNull()) {
                    $params[] = null;

Compatible for all PHP version from 7.0 if I read correctly https://www.php.net/manual/en/reflectiontype.allowsnull.php

@pounard
Copy link
Contributor Author

pounard commented Mar 18, 2021

I'm sorry if I misclassified this, it could be either a bug or a feature request, in my opinion it's a bug, but it would be legit for you to downgrade to a feature request.

I have no strong feeling about this, but since there's no easy other workaround than writing a custom normalizer for each class (and I basically have hundreds that matches this behaviour in my current project) then I consider the fact that working around being not easy as a bug :)

@pounard
Copy link
Contributor Author

pounard commented Mar 19, 2021

OK, I see that @ro0NL seems to like the idea, I'll try to add a PR within the week, as always, lot of work to do, not much spare time for open source :/

pounard added a commit to pounard/symfony that referenced this issue Mar 19, 2021
…nullable constructor parameter denormalization when not present in input
pounard added a commit to pounard/symfony that referenced this issue Mar 22, 2021
…nullable constructor parameter denormalization when not present in input
@chalasr chalasr closed this as completed Mar 29, 2021
chalasr added a commit that referenced this issue Mar 29, 2021
…optional nullable constructor parameters without default value (Pierre Rineau)

This PR was merged into the 4.4 branch.

Discussion
----------

[Serializer] Allow AbstractNormalizer to use null for non-optional nullable constructor parameters without default value

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #40511
| License       | MIT

Serializer component AbstractNormalizer attemps to guess constructor parameters, and falls back using default values when possible. Yet, it misses one use case: nullable non-optional parameter with value not being present in incoming input, case in which null is a valid value, not the default one, yet still valid.

This PR introduce a two-line fix that forcefully set null as value for missing from input non-optional nullable constructor parameters values.

Commits
-------

d29e433 [Serializer] AbstractNormalizer force null for non-optional nullable constructor parameter denormalization when not present in input
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants