DTO est mort vive Data !

Publié le 24 novembre 2022 par William Suppo
Couverture de l'article DTO est mort vive Data !

Spatie a annoncé la fin du support de son paquet spatie/data-transfer-object et celui-ci c’est vu être immédiatement archivé.

Ce paquet, nous l’avions découvert au sein de cet article. Il est temps de passer en revue l’alternative qu’il recommande qui n’est autre que leur nouveau paquet spatie/laravel-data.

Introduction

Ce paquet a pour vocation d’être utilisé dans 3 contextes différents :

Nous allons, dans cet article, explorer les 2 premiers points.

Commençons par le commencement, c’est-à-dire l’implémentation d’une classe. Rien de bien compliqué, notre classe doit étendre de la classe Data et définir les propriétés typées au sein du constructeur :

1namespace App\Data;
2 
3use Spatie\LaravelData\Data;
4 
5class StudentData extends Data
6{
7 public function __construct(
8 public int $id,
9 public string $name,
10 ) {
11 }
12}

On peut d’ores et déjà jouer avec nos classes soit en instanciant un objet avec des paramètres nommés ou bien via la méthode from en lui passant en paramètre un tableau :

1use App\Data\StudentData;
2 
3$boris = new StudentData(id: 42, name: 'Boris');
4$jane = StudentData::from(['id' => 99, 'name' => 'Jane']);

Le casting des propriétés

Une des fonctionnalités appréciées dans le paquet DTO était le casting des propriétés via les attributs, on retrouve ici la même logique que l’on va illustrer avec une nouvelle classe BirthdateData qui a pour particularité d’avoir une propriété de type DateTime :

1namespace App\Data;
2 
3use DateTime;
4use Spatie\LaravelData\Attributes\WithCast;
5use Spatie\LaravelData\Casts\DateTimeInterfaceCast;
6use Spatie\LaravelData\Data;
7 
8class BirthdateData extends Data
9{
10 public function __construct(
11 #[WithCast(DateTimeInterfaceCast::class, format: 'Y-m-d')]
12 public DateTime $date,
13 ) {
14 }
15}

Alors, pourquoi la présence de l’attribut WithCast ? Tout simplement pour que classe BirthdateData puisse convertir, lors de sa construction, une string représentant une date en objet DateTime, ainsi le code suivant va fonctionner :

1use \App\Data\BirthdateData;
2 
3$bornAt = BirthdateData::from(['date' => '1970-01-01']);

On complexifie un peu le tout en ajoutant une propriété bornAt à notre classe StudentData qui sera du type précédemment définit, c’est-à-dire BirthdateData :

1namespace App\Data;
2 
3use Spatie\LaravelData\Data;
4 
5class StudentData extends Data
6{
7 public function __construct(
8 public int $id,
9 public string $name,
10 public BirthdateData $bornAt,
11 ) {
12 }
13}

Lors de la construction de notre objet depuis un tableau, il est tout à fait possible de passer notre date en string car celle-ci va être automatiquement convertie en DateTime :

1$jane = \App\Data\StudentData::from([
2 'id' => 99,
3 'name' => 'Jane',
4 'bornAt' => [
5 'date' => '1970-01-01'
6 ],
7]);

À noter que nous pouvons définir nos propres types.

Utile pour les retours d’API

Nous allons maintenant aborder les utilisations de nos objets en commençant par les retours des méthodes de nos contrôleurs à envisager dans le cadre du développement d’une API.

Comme indiqué précédemment nos nouveaux objets Data peuvent être rendu en lieu et place des Resource de Laravel.

Ainsi, à partir d’un modèle passé en paramètre d’une méthode de notre contrôleur, nous pouvons produire notre objet Data et leur rendre tel quel.

Pour l’exemple, admettons un modèle Student en paramètre de la méthode show de notre contrôleur :

1namespace App\Http\Controllers;
2 
3use App\Data\StudentData;
4use App\Models\Student;
5 
6class StudentController extends Controller
7{
8 public function show(Student $model)
9 {
10 return StudentData::from($model);
11 }
12}

Ce code va nous retourner un json semblable à ceci :

1{
2 "id": 99,
3 "name": "Jane",
4 "bornAt": {
5 "date": "1970-01-01T16:48:29+01:00"
6 }
7}

Utile pour la validation de données

Un autre aspect mis en avant avec ce paquet est la possibilité de déléguer la définition des règles de validation à notre objet Data en ajoutant des attributs aux propriétés comme ceci :

1class BirthdateData extends Data
2{
3 public function __construct(
4 #[Required, Date]
5 #[WithCast(DateTimeInterfaceCast::class, format: 'Y-m-d')]
6 public DateTime $date,
7 ) {
8 }
9}
10 
11class StudentData extends Data
12{
13 public function __construct(
14 #[Nullable]
15 public ?int $id,
16 #[Required, StringType]
17 public string $name,
18 #[Required, ArrayType]
19 public BirthdateData $bornAt,
20 ) {
21 }
22}

On remarque que la validation d’objet Data imbriqué est possible, il faut cependant que notre formulaire respecte la structure des objets :

1<form action="/data" method="post">
2 <input type="text" name="name"/></br>
3 <input type="text" name="bornAt[date]"/></br> <!-- ICI -->
4 <button type="submit">Envoyer</button>
5</form>

Voilà, nous pouvons dorénavant utiliser notre objet StudentData au sein de notre contrôleur en lieu et place des FormRequest :

1public function store(StudentData $data)
2{
3 dd($data->all());
4}

Conclusion

Ce paquet apporte de la consistance dans la manipulation de nos données au sein d’un projet Laravel, là où on peut considérer la définition des règles de validation et des ressources trop volatile.

Il reste plein d’aspect à explorer dans la documentation du paquet.

Ceci étant dit, pour une utilisation dans un contexte autre que celui de Laravel, celui-ci ne semble pas approprié. Ainsi, il faut peut-être privilégier une autre librairie ou une implémentation personnelle du DTO.

William Suppo avatar
William Suppo
Je mange du PHP au p'tit dej', et vous ?

A lire

Autres articles de la même catégorie