In my previous post we saw, in the context of a REST application created with Symfony and PHP8, how to obtain a DTO as a parameter of an action, deserializing the json content of the request body with the Symfony serializer component and exploiting the information available in the URI using a PHP8 attribute.
The DTO thus obtained, however, contains only primitive data, so if in the request there are references to objects of our domain, these will remain as such and will not be converted into the objects themselves.
To achieve this we could use a de/serialization library, such as jms/serializer, but here we will adopt a probably less common solution, using Symfony form types.
PHP8 attribute changes
In the DTOMapper attribute we added a second parameter in the constructor to specify the form type to use to deserialize the content of the request body.
Furthermore, the mapping for the route parameters is much more flexible. In addition to supporting primitive properties (string, bool or int), it also allows you to get a domain object, specifying how to use the route parameters in an invocation of the findOneBy method on the repository of the doctrine entity that is part of the DTO.
Translate the request into a DTO
Our Value Resolver has been simplified, and the logic moved to a dedicated service to be used independently.
Building the DTO involves the following steps:
- instance creation
- mapping of route parameters in DTO properties (primitive or doctrine entity)
- deserializing the request body using a form type
- DTO validation
In addition to the default case that requires the request body to be encoded in application/json, support for multipart/form-data encoding has been added, which should also allow to handle file uploads correctly, although I have not tried because not needed in my use case.
I will highlight a point, which may not be entirely clear from reading the code. In addition to the overall validation of the DTO object, we check for errors in the form, since some violations may emerge from the definition of the form type, for example:
- fields defined as required
- validations defined in the “constraints” option
- TransformationFailedException errors thrown in any DataTransformer used
Detected errors are converted into objects of the ConstraintViolation class, the same with which the validation errors are represented, and inserted in a ValidationFailedException, to allow the handling described in the next paragraph.
Handling of validation errors
Generation of error responses, in particular following DTO validation errors and those detected in the form, is centralized thanks to the event handling infrastructure available in Symfony.
Now it’s possible to use the DTOMapper attribute in the action parameter, compared to the previous post we added the form type.
Alternatively, if the use of the PHP attribute looks clumsy, given the amount of information that we could be forced to pass, we can now use the conversion logic in the action code, having defined it as an independent service.