• 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

Allez plus loin avec JMSSerializer

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

 

Nous avons pu voir que, sans faire de configuration particulière, la librairie est prête à l'emploi et accessible facilement. Il s'agit maintenant de voir quelques particularités pour être flexible en fonction du besoin auquel vous pourriez être confronté.

La configuration

Politique d'exclusion

Dans le cas où nous ne souhaitons pas montrer l'un des champ, il faut faire appel à la politique d'exclusion : en effet, par défaut, tous les champs sont sérialisés. Reprenons notre ressource créée au dernier chapitre. Pour retirer un champ, il faut tout exclure, puis n'exposer que les éléments que l'on souhaite retrouver :

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as Serializer;

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

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

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

    //…
}

Avec ces annotations, nous avons demandé à JMSSerializer d'exclure tous les champs de la classe Article (annotation que l'on retrouve sur la classe,  @Serializer\ExclusionPolicy("All")  ), puis nous avons ajouté l'annotation  @Serializer\Expose  à tous les champs que nous souhaitons voir apparaître.

Si nous requêtons à nouveau notre API pour récupérer les informations d'un article, nous ne devrions plus voir l'ID de cet article :

Article sérialisé, sans le champs id
Article sérialisé, sans le champ ID

Groupe de sérialisation

Dans certains cas, il est possible que vous ayez besoin d'offrir des vues différentes de votre ressource. Pour répondre à ce besoin, il existe les groupes de sérialisation. Pour les utiliser, rien de plus simple !

Il nous suffit d'ajouter l'annotation@Groupssur les champs que nous souhaitons rattacher à un (ou plusieurs) groupe(s). Voici un exemple :

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as Serializer;

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

    /**
     * @ORM\Column(type="string", length=100)
     *
     * @Serializer\Groups({"detail", "list"})
     */
    private $title;

    /**
     * @ORM\Column(type="text")
     *
     * @Serializer\Groups({"detail", "list"})
     */
    private $content;
 
    //…   
}

Ici, je crée deux groupes de sérialisation :  detail  et  list. Je souhaite que lorsque j'indique au serializer de ne sérialiser que les champs qui ont le groupe  detail, il n'affiche que les champs title et content. Pour ce faire, il faut que je modifie mon controller comme suit :

<?php

namespace AppBundle\Controller;

//…
use JMS\Serializer\SerializationContext;

class ArticleController extends Controller
{
    /**
     * @Route("/articles/{id}", name="article_show")
     */
    public function showArticleAction(Article $article)
    {
        $data = $this->get('jms_serializer')->serialize($article, 'json', SerializationContext::create()->setGroups(array('detail')));

        $response = new Response($data);
        $response->headers->set('Content-Type', 'application/json');

        return $response;
    }

    // …
}

C'est donc en troisième paramètre de la méthode  serialize  que j'indique à quel(s) groupe(s) je souhaite que le serializer fasse appel (ligne 15 de l'extrait de code ci-dessus).

Champs sérialisé ayant le groupe detail
Champs sérialisés ayant le groupe detail

Il ne me reste plus qu'à modifier l'action qui sérialise une liste de ne prendre que les champs qui ont les groupes  list :

<?php

namespace AppBundle\Controller;

// …

class ArticleController extends Controller
{
    // …

    /**
     * @Route("/articles", name="article_create")
     * @Method({"GET"})
     */
    public function listArticleAction()
    {
        $articles = $this->getDoctrine()->getRepository('AppBundle:Article')->findAll();

        $data = $this->get('jms_serializer')->serialize($articles, 'json', SerializationContext::create()->setGroups(array('list')));

        $response = new Response($data);
        $response->headers->set('Content-Type', 'application/json');

        return $response;
    }
}

 

Champs sérialisés ayant les groupes list & detail
Champs sérialisés ayant les groupes list

Nous avons donc deux vues de notre ressource en fonction des cas.

Et plus encore !

Nous venons de voir notre première configuration possible pour la sérialisation, mais il en existe de nombreuses autres ! Nous aurons l'occasion d'en expérimenter d'autres tout au long de ce cours, néanmoins je vous invite vivement à aller lire la documentation officielle : configuration possible en annotation de JMSSerializer.

Aller au-delà de la configuration

Travailler avec les events

Il est possible d'intervenir dans la sérialisation et la désérialisation grâce aux évènements lancés par la librairie. Voici les évènements lancés :

  • lors de la sérialisation :

    • serializer.pre_serialize : l'évènement est de type  JMS\Serializer\EventDispatcher\PreSerializeEvent et permet de changer le type de l'objet à sérialiser. Il est également possible d'accéder aux données de l'objet à sérialiser, ainsi qu'au visiteur (objet utilisé pour parcourir l'objet à sérialiser).

    • serializer.post_serialize : l'évènement est de type  JMS\Serializer\EventDispatcher\ObjectEvent et permet de modifier le résultat de la sérialisation de l'objet.

  • lors de la désérialisation :

    • serializer.pre_deserialize : l'évènement est de type  JMS\Serializer\EventDispatcher\PreDeserializeEvent et permet de modifier les données soumises à l'application et/ou modifier le type de l'objet à obtenir après désérialisation.

    • serializer.post_deserialize : l'évènement est de type  JMS\Serializer\EventDispatcher\ObjectEvent et permet de faire des manipulations sur l'objet désérialisé comme de la validation ou ajouter des informations à l'objet qui pourrait provenir d'un service (API) externe par exemple.

Tous les évènements lors de la sérialisation et désérialisation avec JMS Serializer
Tous les évènements lors de la sérialisation et désérialisation avec JMS Serializer

En fonction de ce dont vous avez besoin, il vous suffira de créer un event listener (ou event subscriber) afin d'écouter le bon event. Pour notre application, je vous propose d'ajouter une information au JSON envoyé à nos utilisateurs d'API : après sérialisation, nous ajouterons la date à laquelle l'article a été fourni pour que nos utilisateurs puissent avoir une information concernant la "fraîcheur" de l'article. Il nous faut donc écouter l'évènement  serializer.post_deserialize, et nous allons le faire avec un event subscriber.

C'est parti ! Créons notre classe  ArticleListener dans un dossier Serializer/Listener dans notre bundle AppBundle :

<?php

namespace AppBundle\Serializer\Listener;

use JMS\Serializer\EventDispatcher\Events;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;

class ArticleListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            [
                'event' => Events::POST_SERIALIZE,
                'format' => 'json',
                'class' => 'AppBundle\Entity\Article',
                'method' => 'onPostSerialize',
            ]
        ];
    }

    public static function onPostSerialize(ObjectEvent $event)
    {
        // Possibilité de récupérer l'objet qui a été sérialisé
        $object = $event->getObject();

        $date = new \Datetime();
        // Possibilité de modifier le tableau après sérialisation
        $event->getVisitor()->addData('delivered_at', $date->format('l jS \of F Y h:i:s A'));
    }
}

Il nous faut le déclarer en tant que service et le taguer. Il faut donc ajouter dans le fichier  app/config/services.yml ce qui suit :

services:
    serializer_listener.article:
        class: AppBundle\Serializer\Listener\ArticleListener
        tags:
            - { name: jms_serializer.event_subscriber }

Et voilà ! Désormais, nous avons un élément en plus dans notre JSON, le champs delivered_at.

Objet article sérialisé avec le nouveau champs delivered_at
Objet article sérialisé avec le nouveau champ delivered_at

Serializer Handler

Dans le cas où les évènements ne vous suffisent pas, il vous reste encore la solution très avancée du Serializer Handler. Le principe est de gérer la sérialisation entièrement à la main. Pour créer un handler, il suffit de :

  1. créer une classe qui implémente l'interface  JMS\Serializer\Handler\SubscribingHandlerInterface ;

  2. en faire un service avec le tag  jms_serializer.subscribing_handler .

L'interface SubscribingHandlerInterface nous impose d'implémenter la méthode statique  getSubscribingMethods. Voici un exemple d'implémentation de cette méthode :

<?php

namespace AppBundle\Serializer\Handler;

use JMS\Serializer\GraphNavigator;
use JMS\Serializer\Handler\SubscribingHandlerInterface;

class ArticleHandler implements SubscribingHandlerInterface
{
    public static function getSubscribingMethods()
    {
        return [
            [
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                'format' => 'json',
                'type' => 'AppBundle\Entity\Article',
                'method' => 'serialize',
            ],
            [
                'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
                'format' => 'json',
                'type' => 'AppBundle\Entity\Article',
                'method' => 'deserialize',
            ]
        ];
    }
}

La méthode getSubscribingMethods doit renvoyer un tableau contenant lui-même un ou deux tableaux. Dans le code présenté ci-dessus, nous indiquons que :

  • lors de la sérialisation de l'objet de type AppBundle\Entity\Article au format JSON, ce sera le code de la méthode serialize de la classe ArticleHandler qui sera exécuté ;

  • lors de la désérialisation de l'objet de type AppBundle\Entity\Article au format JSON, ce sera le code de la méthode deserialize de la classe ArticleHandler qui sera exécuté.

Il suffit d'implémenter les méthodes déclarées aux lignes 17 et 23 de la classe ci-dessus :

<?php

namespace AppBundle\Serializer;

use AppBundle\Entity\Article;
use JMS\Serializer\JsonSerializationVisitor;
use JMS\Serializer\JsonDeserializationVisitor;
use JMS\Serializer\SerializationContext as Context;

class ArticlephpHandler implements SubscribingHandlerInterface
{
    // …
    
    public function serialize(JsonSerializationVisitor $visitor, Article $article, array $type, Context $context)
    {
        // L'on reçoit un objet à sérialiser (dans cet exemple, $article)
        // Puis nous pouvons manipuler le graph d'objet grâce à l'objet $visitor 
    }
    
    public function deserialize(JsonDeserializationVisitor $visitor, $data)
    {
        // Dans cet exemple, la méthode doit retourner un objet de type AppBundle\Entity\Article
    }
}

Et enfin, il faut faire de cette classe un service ayant un tag bien précis :

services:
    serializer_handler.article:
        class: AppBundle\Serializer\Handler\ArticleHandler
        tags:
            - { name: jms_serializer.subscribing_handler }
La méthodeserialize

Les arguments de la méthode sont toujours les mêmes. Ceci dit, le type du second argument change en fonction de ce qui a été indiqué dans la méthode getSubscribingMethods (le champ "type" du tableau).

Cette méthode doit retourner un tableau ou une chaîne de caractères. Ceci dit, prenez garde, il faut que ce tableau soit correctement vérifié. Il existe une méthode visitArray() accessible depuis l'objet JsonSerializerVisitor ( $visitor) permettant de parcourir ce tableau et s'assurer qu'il est correct.

Ainsi, si nous reprenons l'exemple présenté auparavant (ajouter un champ delivered_at), le code pour y parvenir est le suivant :

<?php

namespace AppBundle\Serializer\Handler;

// …

class ArticleHandler implements SubscribingHandlerInterface
{
    public static function getSubscribingMethods()
    {
        //…
    }

    public function serialize(JsonSerializationVisitor $visitor, Article $article, array $type, Context $context)
    {
        $date = new \Datetime();

        $data = [
            'title' => $article->getTitle(),
            'content' => $article->getContent(),
            'delivered_at' => $date->format('l jS \of F Y h:i:s A'),
        ];

        return $visitor->visitArray($data, $type, $context);
    }
    
    // …
}
Résultat de la sérialisation avec un serializer handler
Résultat de la sérialisation avec un serializer handler

Comme vous pouvez vous en rendre compte, il faut construire son tableau complètement à la main. Gardez en tête que la création d'un handler est une solution de dernier recours.

Le méthodedeserialize

Tout comme la méthode serialize, les arguments sont toujours tels que je vous les présente ici. Il s'agit cette fois-ci de manipuler $data pour le transformer en objet de type  AppBundle\Entity\Article (type déclaré dans la méthode  getSubscribingMethods).

<?php

namespace AppBundle\Serializer\Handler;

// …

class ArticleHandler implements SubscribingHandlerInterface
{
    public static function getSubscribingMethods()
    {
-       //…
    }

    // …

    public function deserialize(JsonDeserializationVisitor $visitor, $data)
    {
        return new Article($data);
    }
}

Dans le code présenté ci-dessus, nous nous appuyons sur le constructeur de la classe Article pour hydrater notre objet.

 Je vous propose maintenant de prendre le temps de découvrir le composant Serializer de Symfony.

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