• 8 hours
  • Hard

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 11/27/23

Récupérez vos premières données avec le composant serializer de Symfony

Récupérez vos premières données avec le composant serializer de Symfony

Dans ce chapitre, nous allons créer des routes pour récupérer les informations que nous avons enregistrées dans la base de données au chapitre précédent.

Créez votre première route

Le plus simple pour créer notre première route est tout simplement de demander au maker de Symfony de le faire pour nous, grâce à cette nouvelle ligne de commande :

php bin/console make:controller

Symfony nous demande alors un nom pour notre nouveau contrôleur.  Comme son but va être de gérer nos livres, appelons-le tout simplement BookController .

Une fois cette commande exécutée, Symfony a créé pour nous le fichier BookController.php  .

<?php
// src\Controller\BookController.php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class BookController extends AbstractController
{
    #[Route('/book', name: 'book')]
    public function index(): Response
    {
        return $this->json([
            'message' => 'welcome to your new controller!',
            'path' => 'src/Controller/BookController.php',
        ]);
    }
}

Si nous testons cette première route en tapant l’URL dans Postman, nous pouvons déjà constater que nous obtenons bien du JSON :

La route nous envoie une réponse en JSON dans le Body de Postman.
Vérification sur Postman du bon fonctionnement de la route

Petit point de vigilance : les outils de génération de code de Symfony sont là pour faciliter les opérations, pas pour coder à notre place. Il nous incombe de vérifier que ce qui a été généré nous convient parfaitement. 

En l'occurrence, je vais faire quelques petites modifications sur le code proposé, pour le rendre plus lisible et plus fiable.

<?php
// src\Controller\BookController.php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

class BookController extends AbstractController
{
    #[Route('/api/books', name: 'book', methods: ['GET'])]
    public function getBookList(): JsonResponse
    {
        return new JsonResponse([
            'message' => 'welcome to your new controller!',
            'path' => 'src/Controller/BookController.php',
        ]);
    }
}

Voici la liste des points modifiés, j'ai :

  • ajouté /api  devant le chemin de la route. Ce n’est pas obligatoire, mais dans des applications un peu plus complexes, cela permet juste avec l’URL de la route de se rendre compte directement de ce qu’on demande : des données  (/api/…)  ou du HTML ;

  • mis books  au pluriel. En effet, nous allons retourner une liste de livres, un pluriel est plus cohérent ;

  • changé le retour. Plutôt qu’une simple Response, j’ai mis JsonResponse  . Un typage plus précis peut parfois permettre de détecter des erreurs plus tôt ; 

  • écrit  return new JsonResponse   plutôt que  return $this->json . Ces deux écritures sont presque équivalentes dans la mesure ou $this->json(...)  retourne également une JsonResponse , mais cela permet d’être plus explicite ;

  • ajouté  methods: [‘GET’] , pour préciser que cette route ne sera accessible qu’avec un appel GET. Vous pouvez faire le test, si vous tentez d’accéder à la même URL avec Postman en précisant le verbe HTTP POST, Symfony vous répondra que la route n’existe pas. Cela permet d’être cohérent avec REST. 

Récupérez de vraies données

À ce stade, nous avons une première route fonctionnelle. Mais ce que nous voulons vraiment faire, c’est retourner des données issues de la base de données

Pour cela, nous allons les récupérer grâce au Repository de Symfony, et essayer d’envoyer directement la liste des livres dans Symfony.

<?php
    // src\Controller\BookController.php
    // ...
    
    #[Route('/api/books', name: 'book', methods: ['GET'])]
    public function getBookList(BookRepository $bookRepository): JsonResponse
    {
        $bookList = $bookRepository->findAll();

        return new JsonResponse([
            'books' => $bookList,
        ]);
    }

Problème, nous obtenons un résultat qui ressemble à :

{
   "books": [
      {},
      (la même chose répétée une vingtaine de fois)
      {}
   ]
}

Visiblement nos livres sont là, mais vides !

C’est parce que PHP peine à convertir des objets (en l'occurrence ici, c’est même un tableau d’objets) en JSON.

Pour l’aider un peu, nous allons utiliser ce qu’on appelle un Serializer

Un serializer est un programme qui permet de prendre des objets complexes et de les transformer en chaîne de caractère, bien plus facile à transporter. 

Les plus courants sont des serializers qui vont convertir des éléments vers du JSON ou du XML, mais il en existe de nombreux autres. Il existe également des désérialiseurs qui permettent l’opération inverse.

Le Serializer prend un objet PHP et le rend en JSON ou XML. On peut également désérialiser du JSON ou XML pour obtenir un objet PHP.
La sérialisation / désérialisation

Pour cela, nous allons commencer par l’installer grâce à cette commande :

composer require symfony/serializer-pack

Et nous pouvons mettre à jour notre méthode :

<?php
    // src\Controller\BookController.php
    // ...
    
    #[Route('/api/books', name: 'book', methods: ['GET'])]
    public function getBookList(BookRepository $bookRepository, SerializerInterface $serializer): JsonResponse
    {
        $bookList = $bookRepository->findAll();
        $jsonBookList = $serializer->serialize($bookList, 'json');
        return new JsonResponse($jsonBookList, Response::HTTP_OK, [], true);
    }

Notez le changement sur notre JsonResponse  qui prend désormais 4 arguments :

  • les données sérialisées ;

  • le code retour : ici Response::HTTP_OK  correspond au code 200 . Ce code est celui renvoyé par défaut lorsque rien n’est précisé ;

  • les headers (qu’on laisse vides pour l’instant pour garder le comportement par défaut) ;

  • un true qui signifie que nous avons DÉJÀ sérialisé les données et qu’il n’y a donc plus de traitement à faire dessus. Par défaut, la valeur est false lorsque rien n’est précisé, donc il est important d’indiquer ce changement. 

Testons une dernière fois avec Postman : nos données sont bien présentes, c’est parfait !

Cette fois-ci, notre requête GET sur Postman nous retourne un livre avec un id, un title et un coverText !
Test sur Postman pour vérifier que l’on a bien toutes nos données.

Récupérez un livre en particulier

Nous avons réalisé la route la plus facile possible. Aucun filtre, nous obtenons toutes nos données.

Imaginons maintenant que nous voulions récupérer les données qui correspondent à un livre en particulier, en fonction de son id.

<?php
    // src\Controller\BookController.php
    // ...
    
    #[Route('/api/books/{id}', name: 'detailBook', methods: ['GET'])]
    public function getDetailBook(int $id, SerializerInterface $serializer, BookRepository $bookRepository): JsonResponse {

        $book = $bookRepository->find($id);
        if ($book) {
            $jsonBook = $serializer->serialize($book, 'json');
            return new JsonResponse($jsonBook, Response::HTTP_OK, [], true);
        }
        return new JsonResponse(null, Response::HTTP_NOT_FOUND);
   }

Notez la route qui contient désormais l’id. Nous avons gardé books  au pluriel pour faciliter l’utilisation de l’API, l’idée étant de retourner le livre avec l’id choisi parmi l’ensemble des books  . 

Notez également que si le livre n’existe pas, alors nous retournons une réponse vide avec un autre code d’erreur :  Reponse::HTTP_NOT_FOUND , la fameuse erreur 404  dont vous avez probablement déjà entendu parler. 

Découvrez la magie du ParamConverter

Nous pourrions nous arrêter ici. La récupération du livre fonctionne, le code est clair et fonctionnel. Cependant, Symfony propose un petit outil supplémentaire, appelé le ParamConverter, qui peut drastiquement simplifier ce code. 

Pour l’installer, comme toujours, vous pouvez utiliser la ligne de commande :

composer require sensio/framework-extra-bundle

Voyons donc notre code une fois simplifié :

<?php
    // src\Controller\BookController.php
    // ...
    
    #[Route('/api/books/{id}', name: 'detailBook', methods: ['GET'])]
    public function getDetailBook(Book $book, SerializerInterface $serializer): JsonResponse 
    {
        $jsonBook = $serializer->serialize($book, 'json');
        return new JsonResponse($jsonBook, Response::HTTP_OK, ['accept' => 'json'], true);
    }

Et voilà ! Seulement deux lignes ! La magie du ParamConverter permet à Symfony de trouver seul la bonne entité Book  grâce à l’id précisé dans la route !

Et pour la gestion d’erreur ? Si vous tentez de regarder un id invalide, sur Postman vous verrez un message d’erreur en HTML. Ignorez-le pour l’instant, nous verrons plus tard comment le rendre plus agréable à lire. Ce qui est important, c’est le code de retour, qui lui est bien  404 Not found   .

En résumé

  • Le contrôleur permet d’ajouter de nouvelles routes à notre API, et peut être initialisé grâce au Maker.

  • Le repository fourni par Symfony et Doctrine permet d’interroger la base de données et de récupérer son contenu dans des entités (s’il n’y a qu’un élément) ou des tableaux d’entités (s’il y a plusieurs éléments possibles).

  • Les données doivent être sérialisées (par exemple, converties en JSON) avant d’être envoyées au client.

  • Le ParamConverter permet à Symfony de deviner quelle est l’entité que nous voulons récupérer, grâce à l’identifiant passé dans la route. 

Dans ce chapitre nous avons vu comment créer un premier contrôleur, récupérer nos données et même utiliser la magie du ParamConverter pour obtenir un livre en particulier. Rendez-vous dans le prochain chapitre pour ajouter des auteurs et des autrices à nos livres !

Example of certificate of achievement
Example of certificate of achievement