Comment utiliser le Serializer Symfony ?

Fabien

Fabien, Architecte web 4 décembre 2018

Il existe de nombreuses possibilités de configuration et utilisation du composant Serializer de Symfony. Cet article a pour objectif de présenter la sérialisation ainsi que son utilisation.

Qu'est-ce que la sérialisation ?

La sérialisation consiste à transformer un objet en un format spécifique (comme le yaml, json, …) puis de pouvoir passer du format spécifique vers l’objet d’origine.

Les cas typiques d’utilisation de la sérialisation sont :

  • dans une API
  • des microservices entre applications (qui peuvent être dans des langages différents)
  • récupération d'objets depuis la base de données

Le composant Symfony

Présentation

Le framework PHP Symfony propose un composant Serializer complet pour sérialiser les objets en différents formats simplement.

Schéma tiré de la documentation Symfony

Comme présenté dans ce schéma, la sérialisation normalise l’objet en un array avant de l’encoder dans un format spécifique. La désérialisation fait l’inverse, elle décode le format en un array avant de le dénormaliser en un objet.

Installation du composant Symfony

Le composant Symfony peut s’installer simplement avec Composer :

composer require symfony/serializer

Mais le composant peut être à la place cloné via le repository github.

Normalizers / encodeurs et décodeurs

Le composant Serializer de Symfony inclut de nombreux normalizers et encoders.

Les 3 principaux normalizers

  • ObjectNormalizer : utilise le composant pour accéder aux propriétés de l’objet. Cherche les propriétés public de l’objet, cherche toutes les méthodes public ayant en nom get/set/has/is/add/remove suivi d’un nom de propriété ainsi que les méthodes magiques.
  • GetSetMethodNormalizer : utilise les getter/setter de l’objet. Cherche toutes les méthodes public ayant en nom get suivi d’un nom de propriété.
  • PropertyNormalizer : utilise PHP reflexion pour accéder aux propriétés de l’objet. Cherche les propriétés public et private de l’objet.

Les normalizers à utilisation plus spécifique

  • DateTimeNormalizer : convertit les objets implémentant l’interface DateTimeInterface en chaîne de caractères à la norme RFC3339.
  • DataUriNormalizer : transforme un objet SplFileInfo en une chaîne DataURIs pour pouvoir embarquer des fichiers dans les données sérialisées.
  • JsonSerializableNormalizer : gère les objets implémentant l’interface JsonSerializable (a l’avantage de gérer les références circulaires contrairement à json_encode).
  • ArrayDenormalizer : dénormalise des tableaux d’objets utilisant un format de type MyObject[].
  • DateIntervalNormalizer : convertit les objets DateInterval en chaîne de caractères.

Nous avons la possibilité de créer un normalizer personnalisé en implémentant à notre class l‘interface NormalizerInterface. Le service doit avoir le tag serializer.normalizer (avec la configuration de service par défaut de Symfony le tag est automatique).

   new_normalizer:
       class: Path\to\class
       public: false
       tags: [serializer.normalizer]

Les encoders / decoders

Tout comme les normalizers, le composant Serializer inclut plusieurs encodeurs/decodeurs de format :

JsonEncoder, XmlEncoder, YamlEncoder et CsvEncoder.

Nous pouvons également créer des encoders et decoders pour des formats non gérés. Il faut que notre class implémente les EncoderInterface et DecoderInterface. Le service doit avoir le tag serializer.encoder, qui sera appliqué automatiquement avec la configuration par défaut de service de Symfony.

   new_encoder_decoder:
       class: Path\to\class
       public: false
       tags: [serializer.encoder]

Utilisation du serializer Symfony

En tant que composant indépendant

Dans le cas de l’utilisation du serializer indépendamment du framework Symfony, il faut :

  • instancier le Serializer avec les normalizers et encoders en paramètres
  • appeler la méthode serialize du serializer avec l’objet et le format en paramètres
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
//...

      $encoders = array(new JsonEncoder());
      $normalizers = array(new ObjectNormalizer());

      $serializer = new Serializer($normalizers, $encoders);
      $productSerialized = $serializer->serialize($product, 'json');
//...

En ce qui concerne la désérialisation, il faut appeler la méthode deserialize avec en paramètres les données sérialisées, la class et le format :

$productDeserialized = $serializer->deserialize($productSerialized, Product::class, 'json');

Il est possible de déserialiser les données directement dans un objet existant :

$productDeserialized>deserialize($productSerialized, Product::class, 'json', array('object_to_populate' => $product));

En tant que service Symfony

Dans le framework Symfony, on peut bien sûr créer notre serializer comme dans la partie précédente. Cependant, avec l’injection de service, on obtient directement une instance du serializer déjà configurée.

Par exemple, dans une action d’un Controller :

use Symfony\Component\Serializer\SerializerInterface;
//...
public function index(SerializerInterface $serializer)
{
    $productSerialized = $serializer->serialize($this->getProduct(), 'json');
//...

Normalizer/Decoder

Dans ce cas, l’instance récupérer $serializer possède tous les encoders mais aussi des normalizers. Par ordre de priorité :

0 - JsonSerializableNormalizer

1 - DateTimeNormalizer

2 - ConstraintViolationListNormalizer

3 - DateIntervalNormalizer

4 - DataUriNormalizer

5 - ArrayDenormalizer

6 - ObjectNormalizer

Pour l’utilisation des normalizers, le composant utilise le Pattern Chain of Responsibility.

On peut bien sûr ajouter des decoders ou normalizers dans cette instance du serializer récupérée. Par exemple, pour ajouter un normalizer avec une priorité plus haute, on déclare un service dans la configuration de service de Symfony :

   property_normalizer:
       class: Symfony\Component\Serializer\Normalizer\PropertyNormalizer
       public: false
       tags: [serializer.normalizer]

Groups

Nous avons également la possibilité d’utiliser les groupes d’attributs pour sérialiser seulement une partie des propriétés d’un objet. Pour cela, il est nécessaire d’avoir le SensioFrameworkExtraBundle installé dans notre projet (composer require sensio/framework-extra-bundle).

Une fois installé, l’annotation Groups est utilisable pour les attributs de notre objet :

use Symfony\Component\Serializer\Annotation\Groups;

//...
/**
* @Groups({"seo"})
*/
private $metaDescription;
//...

Puis pour sérialiser ou désérialiser :

$productSerialized = $serializer->serialize($product, 'json',['groups' => 'seo']);
$productDeserialized = $serializer->deserialize($productSerialized, Product::class, 'json', ['groups' => 'seo']);


Attributs spécifiques

Il est aussi possible de spécifier les attributs que l’on souhaite sérialiser/désérialiser :

$productSerialized = $serializer->serialize($product, 'json', ['attributes' => ['title']]);
$productDeserialized = $serializer->deserialize($productSerialized, Product::class, 'json', ['attributes' => ['title']]);

Serializer Symfony, plus d'infos

De nombreuses ressources sont disponibles pour comprendre encore plus en profondeur ce composant avec la documentation mais aussi les différentes conférences PHP et Symfony. Pour une utilisation plus avancée de la sérialisation, surtout dans le cas d’une API, APIPlatform est vivement recommandé.

Sources :