Cast un attribut de modèle en DTO
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
La méthode createOrFirst
Voyons ensemble à quelles problématiques répond la nouvelle méthode createOrFirst !
Mathieu De Gracia
Conteneuriser son application Laravel avec Docker
Création du Dockerfile de notre application Laravel
William Suppo
Les bases : Création du contrôleur
Découverte des contrôleurs au sein d'une application Laravel
William Suppo