You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
bug #59134 [HttpKernel] Denormalize request data using the csv format when using "#[MapQueryString]" or "#[MapRequestPayload]" (except for content data) (ovidiuenache)
This PR was squashed before being merged into the 6.4 branch.
Discussion
----------
[HttpKernel] Denormalize request data using the csv format when using "#[MapQueryString]" or "#[MapRequestPayload]" (except for content data)
| Q | A
| ------------- | ---
| Branch? | 6.4
| Bug fix? | yes
| New feature? | no
| Deprecations? | no
| Issues | #59104#50904
| License | MIT
This fixes the scenario mentioned above where using `#[MapQueryString]` or `#[MapRequestPayload]` (except request content) with at least one readonly property with a type mismatch error leads to getting an object with uninitialized properties. The only properties that are set are the ones that are assigned a value via a request parameter and are NOT readonly. Moreover, if the corresponding query parameter is not provided for non-readonly property A and there is at least one other readonly property B that has a type mismatch then A will still be returned as uninitialized (even if it has a default value).
Shortly put, the instantiation fails and the values of the properties cannot be set later on.
Examples
```
class FilterDto {
public function __construct(
public readonly ?int $id = null,
public readonly ?string $name = null,
public ?string $description = null,
) {
}
}
GET https://127.0.0.1:8000/test?id=x&name=test
id: ? ?int
name: ? ?string
description: ? ?string
GET https://127.0.0.1:8000/test?id=x&name=test&description=desc
id: ? ?int
name: ? ?string
description: "desc"
```
The complete list of steps to reproduce this is provided in #59104.
The reason why this happens is because we are disabling the type enforcement of the denormalizer in the `Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver` class and when we eventually end up in the `validateAndDenormalize` method of the `Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer` class we ignore the type mismatch because of:
```
if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) {
return $data;
}
```
Thus, we get a type error when trying to create the object and we fall back to
`$reflectionClass->newInstanceWithoutConstructor();`
Then, based on the provided request data, we attempt to set the values of the properties but this process fails for the readonly properties so they stay uninitialized.
As discussed with `@nicolas`-grekas during [the hackathon at SymfonyCon Vienna 2024](https://live.symfony.com/2024-vienna-con/) the solution here is to stop disabling the type enforcement of the denormalizer. However, this alone is not enough because then we won't be able to use anything but string since this is the type that comes in the request so we also need to set the denormalization format to either `csv` or `xml`.
This comment from `the Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer` sheds some light on why:
```
// In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
// if a value is meant to be a string, float, int or a boolean value from the serialized representation.
// That's why we have to transform the values, if one of these non-string basic datatypes is expected.
```
We avoid `xml` due to some special formatting that occurs so the proposed solution uses `csv`.
Basically, we start using type enforcement + csv format where non-string values are transformed.
Commits
-------
e0957a0 [HttpKernel] Denormalize request data using the csv format when using "#[MapQueryString]" or "#[MapRequestPayload]" (except for content data)
0 commit comments