Aller au contenu principal

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é.

PHP
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 ?

  1. 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.
  2. 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.
  3. Intellisense : Votre IDE connait chaque propriété. Fini le Ctrl+F pour 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.

PHP
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 !

Gestion des cookies

J'utilise Google Analytics, Google Tag Manager et Microsoft Clarity pour améliorer votre expérience. Vous pouvez choisir les services que vous autorisez.

Le code est dans la boîte !

Vous recevrez bientôt les nouveaux billets dans votre boîte mail. Pas de spam, promis.

Désinscription possible à tout moment.