Mettez en place un CRUD

Mettez en place un CRUD

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. 

Quand nous faisons la requĂȘte DELETE, nous obtenons bien un status code 204 No Content
VĂ©rification avec Postman de l’appel

VĂ©rifions Ă©galement directement sur phpMyAdmin. Ici, j’ai voulu supprimer le livre avec l’id 1 :

Sur phpMyAdmin, on voit que l’élĂ©ment que nous venons de supprimer n’est plus dans la table
Vérification sur phpMyAdmin de la suppression réelle

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 :

Dans Postman, on vĂ©rifie que la requĂȘte est en POST, et que le JSON pour le livre est dans le Body
Test avec Postman de la crĂ©ation d’un livre

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 :

En renseignant les informations dans Postman et envoyant la requĂȘte, nous voyons dans le Body que l’élĂ©ment est créé et que l’on a un status code 201 Created
Test avec Postman de la crĂ©ation d’un livre : 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 retrouvons Location dans l’onglet Headers de Postman
Test avec Postman : vérification du header Location dans la réponse

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

Ever considered an OpenClassrooms diploma?
  • Up to 100% of your training program funded
  • Flexible start date
  • Career-focused projects
  • Individual mentoring
Find the training program and funding option that suits you best