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 :
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 misJsonResponse
. Un typage plus précis peut parfois permettre de détecter des erreurs plus tôt ;écrit
return new JsonResponse
plutôt quereturn $this->json
. Ces deux écritures sont presque équivalentes dans la mesure ou$this->json(...)
retourne également uneJsonResponse
, 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.
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 code200
. 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 !
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 !