-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
MapQueryString & MapRequestPayload empty when type missmatch #50904
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
Comments
Same issue for us. I asked the question on slack without success. The workaround I did is to not use the constructor: final class PayloadDTO
{
#[Assert\Length(min: 3)]
public ?string $name = null;
#[Assert\Positive]
public ?int $age = null;
} Like that you can never have an empty object. |
+1 |
same issue |
I have the same issue when using the symfony/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php Lines 593 to 597 in 0b78897
where $context[self::DISABLE_TYPE_ENFORCEMENT] evaluates to true and therefore the data is returned. If this would have evaluated to false a NotNormalizableValueException would have been thrown.
Checking where this context variable is set to symfony/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php Lines 46 to 49 in 0b78897
The fix would therefore be easy, but I'm not sure if this is the intended behaviour because enabling type enforcement would also mean that an integer given as a string (e.g. |
New findings from my side: This only happens when the following code is executed: symfony/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php Lines 177 to 179 in 0b78897
Otherwise symfony/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php Line 190 in 0b78897
So I found another component in our code that automatically converted the request data to an array. In this case, only denormalization takes place and an empty instance is created. The conversion snippet is this one here: https://medium.com/@peter.lafferty/converting-a-json-post-in-symfony-13a24c98fc0e |
I have just now stumbled upon a very similar, if not the same bug. I was trying to implement generic interfaces for some of my requests:
This makes it annoying to use generic interfaces for these mapped DTOs, I cannot DRY my DTOs by using a generic trait for the getters:
Which forces me to copy-paste the same identical getters into every DTO and change the return types from interface to implementation, even though interface absolutely should work. |
Same issue here! Any chance this PR can be prioritized for the next release? Otherwise |
I have the same issue but its "fixed" when I remove the |
I'm again debugging this problem and decided to go a little bit deeper. It's seems to be a combination of two factors: First: Disabling the type enforcement in the RequestPayloadValueResolver: symfony/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php Line 53 in ad68f73
This is normally good behavior, because if you have a "123" (string) this will be automatically converted to 123 (int). This is useful for query strings where each parameter is given as a string. Second: Insufficient validation of data in the AbstractObjectNormalizer: symfony/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php Line 625 in ad68f73
This method validates and denormalizes the data, but if I try to map a string "hello" to an integer type, no check in this method will catch the error. In the end it will reach this part: symfony/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php Lines 610 to 612 in ad68f73
And that is the main problem, because Funny thing is that types are checked this way for the XML and CSV format, e.g.: symfony/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php Lines 683 to 689 in ad68f73
|
You can write |
This has been reported again in #59104. There's a PR attached to it with the fix and a more detailed description of the issue. The fix will make it work as expected without changing your DTOs or providing a custom context to the attribute. |
… 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)
Symfony version(s) affected
6.3.1
Description
When there is a type missmatch on an objet with the attribute
MapQueryString
orMapRequestPayload
an empty instance is created.For instance if I use
MapQueryString
expecting a name (string) and age (int) but the user send a "John" and "foo", I end up with an empty object.How to reproduce
http://localhost:8000/?name=John&age=foo
, you'll end up with an emptyPayloadDTO
(which will cause bugs further down the application).Possible Solution
I tracked down the issue to the
AbstractNormalizer
that ends up instantiating the object without using the constructor (using the reflectionClass) since there is anot_normalizable_value_exceptions
in the context. https://github.com/symfony/symfony/blob/6.4/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php#L424-L428A solution would be to check the context for "collect_denormalization_errors" before skipping the exception or to collect the exception when it happens.
The text was updated successfully, but these errors were encountered: