
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.
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 :

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.
À 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.

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 !

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.
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 .
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 !