Object Mapper : l’art de la simplicité
Comment le composant Object Mapper de Symfony 7.3 simplifie la transformation d’entités en DTOs. Fini le code répétitif : mappez vos objets de façon déclarative, exploitez la puissance des Transformers, et gardez un code propre et lisible.
J’ai décidé de reprendre mes DTOs en utilisant le composant Object Mapper introduit avec Symfony 7.3 pour appliquer à la lettre ces mots de Léonard de Vinci « La simplicité est la sophistication supprême ». Si vous en avez marre d'écrire du code répétitif pour transformer vos entités en DTOs, ce composant va devenir votre nouveau meilleur ami
Qu'est-ce que Object Mapper ?
Imaginez que vous avez une entité complexe avec plein de relations, de propriétés et de méthodes. Maintenant, vous voulez l'afficher dans votre interface utilisateur, mais vous n'avez besoin que de certaines propriétés, peut-être transformées d'une certaine façon. C'est là qu'intervient Object Mapper !
Object Mapper est un nouveau composant Symfony qui permet de mapper (ou transformer) des objets d'une classe vers une autre, de manière déclarative et élégante. Fini les méthodes toArray()
interminables ou les constructeurs avec 15 paramètres !
Installation et configuration
Avant de plonger dans les exemples concrets, assurons-nous que vous avez tout ce qu'il faut pour suivre. L'installation est simple avec Composer :
composer require symfony/object-mapper
Et voilà, vous êtes prêt à mapper !
Les bases du mapping
Le principe fondamental d’Object Mapper est d'utiliser des attributs PHP pour déclarer comment les propriétés doivent être mappées d'un objet à un autre. Prenons un exemple concret tiré de mon code :
#[Map(target: Post::class)]
class PostDto
{
#[Map(target: 'id')]
public ?int $id = null;
#[Map(target: 'title')]
public string $title;
// ... autres propriétés
}
Dans cet exemple, nous déclarons que notre PostDto
peut être mappé vers une entité Post
. L'attribut #[Map]
au niveau de la classe définit la classe cible par défaut, tandis que les attributs #[Map]
sur les propriétés indiquent comment chaque propriété doit être mappée.
Utilisation basique
Maintenant, voyons comment utiliser concrètement l'Object Mapper dans notre code. Voici un exemple tiré de notre composant Twig Post
:
final class Post
{
public ?PostEntity $post = null;
public function __construct(
private readonly ObjectMapperInterface $objectMapper,
) {
}
#[ExposeInTemplate('card')]
public function getPostCard(): ?PostDto
{
return $this->objectMapper->map($this->post, PostDto::class);
}
}
C'est incroyablement simple ! Une seule ligne de code ($this->objectMapper->map($this->post, PostDto::class)
) suffit pour transformer notre entité Post
en DTO. Object Mapper s'occupe de tout le travail fastidieux.
La transformation de valeurs : la puissance cachée
Jusqu'ici, nous avons vu des mappings simples, mais la vraie puissance de Object Mapper réside dans sa capacité à transformer les valeurs pendant le mapping. C'est là que les choses deviennent vraiment intéressantes !
Les Transformers
Les transformers sont des classes qui implémentent l'interface TransformCallableInterface
et qui permettent de modifier une valeur pendant le processus de mapping. Regardons un exemple concret avec notre CategoryColorTransformer
:
class CategoryColorTransformer implements TransformCallableInterface
{
public function __invoke(mixed $value, object $source, ?object $target): string
{
return $value?->getColor()->value ?? 'blue';
}
}
Ce transformer est très simple : il prend un objet catégorie, extrait sa couleur, et retourne la valeur sous forme de chaîne. Si la catégorie est null ou n'a pas de couleur, il retourne blue
par défaut.
Utilisation des Transformers dans le DTO
Maintenant, voyons comment utiliser ce transformer dans notre DTO :
#[Map(source: 'category', transform: CategoryColorTransformer::class)]
public string $categoryColor;
Ici, nous indiquons que la propriété $categoryColor
doit être remplie en prenant la propriété category
de l'objet source, puis en la passant à travers notre CategoryColorTransformer
.
Examinons d'autres exemples de transformations dans notre PostDto
:
#[Map(source: 'category', transform: CategoryTitleTransformer::class)]
public string $categoryTitle;
#[Map(source: 'category', transform: CategorySlugTransformer::class)]
public string $categorySlug;
#[Map(source: 'seo', transform: ExcerptTransformer::class)]
public string $excerpt;
Chacune de ces propriétés utilise un transformer différent pour extraire et formater une information spécifique à partir d'un objet complexe.
Analyse Détaillée de Notre Exemple
Maintenant que nous avons couvert les bases, analysons en détail comment l'Object Mapper est utilisé dans notre projet.
Le DTO (PostDto.php)
Notre PostDto
est un excellent exemple d'utilisation de Object Mapper :
#[Map(target: Post::class)]
class PostDto
{
#[Map(target: 'id')]
public ?int $id = null;
#[Map(target: 'title')]
public string $title;
#[Map(target: 'content')]
public string $content;
#[Map(target: 'slug')]
public string $slug;
#[Map(target: 'status')]
public ContentStatusEnum $status;
#[Map(source: 'category', transform: CategoryTitleTransformer::class)]
public string $categoryTitle;
#[Map(source: 'category', transform: CategorySlugTransformer::class)]
public string $categorySlug;
#[Map(source: 'category', transform: CategoryColorTransformer::class)]
public string $categoryColor;
#[Map(target: 'viewCount')]
public int $viewCount;
#[Map(target: 'createdAt')]
public \DateTimeImmutable $createdAt;
#[Map(target: 'updatedAt')]
public \DateTimeImmutable $updatedAt;
#[Map(source: 'seo', transform: ExcerptTransformer::class)]
public string $excerpt;
}
Ce DTO combine plusieurs types de mappings :
- Mappings directs (id
, title
, content
, etc.)
- Mappings avec transformation (categoryTitle
, categorySlug
, categoryColor
, excerpt
)
- Mappings de types complexes (status
est un enum, createdAt
et updatedAt
sont des objets DateTimeImmutable
)
Le Transformer (CategoryColorTransformer.php)
Notre CategoryColorTransformer
est un exemple parfait de transformer simple mais efficace :
class CategoryColorTransformer implements TransformCallableInterface
{
public function __invoke(mixed $value, object $source, ?object $target): string
{
return $value?->getColor()->value ?? 'blue';
}
}
Ce transformer :
- Reçoit un objet catégorie dans
$value
- Utilise l'opérateur de navigation sécurisée (
?->
) pour éviter les erreurs si la catégorie est null - Extrait la valeur de la couleur
- Fournit une valeur par défaut (
blue
) si nécessaire
L'Utilisation (Post.php)
Enfin, notre composant Twig Post
montre comment utiliser Object Mapper dans un contexte réel :
final class Post
{
public ?PostEntity $post = null;
public function __construct(
private readonly ObjectMapperInterface $objectMapper,
) {
}
#[ExposeInTemplate('card')]
public function getPostCard(): ?PostDto
{
return $this->objectMapper->map($this->post, PostDto::class);
}
}
Ce composant :
- Injecte l'
ObjectMapperInterface
via l'injection de dépendances - Expose une méthode
getPostCard()
qui transforme l'entitéPost
enPostDto
- Utilise l'attribut
#[ExposeInTemplate('card')]
pour rendre le DTO accessible dans le template Twig sous le nomcard
Cette approche permet une séparation claire entre les données de l'entité (qui peuvent contenir des informations sensibles ou inutiles pour l'affichage) et les données nécessaires à l'affichage.
Avantages de Object Mapper
L'utilisation de l'Object Mapper présente de nombreux avantages :
- Réduction du code boilerplate : Plus besoin d'écrire des méthodes de conversion manuelles.
- Déclaratif plutôt qu'impératif : Le mapping est déclaré via des attributs, ce qui rend le code plus lisible.
- Séparation des préoccupations : Les entités peuvent se concentrer sur la logique métier, tandis que les DTOs se concentrent sur les besoins d'affichage.
- Facilité de maintenance : Ajouter ou modifier un mapping est aussi simple que d'ajouter ou modifier un attribut.
- Transformations puissantes : Les transformers permettent des conversions complexes en quelques lignes de code.
Le mot de la fin
Object Mapper de Symfony est un outil puissant qui peut considérablement simplifier votre code et améliorer sa maintenabilité. En utilisant des attributs déclaratifs et des transformers personnalisés, vous pouvez facilement convertir des objets complexes en structures plus simples adaptées à vos besoins spécifiques.
Nous avons vu comment Object Mapper nous permet de transformer des entités Post
complexes en DTOs simples, en extrayant et en transformant les données nécessaires pour l'affichage. Cette approche nous permet de garder notre code propre, maintenable et focalisé sur sa responsabilité première.
Alors, la prochaine fois que vous vous retrouvez à écrire une méthode toArray()
ou un constructeur avec une douzaine de paramètres, pensez à Object Mapper. Votre futur vous remerciera !
N'hésitez pas à explorer davantage la documentation officielle pour découvrir toutes les fonctionnalités avancées que nous n'avons pas pu couvrir dans ce billet.