• 50 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours est en vidéo.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Mis à jour le 27/02/2019

La sérialisation

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Nous avons mis en place le bundle FOSRestBundle pour la gestion des problématiques communes au développement d'API, ainsi que le bundle JMSSerializerBundle, qui permet l'intégration facilitée de la librairie de sérialisation des objets PHP vers JSON (et XML).

Tout au long de cette partie, nous allons créer une API en charge de la gestion d'articles. Dans ce chapitre, nous allons créer notre premier point d'entrée : la consultation des informations d'un article.

Les ressources que notre API va manipuler

Notre API est en charge de la manipulation d'articles et de ses auteurs. Je vous invite donc à créer nos entités pour pouvoir ensuite travailler avec.

Voici la première entité,Article :

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table()
 */
class Article
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private $title;

    /**
     * @ORM\Column(type="text")
     */
    private $content;

    /**
     * @ORM\ManyToOne(targetEntity="Author", cascade={"all"}, fetch="EAGER")
     */
    private $author;

    public function getId()
    {
        return $this->id;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    public function getContent()
    {
        return $this->content;
    }

    public function setContent($content)
    {
        $this->content = $content;

        return $this;
    }

    public function getAuthor()
    {
        return $this->author;
    }

    public function setAuthor(Author $author)
    {
        $this->author = $author;
    }
}

 Il ne nous reste plus qu'à créer l'entité Author (un article est rattaché à un auteur et un auteur peut être rattaché à un ou plusieurs articles), liée à l'entité Article :

<?php

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table()
 */
class Author
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     */
    private $fullname;

    /**
     * @ORM\Column(type="text")
     */
    private $biography;

    /**
     * @ORM\OneToMany(targetEntity="Article", mappedBy="author", cascade={"persist"})
     */
    private $articles;

    public function __construct()
    {
        $this->articles = new ArrayCollection();
    }

    public function getFullname()
    {
        return $this->fullname;
    }

    public function setFullname($fullname)
    {
        $this->fullname = $fullname;
    }

    public function getBiography()
    {
        return $this->biography;
    }

    public function setBiography($biography)
    {
        $this->biography = $biography;
    }

    public function getArticles()
    {
        return $this->articles;
    }
}

La gestion des routes

Nous avons vu l'importance qu'il faut porter à nos routes, en raison de la contrainte d'identifiant unique pour une ressource, ainsi que la contrainte qui impose que pour les points d'entrée, une ou plusieurs méthodes HTTP soient utilisées de manière intelligente. Le FOSRestBundle nous propose des annotations pour gérer ces deux contraintes un peu plus rapidement que l'annotation  @Route  habituelle.

Voici la liste des annotations propres aux routes que nous pourrions utiliser, avec l'indication de la méthode associée  à ce point d'entrée :

  • @Prefix("/api")

  • @Head("/articles")

  • @Get("/articles/{id}")

  • @Post("/articles")

  • @Put("/articles/{id}")

  • @Patch("/articles/{id}")

  • @Delete("/articles/{id}")

  • @Link("/articles/{id}/authors")

  • @Unlink("/articles/{id}/authors")

  • @Options("/authors")

Pour travailler avec les  requirements  comme vous le feriez avec l'annotation  @Route  , il vous suffit d'ajouter l'option à l'annotation.

Voici un exemple d'utilisation :

<?php

namespace AppBundle\Controller;

use FOS\RestBundle\Controller\Annotations\Get;

class ArticleController
{
    /**
     * @Get(
     *     path = "/articles/{id}",
     *     name = "app_article_show",
     *     requirements = {"id"="\d+"}
     * )
     */
    public function showAction()
    {
    }
}

Sérialisez une ressource

 Le FOSRestBundle nous propose de sérialiser sans que nous ayons à faire appel nous-mêmes au serviceserializer. Pour ce faire, il nous faut, dans un premier temps, ajouter la configuration suivante :

format_listener:
    rules:
        - { path: '^/', priorities: ['json'], fallback_format: 'json' }

Nous indiquons ici que pour toutes les routes commençant par  /, les objets retournés par les actions devrons être sérialisés en JSON en priorité. Comme vous pouvez le remarquer, il est possible de donner un ordre pour la sérialisation, pour l'option  priorities : en effet, en fonction de ce qui sera demandé en requête via l'entête  Content-Type, FOSRestBundle sera en mesure de savoir dans quel format il faut sérialiser la réponse. Si dans la requête le format n'est pas indiqué, c'est ce qui est indiqué dans l'option fallback_format qui prend le relais.

Il nous faut ensuite ajouter l'annotation  @View  sur l'action, comme dans l'exemple ci-dessous :

<?php

namespace AppBundle\Controller;

use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\Controller\Annotations\View;

class ArticleController
{
    /**
     * @Get(
     *     path = "/articles/{id}",
     *     name = "app_article_show",
     *     requirements = {"id"="\d+"}
     * )
     * @View
     */
    public function showAction(Article $article)
    {
        return $article;
    }
}

Il nous faut également indiquer en configuration l'élément suivant :

# app/config/config.yml
fos_rest:
    view:
        #…
        view_response_listener: true
    # …

Étant donné que mon action ne renvoit pas de réponse, je demande, grâce à cette configuration, que le FOSRestBundle fasse appel à un listener en charge de récupérer l'objet retourné et qu'il effectue la sérialisation pour moi.

Et voilà ! En une ligne le tour est joué. ^^ 

Quelques options avec l'annotation@View

L'annotation@Viewpropose quelques options bien pratiques :

<?php

namespace AppBundle\Controller;

use FOS\RestBundle\Controller\Annotations\View;

class DefaultController
{
    /**
     * @View(
     *     statusCode = 201,
     *     serializerGroups = {"POST_CREATE"}
     * )
     */
    public function realExampleAction()
    {
        // …
        return $objectToBeSerialized;
    }   
}

L'option  StatusCode  indique simplement quel code status pour la réponse il faut renvoyer.

L'option  serializerGroups  est un tableau prenant en paramètre les groupes de sérialisation que nous souhaitons utiliser pour la sérialisation de l'objet retourné par l'action.

Étendre la classe FOSRestController

Nous avons vu comment utiliser le serializer sans avoir à appeler le service nous-mêmes. Il y a aussi l'option où, dans l'action, nous instancions la réponse à la main. Et enfin, il nous reste encore une option : retourner une vue.

Je vous invite à ouvrir la classe  FOS\RestBundle\Controller\FOSRestController, et vous pourrez voir qu'il s'agit d'une classe abstraite utilisant un trait ( FOS\RestBundle\Controller\ControllerTrait ) contenant des méthodes permettant de manipuler une vue.

Négociation de contenu

Avant de passer à la suite, il faut que nous fassions un petit détour par un concept très important : la négociation de contenu (content negociation).

Nous venons d'évoquer le fait que le FOSRestBundle est capable d'adapter la sérialisation des données pour rendre une réponse qui soit en adéquation avec ce qui est demandé dans la requête. Tout cela est possible grâce à la négociation de contenu.

Qu'est-ce que la négociation de contenu ?

Nos applications doivent être en mesure de  pouvoir fournir plusieurs versions d'une ressource/document sur une même URI. De ce fait, le client spécifie la version qu'il souhaite recevoir grâce aux entêtes.

Voyons un exemple d'entêtes de requête :

Accept: text/html; q=1.0, text/*; q=0.8
Accept-Language: fr;q=1.0, en-gb;q=0.8, en;q=0.7
Accept-Charset: iso-8859-5, unicode-1-1;q=0.8
User-Agent: CERN-LineMode/2.15 libwww/2.17b3
Vary: Accept 

On peut voir ici que dans cette requête il y a beaucoup d'éléments demandés :

  • l'entête  Accept: text/html; q=1.0, text/*; q=0.8  indique qu'en priorité le client souhaite obtenir du html, si ce n'est pas possible, tout ce qui est possible au format texte ;

  • l'entête  Accept-Language: fr;q=1.0, en-gb;q=0.8, en;q=0.7 indique que le client souhaite obtenir du contenu en français en priorité, si ce n'est pas possible, en anglais de Grande-Bretagne (en-gb) et enfin, en anglais si les deux premières possibilités ne peuvent être honorées ;

  • l'entête  Accept-Charset: iso-8859-5, unicode-1-1;q=0.8  indique à nouveau dans quel ordre on souhaite obtenir l'encoding des caractères de la réponse. Tout d'abord  iso-8859-5, puis  unicode-1-1.

Le caractère q pour chacun des formats demandés correspond à la pondération. Plus elle est importante, plus le client le souhaite en priorité. Si aucune valeur n'est données à une demande, elle est au plus fort par défaut, c'est à dire 1.0.

Ainsi, tout le travail de négociation de contenu est là : savoir jongler avec toutes les demandes dans une même requête et savoir ce que l'application (notre API) est capable de fournir afin qu'elle puisse générer une réponse qui soit en adéquation la plus logique avec ce qui est demandé.

Ainsi, si, en tant que client nous souhaitons obtenir du JSON, il nous faut simplement ajouter l'entête Accept : application/json avec une grande priorité.

 

Rendez-vous au prochain chapitre pour avancer dans la création d'API de gestion d'articles avec la suite logique à la sérialisation : la désérialisation.

Exemple de certificat de réussite
Exemple de certificat de réussite