Lors d'une session de LiveCoding un des spectateurs m'a fait découvrir une nouvelle fonctionnalité de Symfony 6.3 que je me devais de partager avec vous : MapRequestPayload & MapQueryString
Cette nouveauté permet de transformer automatiquement le corps de la requête en objets typés (DTO) dans vos controllers (c'est d'ailleurs une fonctionnalité déjà existante dans d'autre framework comme Laravel avec les FormRequest) et permet de simplifier la logique.
Dans les versions précédentes si on voulait lire les données de la requête il fallait les décoder manuellement.
public function index (Request $request) {
$data = json_decode($request->getContent());
}
Et on pouvait se reposer sur le Serializer pour obtenir un objet typé, et le Validator pour assurer la structure des données.
public function index(
Request $request,
SerializerInterface $serializer,
ValidatorInterface $validator,
): Response
{
$data = $serializer->deserialize($request->getContent(), PaginationDTO::class, 'json');
$errors = $validator->validate($data);
if (count($errors) > 0) {
// On traite les erreurs pour les afficher
// ...
return new JsonResponse([], Response::HTTP_UNPROCESSABLE_ENTITY);
}
// L'objet et valide on peut continuer
// ...
return new JsonResponse([]);
}
Cela faisait malheureusement beaucoup de code pour un besoin assez fréquent, surtout lorsque l'on travaille sur une API.
MapRequestPayload & MapQueryString
Deux nouveaux attributs ont été ajoutés dans la version 6.3 de symfony pour simplifier ce travail. On commence par créer l'objet qui va représenter nos données.
<?php
namespace App\DTO;
use Symfony\Component\Validator\Constraints as Assert;
class PaginationDTO
{
public function __construct(
#[Assert\Positive()]
public readonly int $limit = 10,
#[Assert\Positive()]
public readonly int $page = 1,
#[Assert\Valid]
/**
* @var FilterDTO[]
*/
public readonly array $filters
){
}
}
Ensuite dans notre controller on peut ajouter un paramètre typé avec cette classe et on y ajoute l'attribut MapRequestPayload
si on veut que l'objet soit construit à partir des données de la requête et MapQueryString
si l'objet doit être construit à partir des données provenant de l'URL.
<?php
namespace App\Controller;
use App\DTO\PaginationDTO;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Annotation\Route;
class DemoController extends AbstractController
{
#[Route('/demo', name: 'app_demo')]
public function index(
#[MapRequestPayload] PaginationDTO $paginationDTO,
): Response
{
return new JsonResponse($paginationDTO);
}
}
Avec cet attribut Symfony va se charger de construire votre objet automatiquement à partir des données de la requête et validera que l'objet corresponde à vos contraintes. Si la validation échoue l'erreur sera automatiquement sérialisée et renvoyée sous forme de réponse.
Pour des cas plus complexes il est possible de personnaliser le contexte de sérialisation mais aussi de changer le resolver.
#[MapRequestPayload(
serializationContext: ['...'],
resolver: App\...\ProductReviewRequestValueResolver
)]
Ces 2 attributs vont permettre de grandement simplifier le code des controllers et rendre le travail sur les APIs beaucoup plus simple.
Si vous voulez découvrir plus de nouveauté sur la version 6.3 de symfony je vous renvoie sur l'article du blog symfony qui liste les fonctionnalités significatives de la 6.3