How to deserialize a REST request with Symfony forms

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

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

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

Conclusions

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.