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
Description
I'm trying to make my API use DTOs for both input and output and I want to reuse same classes for both input and output.
This works fine for input and output of objects without embedded objects. Also works fine for output of embedded objects: both the "parent" object and the "child" object are being transformed to DTOs before output to JSON.
However, it breaks with input of embedded objects: even though I specify an input class for a resource which I'm sending embedded, a denormalizer for that DTO class is never being called, and neither is the DTO to Entity transformer. Instead, API Platform attempts to denormalize my input directly into the Entity class, creates an entity full of null values, and then causes an exception when trying to save that very very broken value into the database.
After tracing what happens, I think this is due to how AbstractItemNormalizer works: I discovered that it actually does make a call to my child-DTO-to-Entity-transformer's supportsTransformation() method, but passes no information about the DTO class, from which the ability to transform is being queried. This causes my transformer to return false and, as a result, no transformer that is able to transform the DTO to Entity is being found, so the whole Denormalize To DTO and Transform logic, which is applied to DTOs, is being skipped, and instead an attempt is made to denormalize the array straight into the Entity, which ends up producing a blank Entity which can not and should not be saved.
Same issue seems to be happening with Api Platform 2.6.
How to reproduce
I went through the API Platform course material on SymfonyCasts, and updated the project I produced along the way with a scenario to reproduce the problem. The result is available in this branch. An easy way to see it live is to send a POST request to /api/users with the following JSON body:
{
"email": "cheesefan22@example.com",
"username": "cheesefan22",
"password": "123",
"isMe": true,
"isMvp": true,
"cheeseListings": [
{
"title": "Mysterious munster",
"price": 1500
},
{
"title": "Block of cheddar the size of your face!",
"price": 5000
}
]
}
The crucial part here is, of course, the cheeseListings array. When I send this request, I get a 500 response caused by a Doctrine Integrity constraint violation (which says that null was supplied as title). Furthermore, I've added a couple debug dumps which are shown in the profiler's Debug tab, where it's clearly visible what arguments were passed to the input DataTransformer in question and what it returned. Interestingly, both $context['input'] and $context['output'] are passed as null:
Additional Context
I think there might be a somewhat deeper problem with how supportsTransformation() is being called: right now it has to sort out what is being checked from both the context and the data that is being passed. For example, for DTO→Entity transformers, the data passed might be an array while in reality the ability to transform from DTO is being checked (like in this case). Furthermore, at least in this case $context['input'] is undefined, and $context['output'] holds a class name of the parent object, which is hardly relevant to the child object transformer.
Even the example in the official documentation looks somewhat weird, because it only suggests checking that the value of $to is correct and that $context['input']['class'] is not null, thus encouraging a blind assumption that the actual input will be acceptable if output class is what we expect. This actually makes me wonder why the not null check is even there.
Perhaps supportsTransformation() should accept an additional argument which would specify object class or scalar type of the original data? I reckon that most transformers could just check that value in combination with $to instead of looking at actual data.
Alternatively, perhaps $data could be made more useful than it is now. What's the point of passing an unserialized JSON array there, if you're checking support for transfroming from a DTO?
The text was updated successfully, but these errors were encountered:
API Platform version(s) affected: 2.5.10
Description
I'm trying to make my API use DTOs for both input and output and I want to reuse same classes for both input and output.
This works fine for input and output of objects without embedded objects. Also works fine for output of embedded objects: both the "parent" object and the "child" object are being transformed to DTOs before output to JSON.
However, it breaks with input of embedded objects: even though I specify an input class for a resource which I'm sending embedded, a denormalizer for that DTO class is never being called, and neither is the DTO to Entity transformer. Instead, API Platform attempts to denormalize my input directly into the Entity class, creates an entity full of null values, and then causes an exception when trying to save that very very broken value into the database.
After tracing what happens, I think this is due to how
AbstractItemNormalizer
works: I discovered that it actually does make a call to my child-DTO-to-Entity-transformer'ssupportsTransformation()
method, but passes no information about the DTO class, from which the ability to transform is being queried. This causes my transformer to returnfalse
and, as a result, no transformer that is able to transform the DTO to Entity is being found, so the whole Denormalize To DTO and Transform logic, which is applied to DTOs, is being skipped, and instead an attempt is made to denormalize the array straight into the Entity, which ends up producing a blank Entity which can not and should not be saved.Same issue seems to be happening with Api Platform 2.6.
How to reproduce
I went through the API Platform course material on SymfonyCasts, and updated the project I produced along the way with a scenario to reproduce the problem. The result is available in this branch. An easy way to see it live is to send a POST request to
/api/users
with the following JSON body:The crucial part here is, of course, the

cheeseListings
array. When I send this request, I get a 500 response caused by a Doctrine Integrity constraint violation (which says thatnull
was supplied astitle
). Furthermore, I've added a couple debug dumps which are shown in the profiler's Debug tab, where it's clearly visible what arguments were passed to the input DataTransformer in question and what it returned. Interestingly, both$context['input']
and$context['output']
are passed asnull
:Additional Context
I think there might be a somewhat deeper problem with how
supportsTransformation()
is being called: right now it has to sort out what is being checked from both the context and the data that is being passed. For example, for DTO→Entity transformers, the data passed might be an array while in reality the ability to transform from DTO is being checked (like in this case). Furthermore, at least in this case$context['input']
is undefined, and$context['output']
holds a class name of the parent object, which is hardly relevant to the child object transformer.Even the example in the official documentation looks somewhat weird, because it only suggests checking that the value of
$to
is correct and that$context['input']['class']
is notnull
, thus encouraging a blind assumption that the actual input will be acceptable if output class is what we expect. This actually makes me wonder why the not null check is even there.Perhaps
supportsTransformation()
should accept an additional argument which would specify object class or scalar type of the original data? I reckon that most transformers could just check that value in combination with$to
instead of looking at actual data.Alternatively, perhaps
$data
could be made more useful than it is now. What's the point of passing an unserialized JSON array there, if you're checking support for transfroming from a DTO?The text was updated successfully, but these errors were encountered: