Utilisons un DTO en tant qu’attribut afin de rendre notre code plus consistant et d'éviter les surprises !
Implémentation du DTO
Nous allons partir sur un cas d’usage simple : un modèle Post
qui possède les attributs suivants :
-
title
: le titre de l’article -
content
: le contenu de l’article -
image
: un json contenant les propriétés de notre DTO
Notre DTO Image
possède, quant à lui, les propriétés src
et alt
, voici son implémentation :
1<?php 2 3namespace App\DataTransfertObjects; 4 5class Image 6{ 7 public function __construct( 8 public readonly string $src, 9 public readonly string $alt,10 ) {11 }12}
Création du modèle
Commençons par implémenter la migration de notre modèle :
1<?php 2 3use Illuminate\Database\Migrations\Migration; 4use Illuminate\Database\Schema\Blueprint; 5use Illuminate\Support\Facades\Schema; 6 7return new class extends Migration 8{ 9 /**10 * Run the migrations.11 */12 public function up(): void13 {14 Schema::create('posts', function (Blueprint $table) {15 $table->id();16 $table->timestamps();17 $table->string('title');18 $table->text('content');19 $table->json('image'); 20 });21 }22 23 /**24 * Reverse the migrations.25 */26 public function down(): void27 {28 Schema::dropIfExists('posts');29 }30};
À noter que la colonne image
est de type json
, ce qui nous permettra de sérialiser notre DTO en base de données.
Tant que nous abordons les données, implémentons la Factory
de notre modèle afin de pouvoir nourrir facilement notre base :
1<?php 2 3namespace Database\Factories; 4 5use App\DataTransfertObjects\Image; 6use Illuminate\Database\Eloquent\Factories\Factory; 7 8/** 9 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>10 */11class PostFactory extends Factory12{13 /**14 * Define the model's default state.15 *16 * @return array<string, mixed>17 */18 public function definition(): array19 {20 return [21 'title' => 'Ceci est un titre ' . $this->faker->randomDigit(),22 'content' => $this->faker->paragraph(),23 'image' => new Image( 24 src: $this->faker->imageUrl(),25 alt: $this->faker->sentence(),26 ),27 ];28 }29}
La particularité ici réside dans l’instanciation d’un DTO afin de construire l’attribut image
de notre modèle.
Enfin, découvrons notre modèle Post
et surtout la manière dont on ordonne à Laravel de transformer le json
en objet Image
:
1<?php 2 3namespace App\Models; 4 5use App\Casts\ImageCaster; 6use Illuminate\Database\Eloquent\Factories\HasFactory; 7use Illuminate\Database\Eloquent\Model; 8 9class Post extends Model10{11 use HasFactory;12 13 protected $casts = [14 'image' => ImageCaster::class, 15 ];16}
Voilà, c’est via la classe ImageCaster
que Laravel va transformer la propriété, à nous d’y définir comment cela sera fait dans le prochain chapitre.
Création du Caster
Laravel nous offre la possibilité de nous créer un caster via une commande, utilisons là afin de produire notre ImageCaster
:
1php artisan make:cast ImageCaster
Nous voilà en possession d’une nouvelle classe App\Casts\ImageCaster
dont il nous reste à définir les méthodes get
et set
.
La méthode get
est appelé lorsque l’on récupère des données de la base afin de créer l’instance du modèle, autrement dit, nous allons y définir comment transformer le json
en Image
:
1public function get(Model $model, string $key, mixed $value, array $attributes): mixed2{3 $attributes = json_decode($value, true);4 5 return new Image(6 src: $attributes['src'],7 alt: $attributes['alt'],8 );9}
On décode le contenu du json
en tableau et on crée notre instance Image
.
Enfin, la méthode set
, qui est l’opposée de get
, va définir comment sérialiser une instance d’Image
:
1public function set(Model $model, string $key, mixed $value, array $attributes): mixed 2{ 3 if (! $value instanceof Image) { 4 throw new InvalidArgumentException('The given value is not an Image'); 5 } 6 7 return json_encode([ 8 'src' => $value->src, 9 'alt' => $value->alt,10 ]);11}
On vérifie que value
est bien du type attendu, puis on transforme notre Image
en json
.
Bonus
Nous avons dorénavant toutes les briques à notre disposition afin d’utiliser sereinement notre modèle Post
dans une vue par exemple :
1<body>2 @foreach($posts as $post)3 <div>4 <h2>{{ $post->title }}</h2>5 <img src="{{ $post->image->src }}" alt="{{ $post->image->alt }}"> 6 <div>{{ $post->content }}</div>7 </div>8 @endforeach9</body>
Nous venons de faire un tour sur le potentiel du casting de Laravel.
Pour aller plus loin, libre à vous de consolider l’instanciation du DTO en vérifiant la bonne présence d’une clé et le cas échéant définir une valeur par défaut par exemple.
A lire
Autres articles de la même catégorie
Utiliser une API pour nourrir une base de manière cohérente
Implémentation d’une alternative à l’utilisation de Faker pour disposer de données cohérentes.
Les bases 5/6 : Contrôle d’accès
Découverte du contrôle des accès de nos utilisateurs à travers les Policies.
Initiation à l’analyse de code
Initiez-vous à l'analyse de code grace à phpmetrics !