Les DTOs : arrêtez de transformer vos données en kebab
Transformez votre code en architecture robuste avec les DTOs readonly de PHP 8 et Object Mapper de Symfony
Publié le
Temps de lecture 4 min
Vous connaissez cette sensation. Ce moment précis où vous ouvrez une méthode processData(array $data) écrite il y a six mois par un collègue (ou pire, par vous-même).
Vous regardez $data['user_info']['meta_data'][0]['valeur_reelle_final_v2'] et vous sentez le désespoir monter. De quoi est fait ce tableau ? Est-ce que cette clé existe toujours ? Pourquoi est-ce que parfois c'est une string "null" et parfois un vrai null ?
C'est ce que j'appelle le "syndrome du kebab". On empile des couches de viande et de légumes dans un pain (le tableau associatif), ça dégouline de sauce partout, c'est instable, et si vous appuyez trop fort, tout s'effondre sur votre clavier.
Il existe un remède. Il ne demande pas une ordonnance, juste un peu de rigueur : le DTO (Data Transfer Object).
Pourquoi nous infligeons-nous la douleur des tableaux ?
Les tableaux associatifs sont la drogue douce du développeur PHP. Faciles à créer, flexibles, rapides. "Je vais juste passer un petit tableau d'options", vous dites-vous.
Trois mois plus tard, ce "petit tableau" est devenu un monstre tentaculaire traversant 15 services, et plus personne n'ose toucher à une clé de peur de casser une fonctionnalité obscure en production.
Le DTO est l'antidote. C'est un contrat. C'est une promesse.
La révélation PHP 8 : le DTO readonly
Avec les versions modernes de PHP, créer un DTO n'est plus une corvée verbeuse avec 50 getters et setters. C'est élégant.
Regardez ce que j'utilise pour traiter les données de l'API RTE (Réseau de Transport d'Électricité) dans ce projet. L'API renvoie un JSON complexe, parfois incohérent. Au lieu de laisser ce chaos entrer dans mon domaine, je le stoppe net à la frontière avec un DTO blindé.
namespace App\Dto\CarbonAware;
use Symfony\Component\ObjectMapper\Attribute\Map;
/**
* DTO for RTE éCO₂mix national real-time data.
* Plus de devinettes. On sait exactement ce qu'on a.
*/
final readonly class RteNationalDataDto
{
public function __construct(
#[Map(source: 'date_heure')]
public \DateTimeImmutable $dateHeure,
public int $consommation,
// On peut même mapper des objets imbriqués automatiquement !
#[Map(target: RteProductionDto::class)]
public RteProductionDto $production,
#[Map(source: 'ech_physiques')]
public int $imports,
// Valeurs par défaut = Sécurité absolue
public bool $isFallback = false,
public ?string $source = 'rte',
) {
}
/**
* Le DTO peut porter de la logique de PRESENTATION ou de CALCUL simple.
* PAS de logique métier complexe (accès BDD, appels API...).
*/
public function getCarbonIntensity(): int
{
return $this->tauxCo2;
}
}
Qu'est-ce qu'on gagne immédiatemment ?
- Immutabilité (
readonly) : Une fois créé, cet objet ne changera jamais. Vous pouvez le passer à 10 services, aucun ne pourra le modifier en douce. C'est la tranquillité d'esprit absolue. - Typage fort : Si la consommation n'est pas un entier, l'application plante immédiatement à l'entrée, avec une erreur claire. Pas de "NaN" qui se propage silencieusement jusqu'à la facture du client.
- Intellisense : Votre IDE connait chaque propriété. Fini le
Ctrl+Fpour chercher les clés de tableau.
La magie noire de Symfony : Object Mapper
"Mais Pierre, c'est pénible d'hydrater ces objets ! Il faut faire des new Dto($data['truc'], $data['machin']...) partout !"
C'était vrai... Avant... Maintenant, j'utilise le composant Object Mapper.
final readonly class RteDataMapper
{
public function __construct(
private ObjectMapperInterface $objectMapper,
) {
}
public function map(array $data): RteNationalDataDto
{
// Une ligne. C'est tout.
// L'ObjectMapper lit les attributs #[Map], convertit les types,
// instancie les sous-DTOs et gère les erreurs.
return $this->objectMapper->map($data, RteNationalDataDto::class);
}
}
C'est presque indécent de simplicité. L'attribut #[Map(source: 'date_heure')] dans le DTO fait le pont entre le chaos du monde extérieur (le snake_case de l'API) et l'ordre de votre domaine (le camelCase de votre classe).
Le mot de la fin
Arrêtez de maltraiter vos données. Donnez-leur une maison. Un DTO, c'est un investissement de 2 minutes qui vous épargnera 2 heures de debugging dans six mois.
Si vos méthodes acceptent encore des array $options, posez ce clavier, respirez un grand coup, et créez votre premier DTO. Vos "futur vous" vous remercieront dans six mois quand il faudra apporter des nouvelles fonctionnalités !