12. Ajouter une ressource

Occupons-nous d'ajouter de nouvelles ressources à notre API.

 

Modifions notre PhoneController

On va créer une nouvelle méthode, appelée new, qui aura les caractéristiques suivantes : 

- notre méthode new va pointer à la racine, en méthode POST

- cette méthode va renvoyer une réponse en JSON, de confirmation de création de la ressource

 

Voici notre code actualisé :

<?php

namespace App\Controller;

use App\Entity\Phone;
use App\Repository\PhoneRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;

/**
 * @Route("/api/phones")
 */
class PhoneController extends AbstractController
{
    /**
     * @Route("/{id}", name="show_phone", methods={"GET"})
     */
    public function show(Phone $phone, PhoneRepository $phoneRepository, SerializerInterface $serializer)
    {
        $phone = $phoneRepository->find($phone->getId());
        $data = $serializer->serialize($phone, 'json', [
            'groups' => ['show']
        ]);
        return new Response($data, 200, [
            'Content-Type' => 'application/json'
        ]);
    }

    /**
     * @Route("/{page<\d+>?1}", name="list_phone", methods={"GET"})
     */
    public function index(Request $request, PhoneRepository $phoneRepository, SerializerInterface $serializer)
    {
        $page = $request->query->get('page');
        if(is_null($page) || $page < 1) {
            $page = 1;
        }
        $phones = $phoneRepository->findAllPhones($page, getenv('LIMIT'));
        $data = $serializer->serialize($phones, 'json', [
            'groups' => ['list']
        ]);
        return new Response($data, 200, [
            'Content-Type' => 'application/json'
        ]);
    }

    /**
     * @Route("/", name="add_phone", methods={"POST"})
     */
    public function new(Request $request, SerializerInterface $serializer, EntityManagerInterface $entityManager)
    {
        $phone = $serializer->deserialize($request->getContent(), Phone::class, 'json');
        $entityManager->persist($phone);
        $entityManager->flush();
        $data = [
            'status' => 201,
            'message' => 'Le téléphone a bien été ajouté'
        ];
        return new JsonResponse($data, 201);
    }

}

Passons maintenant sur Postman pour vérifier que cela fonctionne :

- indiquez la méthode POST

- pointez vers l'URL http://localhost:8000/api/phones/ (pensez à mettre le slash à la fin)

- dans le body, sélectionnez raw et choisissez le type application/json.

Pensez à renseigner les informations du téléphone en json : name, price, color et description.

Soumettez votre requête et admirez le résultat :


 

Tout fonctionne parfaitement, vous venez de créer votre première ressource dans votre API.

Si vous essayez de soumettre vos données à l'URL http://localhost:8000/api/phones, vous avez une erreur qui s'affiche 😔


 

On va modifier notre contrôleur, de manière à ne pas avoir de slash à la fin de nos URL. 

Voici notre contrôleur refactorisé : 

<?php

namespace App\Controller;

use App\Entity\Phone;
use App\Repository\PhoneRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;

/**
 * @Route("/api")
 */
class PhoneController extends AbstractController
{
    /**
     * @Route("/phones/{id}", name="show_phone", methods={"GET"})
     */
    public function show(Phone $phone, PhoneRepository $phoneRepository, SerializerInterface $serializer)
    {
        $phone = $phoneRepository->find($phone->getId());
        $data = $serializer->serialize($phone, 'json', [
            'groups' => ['show']
        ]);
        return new Response($data, 200, [
            'Content-Type' => 'application/json'
        ]);
    }

    /**
     * @Route("/phones/{page<\d+>?1}", name="list_phone", methods={"GET"})
     */
    public function index(Request $request, PhoneRepository $phoneRepository, SerializerInterface $serializer)
    {
        $page = $request->query->get('page');
        if(is_null($page) || $page < 1) {
            $page = 1;
        }
        $phones = $phoneRepository->findAllPhones($page, getenv('LIMIT'));
        $data = $serializer->serialize($phones, 'json', [
            'groups' => ['list']
        ]);
        return new Response($data, 200, [
            'Content-Type' => 'application/json'
        ]);
    }

    /**
     * @Route("/phones", name="add_phone", methods={"POST"})
     */
    public function new(Request $request, SerializerInterface $serializer, EntityManagerInterface $entityManager)
    {
        $phone = $serializer->deserialize($request->getContent(), Phone::class, 'json');
        $entityManager->persist($phone);
        $entityManager->flush();
        $data = [
            'status' => 201,
            'message' => 'Le téléphone a bien été ajouté'
        ];
        return new JsonResponse($data, 201);
    }

}

Retournez sur Postman. Si vous essayez maintenant d'accéder à l'URL http://localhost:8000/api/phones, vous devez avoir une réponse au format JSON.


 

Tout fonctionne bien maintenant 😉

Ah mais attends, ici on a saisi toutes les données, mais si on en oublie une, que se passe-t-il ? 😒

Essayons ensemble, retournez sur Postman et enlevez le champ name. Soumettez vos données et...

Une erreur s'affiche 😔


 

Si vous regardez plus attentivement cette erreur, vous allez remarquer qu'elle vient de notre ExceptionSubscriber.

L'exception pour laquelle on avait mis en place le subscriber est pour une erreur de type NotFoundHttpException.

Non mais tu la sors d'où cette classe ? Comment je fais pour la retrouver si j'ai besoin de savoir quelle erreur est lancée ? 😒

Ok, je vous l'accorde, vous avez besoin de plus d'explications. Faisons une petite parenthèse et revenons sur notre ExceptionSubscriber.

Mettez le code de la méthode onKernelException en commentaires comme ici :

<?php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;

class ExceptionSubscriber implements EventSubscriberInterface
{
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        $exception = $event->getException();
        dump($exception);
        /*
        $data = [
            'status' => $exception->getStatusCode(),
            'message' => 'Resource not found'
        ];

        $response = new JsonResponse($data);
        $event->setResponse($response);
        */
    }

    public static function getSubscribedEvents()
    {
        return [
           'kernel.exception' => 'onKernelException',
        ];
    }
}

Pensez à ajouter le dump pour avoir le type d'exception lancée. Réessayez d'accéder via Postman à l'URL http://localhost:8000/api/phones/123 en méthode GET.

Voici le message affiché :

Vous voyez que l'exception est bien de type NotFoundHttpException ? 😅

Maintenant que nous savons quel est le type d'exception lancée, modifions notre ExceptionSubscriber en conséquence :

<?php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class ExceptionSubscriber implements EventSubscriberInterface
{
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        $exception = $event->getException();
        if($exception instanceof NotFoundHttpException) {
            $data = [
                'status' => $exception->getStatusCode(),
                'message' => 'Resource not found'
            ];

            $response = new JsonResponse($data);
            $event->setResponse($response);
        }
    }

    public static function getSubscribedEvents()
    {
        return [
           'kernel.exception' => 'onKernelException',
        ];
    }
}
Eh mais attends, quel est le lien avec le problème qui s'affichait tout à l'heure ?😒

Un peu de patience, vous allez voir 😝

Retournez sur Postman, essayez d'actualiser votre requête :


 

Essayez maintenant d'accéder à l'URL qui nous permettait de créer une nouvelle ressource : http://localhost:8000/api/phones (méthode POST rappelez-vous)

On a maintenant une nouvelle erreur qui s'affiche, qui nous indique que le champ name ne peut pas être NULL.

Corrigeons cette erreur dans le prochain chapitre, consacré à la validation des données.