Comment utiliser le Serializer Symfony ?
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 :
- La documentation officielle de Symfony
- Maîtriser le composant Serializer de Symfony - Kévin Dunglas - PHP Tour Montpellier 2018