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

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.
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.
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.
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Ă©.