Les DTOs : ou comment être sûr que tes données ne ressemblent pas à un kebab

Temps de lecture : 7 min

Découvrez pourquoi les DTOs sont essentiels pour des données bien rangées. Promis, pas de sauce blanche dans le code !

On va parler d'une façon élégante de gérer tes données : les DTOs (Data Transfer Objects). Alors oui, c'est encore une abréviation à la con pour faire croire à tes potes que tu bosses sur un truc compliqué.

C'est qui ce DTO ?

Un DTO, c'est juste une classe stupide qui transporte des données d'un point A à un point B, comme un livreur Uber Eats, mais sans le scooter bruyant et les retards inexpliqués.

Concrètement, c'est un objet qui :

  1. Ne contient QUE des données
  2. N'a pas de logique métier (ou alors t'as pas compris le concept, champion)
  3. Est généralement immuable (comme les promesses électorales, sauf que lui, il tient sa parole)
  4. Passe d'une couche à l'autre de ton application sans se transformer en gremlin si tu lui donnes à manger après minuit

Pourquoi je m'emmerderais avec ça ?

Ah, la fameuse question existentielle du développeur flemmard. Voilà pourquoi tu devrais t'y intéresser, même si ça implique de taper quelques lignes de code en plus :

  1. Isolation des couches : Tes entités Doctrine restent tranquillement dans leur bac à sable sans se faire tripoter par tout le monde
  2. Transfert efficace : Tu choisis exactement quelles données voyagent, pas besoin de trimballer toute la base de données à chaque requête
  3. Validation ciblée : Tu valides uniquement ce qui compte pour une opération donnée, pas tout le bordel d'un coup
  4. Versionnage facilité : Si ton API change, tu changes juste ton DTO, pas toute ta stack

Bref, c'est comme mettre un préservatif à tes données : ça évite les mauvaises surprises et les transmissions non désirées.

Comment j'ai implémenté ça dans mon projet

J'utilise des DTOs à plusieurs endroits stratégiques.

1. Le NavigationItemDto : parce que la navigation, c'est important (paraît-il)

// src/Dto/NavigationItemDto.php
namespace App\Dto;

final readonly class NavigationItemDto
{
    public function __construct(
        public int $contentId,
        public string $title,
        public string $url,
        public string $type,
        public string $slug,
    ) {
    }

    public static function fromArray(array $data): self
    {
        return new self(
            contentId: $data['contentId'],
            title: $data['title'],
            url: $data['url'],
            type: $data['type'],
            slug: $data['slug'],
        );
    }

    public function toArray(): array
    {
        return [
            'contentId' => $this->contentId,
            'title' => $this->title,
            'url' => $this->url,
            'type' => $this->type,
            'slug' => $this->slug,
        ];
    }
}

Ce petit bijou (oui j'aime être modeste) me permet de gérer les éléments de navigation dans mon site. Remarque le readonly qui empêche quiconque de modifier ces données après création. C'est comme dire à ta/ton conjoint.e : "Tu me prends comme je suis ou tu dégages". Direct, efficace.

Et les méthodes fromArray et toArray ? C'est pour la sérialisation/désérialisation, parce que stocker ça en JSON dans la base de données, c'est quand même pratique et bien plus classe.

2. Le PageMetricDto : pour les stats de pages, parce que ton ego a besoin de savoir combien de gens ont lu ton article sur les poneys qui cueillent des framboises

// src/Dto/PageMetricDto.php
namespace App\Dto;

use DateTimeImmutable;
use DateTimeInterface;

class PageMetricDto
{
    private readonly string $url;
    private readonly int $views;

    public function __construct(
        string $url, 
        int $views, 
        private readonly DateTimeInterface $date = new DateTimeImmutable()
    ) {
        $this->url = $url;
        $this->views = $views;
    }

    public function getUrl(): string
    {
        return $this->url;
    }

    public function getViews(): int
    {
        return $this->views;
    }

    public function getDate(): DateTimeInterface
    {
        return $this->date;
    }
}

Celui-là, je l'utilise quand je récupère les statistiques de vues de pages depuis Umami (un truc d'analytics qui respecte la vie privée, contrairement à certains géants du web qu'on ne nommera pas... Google).

Tu noteras que j'ai mis des getters plutôt que des propriétés publiques. C'est une variante acceptable pour les DTOs, surtout quand t'as envie de faire semblant d'être rigoureux.

3. Le PageViewDto : parce qu'un DTO c'est bien, mais trois c'est mieux

// src/Dto/PageViewDto.php
namespace App\Dto;

readonly class PageViewDto
{
    public function __construct(
        public string $url,
        public int $views,
        public string $slug,
        public int $postId,
    ) {
    }
}

Celui-là est tout petit, mais il fait le job. Je l'utilise pour transporter les données de vues de pages avec le slug et l'ID du post associé. Simple, efficace, pas de chichis.

Comment j'utilise ces merveilles dans mon application ?

Pour la Navigation

Dans mon entité Navigation, j'utilise le DTO pour convertir entre le format JSON stocké en base et les objets utilisables en PHP :

// src/Entity/Extra/Navigation.php
public function getItems(): array
{
    // Convertir les données JSON en objets NavigationItemDto
    $dtos = [];
    foreach ($this->items as $item) {
        $dtos[] = NavigationItemDto::fromArray($item);
    }

    return $dtos;
}

public function setItems(array $items): static
{
    // Convertir les objets NavigationItemDto en tableau pour stockage JSON
    $itemsArray = [];
    foreach ($items as $item) {
        $itemsArray[] = $item instanceof NavigationItemDto ? $item->toArray() : $item;
    }

    $this->items = $itemsArray;

    return $this;
}

C'est comme si tu avais un traducteur personnel entre le monde de la base de données et celui de ton application. Pratique non ? Plutôt que de manipuler des tableaux associatifs à l'arrache (comme un développeur PHP de 2005), t'as de vrais objets typés.

Pour les statistiques de Pages

Dans mon service PageViewTransformer, je transforme les données brutes d'Umami en DTOs propres et utilisables :

// src/Service/Analytic/PageViewTransformer.php
public function transformUmamiData(array $umamiData): array
{
    $dtos = [];

    foreach ($umamiData as $item) {
        // Vérifier que les clés nécessaires sont présentes
        if (!isset($item['x'], $item['y'])) {
            continue;
        }

        $url = $item['x'];
        $views = (int) $item['y'];

        // Ignorer les URLs exclues
        if ($this->shouldExcludePath($url)) {
            continue;
        }

        $dtos[] = new PageMetricDto($url, $views);
    }

    return $dtos;
}

Ce code prend des données au format chelou d'Umami (qui a pensé que x et y étaient des noms explicites pour une URL et un compteur de vues ?) et les transforme en objets bien propres.

Les erreurs à éviter, parce que se planter, c'est humain, mais se planter bêtement, c'est juste bête

  1. Ne mets pas de logique métier dans tes DTOs : Si ton DTO calcule, décide ou valide, c'est plus un DTO, c'est un service déguisé. Un DTO c'est con comme une brique, et c'est sa force.

  2. N'hérite pas entre DTOs : C'est pas une série Netflix avec 15 saisons. Garde ça simple, chaque DTO doit être autonome. L'héritage ici, c'est comme mettre du ketchup sur des sushis : techniquement possible, mais clairement une mauvaise idée.

  3. Ne réutilise pas les mêmes DTOs partout : Chaque contexte mérite son propre DTO. Si ton formulaire a besoin de 3 champs et ton API de 15, fais deux DTOs différents, feignasse.

  4. N'oublie pas la validation : Un DTO sans validation, c'est comme un bar sans alcool : ça ressemble à quelque chose de bien, mais au final tout le monde est déçu. Utilise les contraintes de validation, c'est fait pour ça.

Le mot de la fin, parce que tout a une fin (sauf la saucisse qui en a deux)

Les DTOs, c'est comme les sous-vêtements : personne ne les voit, mais tout le monde est bien content qu'ils soient là. Ils structurent tes données, maintiennent ton code propre et te permettent de dormir la nuit sans te demander pourquoi ton application ressemble à un plat de spaghettis après un tremblement de terre.

J'utilise des DTOs à différents endroits stratégiques, ce qui me permet de garder une architecture propre et découplée, où chaque couche ne sait que ce qu'elle a besoin de savoir, comme dans une bonne dictature fonctionnelle.

Alors la prochaine fois que tu te retrouves à passer des tableaux associatifs d'un bout à l'autre de ton application, souviens-toi : les DTOs sont là pour toi. Embrasse-les (métaphoriquement), aime-les, et ils te le rendront bien : un DTO par jour éloigne le spaghetti code pour toujours. Ou un truc du genre.

Liens utiles

Confidentialité

Ce site utilise Umami pour analyser le trafic de manière anonyme. Acceptez-vous la collecte de données anonymes ?