Jusqu’à présent nous avons mis en place nos entités, un système de fixtures pour préremplir la base de données, et nous avons créé les premières routes permettant de récupérer des informations sur les auteurs et les livres.
Maintenant, l’idée va être de réaliser un CRUD (Create, Request, Update, Delete) pour pouvoir mettre à jour nos données.
Supprimez un élément avec DELETE
Parmi ces actions, comme nous avons déjà des données, le plus simple à réaliser est delete
. Nous allons commencer par lui.
Nous sommes en train de construire une API REST, donc il est important d’utiliser les bonnes méthodes HTTP. Pour supprimer un élément, sans surprise le verbe à utiliser n’est plus GET, mais DELETE.
<?php
// src\Controller\BookController.php
//...
#[Route('/api/books/{id}', name: 'deleteBook', methods: ['DELETE'])]
public function deleteBook(Book $book, EntityManagerInterface $em): JsonResponse
{
$em->remove($book);
$em->flush();
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
}
Comme vous pouvez le voir, supprimer un élément est relativement simple. Nous utilisons le même principe que pour retourner un élément, sauf qu’au lieu de forger une réponse avec l’élément, on le supprime grâce à l’ EntityManagerInterface
, et on retourne une réponse 204 - No content
.
Vérifions avec Postman.
La route est la même que pour afficher un élément, sauf qu’au lieu d’utiliser un GET qui signifie que nous voulons récupérer les données, nous utilisons DELETE qui signifie que nous voulons effacer la donnée.
Vérifions également directement sur phpMyAdmin. Ici, j’ai voulu supprimer le livre avec l’id 1 :
L’élément avec l’id 1 n’est plus présent, le premier possède l’id 2.
Le DELETE est fonctionnel, passons maintenant à la création.
Créez un nouveau livre avec POST
Sur le même principe que le DELETE vu juste au-dessus, nous allons créer une nouvelle méthode qui va utiliser un nouveau verbe HTTP, POST.
Créez un livre simple
Cependant il existe une différence majeure au niveau des données entre le DELETE et le POST. Cette fois, nous ne pouvons pas nous contenter d’envoyer une petite information comme un id directement dans l’URL. Nous devons, dans le corps du message Body, envoyer les informations sur le livre que nous voulons créer.
Une fois n’est pas coutume, je vais commencer par créer la requête sur Postman pour bien montrer ce que l’on vise :
Notez les éléments entourés :
la requête doit être en POST ;
dans l’onglet Body, je précise, ici en raw (c’est-à-dire en brut ; j’aurais pu envoyer les données également via form-data), le JSON que j’ai l’intention d’utiliser pour créer un livre.
Coté Symfony, je vais également créer une nouvelle entrée dans le contrôleur BookController.php
:
<?php
// src\Controller\BookController.php
//...
#[Route('/api/books', name:"createBook", methods: ['POST'])]
public function createBook(Request $request, SerializerInterface $serializer, EntityManagerInterface $em, UrlGeneratorInterface $urlGenerator): JsonResponse
{
$book = $serializer->deserialize($request->getContent(), Book::class, 'json');
$em->persist($book);
$em->flush();
$jsonBook = $serializer->serialize($book, 'json', ['groups' => 'getBooks']);
$location = $urlGenerator->generate('detailBook', ['id' => $book->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
return new JsonResponse($jsonBook, Response::HTTP_CREATED, ["Location" => $location], true);
}
Le chemin est le même que pour la récupération de l’ensemble des livres, avec la précision POST pour la méthode.
L’idée est de récupérer ce que l’on reçoit dans la requête (traduit ici par $request->getContent
), et cette fois-ci de désérialiser l’élément, directement dans un objet de la classe Book
!
À ce stade, nous pouvons persister l’élément.
Dernier point, il est courant de retourner l’élément créé. Pour cela, on le sérialise et on l’envoie.
Notez également l’apparition d’un $urlGenerator
, qui permet de générer la route qui pourrait être utilisée pour récupérer des informations sur le livre ainsi créé.
Là encore, lorsqu’on crée une ressource, il est d’usage de retourner un champ Location dans le header. Ce champ contient l’URL qui pourrait être interrogée si on voulait avoir plus d’informations sur l’élément créé.
Testons maintenant avec Postman !
Et voici le résultat final :
Notez en particulier l’id de l’élément créé (ici 22), et le status : 201 Created
.
On peut aussi regarder l’onglet Headers et constater la présence de notre nouvelle entrée Location :
Ajoutez un auteur au livre
Nous pourrions nous arrêter là, mais je suis certain que la plupart d’entre vous ont noté que le author
était NULL
.
Effectivement, ici nous n’avons traité que de la création d’un livre simple, mais que faire si ce livre a également un auteur ?
Plusieurs cas peuvent se présenter :
l’auteur existe dans la base de données : il faudrait simplement passer son id ici ;
l’auteur n’est pas dans la base de données :
on pourrait vouloir le créer en même temps que le livre, par exemple en passant un champ
author
qui contiendrait toutes les informations sur le nouvel auteur,il est également envisageable de ne rien rajouter ici pour garder la création d’un livre simple, et rajouter une nouvelle route
LinkBookAndAuthor
pour permettre de créer un lien entre un livre existant et un auteur existant.
Toutes ces solutions sont viables et peuvent être codées !
Je vais ici opter pour une solution intermédiaire et me contenter de passer l’id d’un auteur existant. Si celui-ci n'existe pas, charge au client de le créer au préalable.
{
"title": "Le Silmarillion",
"coverText": "Beren et Lúthien et autres histoires",
"idAuthor" : 5
}
Comme vous le voyez, j’ai passé un idAuthor
. Mais celui-ci ne correspond pas à la désérialisation “classique”.
De fait, dans le contrôleur, nous allons devoir le gérer directement :
<?php
// src\Controller\BookController.php
//...
#[Route('/api/books', name:"createBook", methods: ['POST'])]
public function createBook(Request $request, SerializerInterface $serializer, EntityManagerInterface $em, UrlGeneratorInterface $urlGenerator, AuthorRepository $authorRepository): JsonResponse
{
$book = $serializer->deserialize($request->getContent(), Book::class, 'json');
// Récupération de l'ensemble des données envoyées sous forme de tableau
$content = $request->toArray();
// Récupération de l'idAuthor. S'il n'est pas défini, alors on met -1 par défaut.
$idAuthor = $content['idAuthor'] ?? -1;
// On cherche l'auteur qui correspond et on l'assigne au livre.
// Si "find" ne trouve pas l'auteur, alors null sera retourné.
$book->setAuthor($authorRepository->find($idAuthor));
$em->persist($book);
$em->flush();
$jsonBook = $serializer->serialize($book, 'json', ['groups' => 'getBooks']);
$location = $urlGenerator->generate('detailBook', ['id' => $book->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
return new JsonResponse($jsonBook, Response::HTTP_CREATED, ["Location" => $location], true);
}
Notez comme le paramètre idAuthor
a été récupéré directement, et comme nous l’avons utilisé pour aller chercher les informations sur l’entité Author pour l’assigner au Book.
Modifiez un livre avec PUT
Maintenant que nous avons réalisé la création, il nous reste à mettre en place la modification. La méthode HTTP à utiliser sera PUT, et nous allons modifier une ressource avec un id donné dans l’URL.
Dans notre contrôleur, nous pouvons créer une nouvelle méthode updateBook
pour la mise à jour :
<?php
//...
#[Route('/api/books/{id}', name:"updateBook", methods:['PUT'])]
public function updateBook(Request $request, SerializerInterface $serializer, Book $currentBook, EntityManagerInterface $em, AuthorRepository $authorRepository): JsonResponse
{
$updatedBook = $serializer->deserialize($request->getContent(),
Book::class,
'json',
[AbstractNormalizer::OBJECT_TO_POPULATE => $currentBook]);
$content = $request->toArray();
$idAuthor = $content['idAuthor'] ?? -1;
$updatedBook->setAuthor($authorRepository->find($idAuthor));
$em->persist($updatedBook);
$em->flush();
return new JsonResponse(null, JsonResponse::HTTP_NO_CONTENT);
}
Ici nous pouvons voir une nouvelle option pour la désérialisation. Nous avons rajouté un paramètre, [AbstractNormalizer::OBJECT_TO_POPULATE]
.
L’idée ici est de désérialiser directement à l’intérieur de l’objet $currentBook
, qui correspond au livre passé dans l’URL.
On gère toujours à la main l’idAuthor
.
Le PUT peut avoir plusieurs réponses possibles. Celle qui est recommandée est de retourner une réponse vide avec un 204 - No content
. Cela signifie que l’opération s’est bien passée.
Comme l’id de la ressource est déjà connu ainsi que les modifications demandées, il n’est pas nécessaire de retourner quoi que ce soit comme information. Le code HTTP, dans la série des 200
, est suffisant pour indiquer que tout fonctionne.
Exercez-vous
Et maintenant, c’est à vous de jouer !
Essayez de réaliser le CRUD pour l’auteur, cela permettra de voir si tout est bien compris.
Il vous faudra donc bien penser à faire une suppression dite en cascade, pour supprimer les livres en même temps que l’auteur lié.
Vous vous en doutez, il existe déjà un mécanisme qui permet de le faire. Je n’en dis pas plus, je vous laisse chercher !
Pensez également à commenter votre projet ! Cela vous permettra également de revenir sur les divers éléments, et de voir s’ils sont bien compris.
En résumé
Pour supprimer un élément, il faut utiliser le verbe HTTP DELETE.
Pour créer un nouveau livre, il faut utiliser le verbe HTTP POST, et s'assurer d'envoyer les données dans un format qui sera désérialisable par le contrôleur (ici du JSON).
Il est possible de réaliser des opérations plus complexes directement au niveau du contrôleur (par exemple, associer une seconde entité).
Ça y est, notre CRUD est complet, et cela clôt également la première partie du cours. Un petit quiz vous attend pour vérifier que vous avez bien compris les notions abordées.
La seconde partie nous permettra d’aborder des points plus complexes, comme l’authentification ou encore l’autodécouvrabilité.