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

Skip to content

[Serializer] Constructor arguments are mapped wrong if request is missing an argument #52422

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
Havrin opened this issue Nov 2, 2023 · 1 comment

Comments

@Havrin
Copy link

Havrin commented Nov 2, 2023

Symfony version(s) affected

6.3.6

Description

This issue rose while using the MapRequestPayload Attribute on a request.

Consider the following DTO and Controller:

final class ChangePassword
{
    #[SerializedName(serializedName: 'old_password')]
    private ?string $oldPassword = null;

    public function __construct(
        #[Assert\NotNull,
            Assert\NotBlank,
            Assert\Email]
        private string $email,
        #[Assert\NotNull,
            Assert\NotBlank]
        private string $password,
        ?string $oldPassword = null
    ) {
        $this->oldPassword = $oldPassword;
    }

    public function getEmail(): string
    {
        return $this->email;
    }

    public function getPassword(): string
    {
        return $this->password;
    }

    public function getOldPassword(): ?string
    {
        return $this->oldPassword;
    }
}
   #[Route(path: 'api/change-password', name: 'change-password', methods: ['POST'])]
    public function changePasswordAction(#[MapRequestPayload] ChangePassword $changePassword): JsonResponse
    {
        return new JsonResponse($changePassword->getEmail());
    }

When calling this route with

{
  "password": "abc",
  "old_password": "def"
}

the created object looks like this:

object(App\DTO\ChangePassword)#2315 (3) {
["oldPassword":"App\DTO\ChangePassword":private]=>
NULL
["email":"App\DTO\ChangePassword":private]=>
string(3) "abc"
["password":"App\DTO\ChangePassword":private]=>
string(3) "def"
}

the values are not correctly mapped

How to reproduce

A simple way to reproduce it is described above.

It's also possible to use my github project: https://github.com/Havrin/symfony-payload-bug

start it and throw the following request against https://localhost/api/change-password

{
  "password": "abc",
  "old_password": "def"
}

Possible Solution

While debugging I noticed, that https://github.com/symfony/serializer/blob/1197823bacefc3397fafb8f10709c1574b2f69ce/Normalizer/AbstractNormalizer.php#L376 is creating an array of values with just an index-number as key.
After creating the object in https://github.com/symfony/serializer/blob/1197823bacefc3397fafb8f10709c1574b2f69ce/Normalizer/AbstractNormalizer.php#L420 the values are wrongly mapped. Seems like the code is not correctly using the name as key but rather the index-number.

Additional Context

The wrong mapping started occurring after this fix: #51907

@Havrin Havrin added the Bug label Nov 2, 2023
nicolas-grekas added a commit that referenced this issue Nov 20, 2023
This PR was merged into the 5.4 branch.

Discussion
----------

[Serializer] Fix denormalize constructor arguments

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | Fix #52499, Fix #52422
| License       | MIT

Since this PR: #51907, objects with partial constructor parameters were wrongly instantiated.

This PR fixes that issue by delegating the properties values assignment, by unsetting normalized data only when the constructor has been called properly.

This might correct #50759 as well.

Commits
-------

8f7c7ae [Serializer] Fix denormalize constructor arguments
@Havrin
Copy link
Author

Havrin commented Dec 5, 2023

Unfortunately, this is not fixed in the latest 6.4 version for me. Same Request and Project as before. Not 100% sure if this is a Serializer or MapRequestPayload Problem.
Project updated to 6.4: https://github.com/Havrin/symfony-payload-bug

Sending a request against http://localhost/api/change-password with

{
  "password": "abc",
  "old_password": "def"
}

Results in:

object(App\DTO\ChangePassword)#192 (3) {
    [
        "oldPassword":"App\DTO\ChangePassword":private
    ]=>
  NULL
  [
        "email":"App\DTO\ChangePassword":private
    ]=>
  string(3) "abc"
  [
        "password":"App\DTO\ChangePassword":private
    ]=>
  string(3) "def"
}

Check var_dump in https://github.com/Havrin/symfony-payload-bug/blob/master/src/EventSubscriber/ExceptionResponseEventSubscriber.php

Btw. maybe the nullable oldPassword is a problem. Using a not nullable oldPassword can be tested by using the same payload against http://localhost/api/change-password-without-null

{
  "password": "abc",
  "old_password": "def"
}

Results in:

object(App\DTO\ChangePasswordWithoutNull)#192 (0) {
    [
        "email":"App\DTO\ChangePasswordWithoutNull":private
    ]=>
  uninitialized(string)
  [
        "password":"App\DTO\ChangePasswordWithoutNull":private
    ]=>
  uninitialized(string)
  [
        "oldPassword":"App\DTO\ChangePasswordWithoutNull":private
    ]=>
  uninitialized(string)
}

With all validations triggered (not only for the missing email).
So I would also say that #50759 is also not fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants