Création d'une API REST sur Symfony

27 06 2017

Création d'une API REST sur Symfony

Une API est conçue par des développeurs pour des développeurs. Le principe d'une API REST (Representational State Transfer) est de mettre à disposition des ressources à travers des url au format json ou xml. On met ainsi à disposition des url sur lesquelles un client Angular ou Symfony pourra venir effectuer des requêtes HTTP pour consommer cette API. Dans cette article nous allons voir comment créer une API REST avec Symfony.

Tout d'abord, il y a plusieurs niveaux pour définir une API REST. Les niveaux de conformité d'une API REST sont définis dans le modèle de maturité de Richardson.

  • niveau 0 : Le RPC sur HTTP en POX (Plain Old XML)
  • niveau 1 : L’utilisation de ressources différentiées
  • niveau 2 : L’utilisation des verbes HTTP
  • niveau 3 : L’utilisation des contrôles hypermédia

Une API qui répond aux 4 critères est dit pleinement REST ou Restful.

 

Utilité d'une API REST

L'utilité d'une API REST est de pouvoir lire des resources situées sur un serveur et de pouvoir les consommer depuis un autre serveur. L'échange de flux de données est ainsi qualifée de cross-domain. Pour cela nous aurons besoin de spécifier des entêtes spéciales pour autoriser un serveur à dialoguer avec un autre serveur. Car l'échange de flux de données cross-domain n'est pas autorisé par défaut. Pour cela nous aurons besoin de rajouter des entêtes spéciales de type CORS (Cross Origin Resource Sharing) avec notamment le control-access-allow-origin.

 

Configuration de Symfony

Pour créer une API REST avec Symfony, nous aurons besoin de 2 bundle:

  1. JMSSerializerBundle
  2. FOSRestBundle

JMSSerializerBundle va permettre de sérialiser les données au format json ou de les desérialiser en objet.

FOSRestBundle va permettre de simplifier la création de votre API REST grâce à une configuration spéciale de votre framework Symfony

Je n'expliquerai pas comment télécharger ces bundles, ni comment les activer. Pour cela consultez l'article Télécharger un bundle avec la commande require

Maintenant que ces 2 bundles ont été téléchargés et activés dans le appKernel.php, nous avons besoin de préciser dans le fichier config.yml la configuration de Symfony pour le bundle FOSRest:

fos_rest:
    param_fetcher_listener: true
    body_listener: true
    format_listener:
        rules:
            - { path: '^/api', priorities: ['json'], fallback_format: 'json' }
            - { path: '^/', priorities: ['html'], fallback_format: 'html' }
    view:
        view_response_listener: true
        formats:
            xml: true
            json : true
        templating_formats:
            html: true
        force_redirects:
            html: true
        failed_validation: HTTP_BAD_REQUEST
        default_engine: twig
    routing_loader:
        default_format: false
        include_format: false

 

Création d'une API REST

Nous allons maintenant créer notre controller placeController. Son rôle sera de pouvoir effectuer des actions sur des urls aux travers de verbes HTTP. Chaque action aura une méthode HTTP et une url qui lui sera propre. On pourra donc dire que notre API atteint le niveau 2 du modèle de maturité de Richardson.

Notre API va traité une entité Places qui contiendra 2 atrributs name et adress. Voici notre entité i:

<?php

namespace Blog\JournalBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Places
 * @ORM\Table(name="places", uniqueConstraints={@ORM\UniqueConstraint(name="places_name_unique",columns={"name"})})
 * @ORM\Entity(repositoryClass="Blog\JournalBundle\Repository\PlacesRepository")
 */
class Places
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     * @Assert\NotBlank()
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="address", type="string", length=255)
     * @Assert\NotBlank()
     */
    private $address;


    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Place
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set address
     *
     * @param string $address
     *
     * @return Place
     */
    public function setAddress($address)
    {
        $this->address = $address;

        return $this;
    }

    /**
     * Get address
     *
     * @return string
     */
    public function getAddress()
    {
        return $this->address;
    }
}

 

Et voici notre contrôleur placeController.php:

<?php
namespace Blog\JournalBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\View\View;
use Blog\JournalBundle\Entity\Places;
use Blog\JournalBundle\Form\PlacesType;

class PlaceController extends Controller
{

    /**
     * @Rest\View()
     * @Rest\Get("/places")
     */
    public function getPlacesAction(Request $request)
    {
        $places = $this->get('doctrine.orm.entity_manager')
                ->getRepository('JournalBundle:Places')
                ->findAll();
        
    return $places;
    }

   /**
     * @Rest\View()
     * @Rest\Get("/places/{id}")
     */
    public function getPlaceAction(Request $request)
    {
        $place = $this->get('doctrine.orm.entity_manager')
                ->getRepository('JournalBundle:Places')
                ->find($request->get('id'));
        /* @var $place Place */

        if (empty($place)) {
            return new JsonResponse(['message' => 'Place not found'], Response::HTTP_NOT_FOUND);
        }

        return $place;
    }

    /**
     * @Rest\View(statusCode=Response::HTTP_CREATED)
     * @Rest\Post("/places")
     */
    public function postPlaceAction(Request $request)
    {
        $place = new Places();
        $form = $this->createForm(PlacesType::class, $place);

        $form->submit($request->request->all());

        if ($form->isValid()) {
            $em = $this->get('doctrine.orm.entity_manager');
            $em->persist($place);
            $em->flush();
            return $place;
        } else {
            return $form;
        }
    }

     /**
     * @Rest\View(statusCode=Response::HTTP_NO_CONTENT)
     * @Rest\Delete("/places/{id}")
     */
    public function removePlaceAction(Request $request)
    {
        $em = $this->get('doctrine.orm.entity_manager');
        $place = $em->getRepository('JournalBundle:Places')
                    ->find($request->get('id'));
        /* @var $place Place */

        if ($place) {
            $em->remove($place);
            $em->flush();
        }
    }

    /**
     * @Rest\View()
     * @Rest\Put("/places/{id}")
     */
    public function updatePlaceAction(Request $request)
    {
        $em = $this->get('doctrine.orm.entity_manager');
        $place = $em->getRepository('JournalBundle:Places')
                    ->find($request->get('id'));

        if (empty($place)) {
            return new JsonResponse(['message' => 'Place not found'], Response::HTTP_NOT_FOUND);
        }

        $form = $this->createForm(PlacesType::class, $place);

        $form->submit($request->request->all());

        if ($form->isValid()) {
            $em = $this->get('doctrine.orm.entity_manager');
            $em->merge($place);
            $em->flush();
            return $place;
        } else {
            return $form;
        }
    }

    /**
     * @Rest\View()
     * @Rest\Patch("/places/{id}")
     */
    public function patchPlaceAction(Request $request)
    {
        $place = $this->get('doctrine.orm.entity_manager')
                ->getRepository('JournalBundle:Places')
                ->find($request->get('id'));

        if (empty($place)) {
            return new JsonResponse(['message' => 'Place not found'], Response::HTTP_NOT_FOUND);
        }

        $form = $this->createForm(PlacesType::class, $place);

        $form->submit($request->request->all(), false);

        if ($form->isValid()) {
            $em = $this->get('doctrine.orm.entity_manager');
            $em->merge($place);
            $em->flush();
            return $place;
        } else {
            return $form;
        }
    }
}

 

Notre API REST est à présent fonctionnelle. On va pouvoir tester notre API avec Postman. Puis nous pourrons consommer cette API avec un client comme Angular ou Symfony pour effectuer des requêtes dessus soit depuis le même serveur, soit depuis un autre serveur. Pour savoir comment faire vous pouvez lire notre article Consommer une API REST avec AngularJS.


 catégorie: Symfony



Laisser un commentaire