Les Enums en PHP offrent de nombreuses possibilités sur un projet, particulièrement lorsqu’on les associe efficacement à des entités pour gérer des affichages de valeurs, des formulaires, et plus encore. Dans ce guide complet Laravel, nous allons découvrir comment les utiliser.
Attention : les attributs PHP sont disponibles depuis la version 8 de PHP.
Cependant, il peut arriver que l’on ait besoin d’ajouter des informations supplémentaires à un Enum. Voici quelques exemples :
Et on peut imaginer beaucoup de cas comme ceux-ci. Une solution assez simple est de créer une méthode à notre Enum (Par exemple description) qui nous renvoie la valeur correspondante.
Cependant, cette approche peut générer de nombreuses méthodes à créer en fonction des besoins, et le code reste assez rigide, comme l’exemple suivant :
public function description(): string
{
return match($this)
{
self::ADMIN => 'He\'s got full powers',
self::USER => 'A classic user, with classic rights',
self::GUEST => 'Oh, a guest, be nice to him!',
};
}
Dans cet article, nous allons voir comment aller plus loin en utilisant les Attributs PHP pour enrichir nos Enums. Nous aborderons les concepts d’Enums, d’Attributs et de Reflection API. J’ai eu cette idée en découvrant un package qui propose de nombreuses fonctionnalités supplémentaires pour les Enums, notamment les attributs. Je me suis dit que creuser ce sujet et refaire une version moi-même serait intéressant, et je vous parlerai du package en question à la fin de l’article.
Vous êtes développeur ou développeuse PHP ?
👉🏼 Découvrez nos offres d’emploi PHP
👉🏼 Testez vous sur notre QCM Laravel
Personnellement, j’utilise Sail, qui est très simple à mettre en place, pour faire tourner mon projet en local. Toutes les informations sont sur la documentation officielle de Laravel : https://laravel.com/docs/10.x#laravel-and-docker.
J’ai également configuré un alias pour la commande sail
pour ne pas avoir à écrire ./vendor/bin/sail
à chaque commande. Toutes les informations se trouvent ici : https://laravel.com/docs/10.x/sail#configuring-a-shell-alias
Commençons par créer un nouveau projet avec la commande suivante :
curl -s "https://laravel.build/enums-powers" | bash
Ensuite, installons Tailwind pour pouvoir styliser rapidement notre projet (et aussi parce que j’aime bien Tailwind). Le guide complet d’installation de Tailwind pour Laravel est disponible ici : https://tailwindcss.com/docs/guides/laravel
L’installation a dû générer deux fichiers tailwind.config.js
et postcss.config.js
.
Une fois le projet initialisé, lancé en local et la commande npm run dev
exécutée, nous pouvons commencer à travailler dessus.
Pour le moment, nous retrouvons cette fameuse landing page :
welcome.blade.php
Commençons par exécuter les migrations pour créer la table des utilisateurs :
sail artisan migrate
Ensuite, modifions le fichier database/seeders/DatabaseSeeder.php
pour ajouter des utilisateurs, comme suit :
<?php
namespace Database\Seeders;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/ public function run(): void
{
\App\Models\User::factory(10)->create();
}
}
Puis on finit par lancer la commande suivante pour créer les users :
sail artisan db:seed
Commençons par créer un Controller pour gérer nos utilisateurs. Pour cela, on va exécuter la commande suivante :
sail artisan make:controller UserController
Ensuite, créons une route pour accéder à notre page de liste des utilisateurs. Modifiez le fichier routes/web.php
et ajoutez la route suivante à la place de la route par défaut :
Route::get('/', [UserController::class, 'index'])->name('users.index');
Ensuite, créons la méthode index dans notre Controller pour retourner la vue de listing des utilisateurs. Pour cela, on va modifier le fichier app/Http/Controllers/UserController.php
et ajouter la méthode suivante :
public function index()
{
return view('users');
}
Puis, créons un layout pour notre application dans un fichier resources/views/components/layout.blade.php
et ajoutons-y le code suivant :
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@vite('resources/css/app.css')
<title>Enums powers</title>
</head>
<body class="antialiased">
<div class="relative min-h-screen bg-gray-100 bg-center">
<div class="p-6 mx-auto max-w-7xl lg:p-8">
{{ $slot }}
</div>
</div>
</body>
</html>
Ainsi, le code sur la page de listing des utilisateurs sera plus léger. On va donc créer la vue de listing des utilisateurs dans un fichier resources/views/users.blade.php
et y ajouter le code suivant :
<x-layout>
<h1 class="text-3xl font-bold">Users</h1>
<!-- We will insert the users list here later -->
</x-layout>
Pour pouvoir afficher la liste des utilisateurs, on va l’envoyer à la vue depuis le Controller. Pour cela, modifions la méthode index du Controller app/Http/Controllers/UserController.php
et ajoutons ceci :
public function index()
{
$users = User::all();
return view('users', compact('users'));
}
On va ensuite modifier la vue resources/views/users.blade.php
pour afficher la liste des utilisateurs comme ceci :
<x-layout>
<h1 class="text-3xl font-bold">Users</h1>
<ul>
@foreach ($users as $user)
<li class="px-4 py-2 my-4 bg-white border border-gray-300 rounded">
<p class="font-semibold">{{ $user->name }}</p>
<p class=text-gray-500>{{ $user->email }}</p>
</li>
@endforeach
</ul>
</x-layout>
Ce qui nous donne une belle liste d’utilisateurs :
Nous n’avons toujours pas de rôles à afficher pour nos utilisateurs. On va donc créer un Enum pour gérer les rôles. Pour cela, on va créer le fichier app/Enums/RoleEnum.php
et y ajouter le code suivant :
<?php
namespace App\Enums;
enum RoleEnum: string
{
case ADMIN = 'admin';
case USER = 'user';
case GUEST = 'guest';
}
Modifions maintenant le modèle app/Models/User.php
pour utiliser notre Enum. Pour cela, on va ajouter la ligne suivante dans la propriété $casts
:
protected $casts = [
/* ... */ 'role' => RoleEnum::class,
];
On va ensuite modifier la migration pour rajouter la colonne role
dans la table users
. Pour cela, modifions le fichier database/migrations/2014_10_12_000000_create_users_table.php
avec une nouvelle définition de la table users comme ci-dessous :
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('role')->default(RoleEnum::GUEST->value);
$table->rememberToken();
$table->timestamps();
});
Il faut aussi modifier la factory database/factories/UserFactory.php
pour y ajouter le rôle. Et transformer la définition comme-ceci :
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
'remember_token' => Str::random(10),
'role' => fake()->randomElement(RoleEnum::cases())->value,
];
Ce que nous avons fait ici, c’est récupérer les différents cas du RoleEnum et prendre au hasard la valeur d’un des cas pour créer le User en cours de seeding.
On modifie ensuite la vue resources/views/users.blade.php
pour afficher le rôle de chaque utilisateur :
<x-layout>
<h1 class="text-3xl font-bold">Users</h1>
<ul>
@foreach ($users as $user)
<li class="flex justify-between px-4 py-2 my-4 bg-white border border-gray-300 rounded">
<div>
<p class="font-semibold">{{ $user->name }}</p>
<p class=text-gray-500>{{ $user->email }}</p>
</div>
<p class="font-bold text-gray-500">{{ $user->role }}</p>
</li>
@endforeach
</ul>
</x-layout>
Ici, on a rajouté le rôle et utilisé la classe Tailwind flex
pour réorganiser les éléments.
Relançons les migrations et le seeding pour avoir des utilisateurs avec des rôles :
sail artisan migrate:fresh --seed
Ce qui nous donne cette nouvelle vue :
Admettons que je veuille ajouter une description pour chaque rôle, on va tenter de le faire sans les attributs dans un premier temps. On va donc modifier l’Enum pour y ajouter une méthode description comme ceci :
<?php
namespace App\Enums;
enum RoleEnum: string
{
case ADMIN = 'admin';
case USER = 'user';
case GUEST = 'guest';
public function description(): string
{
return match($this)
{
self::ADMIN => 'He\'s got full powers',
self::USER => 'A classic user, with classic rights',
self::GUEST => 'Oh, a guest, be nice to him!',
};
}
}
On va pouvoir modifier la vue resources/views/users.blade.php
pour afficher la description du rôle comme ceci :
<x-layout>
<h1 class="text-3xl font-bold">Users</h1>
<ul>
@foreach ($users as $user)
<li class="flex justify-between px-4 py-2 my-4 bg-white border border-gray-300 rounded">
<div>
<p class="font-semibold">{{ $user->name }}</p>
<p class=text-gray-500>{{ $user->email }}</p>
</div>
<div class="text-right">
<p class="font-bold text-gray-500">{{ $user->role }}</p>
<p>{{ $user->role->description() }}</p>
</div>
</li>
@endforeach
</ul>
</x-layout>
Et voilà le résultat :
Ce n’est pas la seule manière de faire ça à partir d’un Enum mais l’important est que ça génère du code pour chaque élément qu’on voudra ajouter à un Enum.
On va maintenant passer au vif du sujet, le refactoring.
Supprimons ce qui ne va plus nous servir, à savoir : – La méthode description
de l’Enum RoleEnum
On va utiliser les attributs PHP, qui sont disponibles depuis la version 8. On va donc créer un attribut pour pouvoir récupérer les informations de l’Enum. Si vous n’êtes pas familiers avec le concept, voici la documentation : https://www.php.net/manual/en/language.attributes.overview.php.
Pour utiliser un attribut, il faut créer une classe correspondante. Créons, tout d’abord, une classe AttributeProperty
qui sera la parente de tous nos attributs. On la place dans le dossier app/Attributes
et on y ajoute le code suivant :
<?php
namespace App\Attributes;
use Attribute;
/* Indicate that Attribute will be use only on constants */#[Attribute(Attribute::TARGET_CLASS_CONSTANT)]
class AttributeProperty
{
/* Will be useful to retrive attribute class later */ public const ATTRIBUTE_PATH = 'App\Attributes\\';
public function __construct(
private mixed $value,
) {
}
/**
* Get the value of the attribute
*/ public function get(): mixed
{
return $this->value;
}
}
Créons maintenant notre premier attribut, la description, pour remplacer notre exemple précédent. Pour ceci, on créer une classe Description
dans le dossier app/Attributes
et on y ajoute le code suivant :
<?php
namespace App\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS_CONSTANT)]
class Description extends AttributeProperty {}
Et ça suffit, notre classe d’attribut Description
est déjà fonctionnelle. On va pouvoir l’utiliser dans notre Enum.
Rajoutons là comme ci-dessous :
<?php
namespace App\Enums;
use App\Attributes\Description;
enum RoleEnum: string
{
#[Description('He\'s got full powers')]
case ADMIN = 'admin';
#[Description('A classic user, with classic rights')]
case USER = 'user';
#[Description('Oh, a guest, be nice to him!')]
case GUEST = 'guest';
}
Pour pouvoir utiliser un attribut il faut le récupérer. Pour cela, nous allons utiliser la Reflection API
qui permet de récupérer des informations concernant les classes, les méthodes, les propriétés, etc. Pour plus d’informations, voici la documentation : https://www.php.net/manual/en/book.reflection.php.
L’idée est de pouvoir utiliser cette méthode sur tous nos futurs Enums. On va donc créer un trait AttributableEnum
dans le dossier app/Traits
et y ajouter le code suivant :
<?php
namespace App\Traits;
trait AttributableEnum
{
/* ... */}
On ajoute ce trait à notre Enum app/Enums/RoleEnum.php
comme ceci :
<?php
namespace App\Enums;
use App\Attributes\Description;
use App\Traits\AttributableEnum;
enum RoleEnum: string
{
/* We use the trait we have created */ use AttributableEnum;
#[Description('He\'s got full powers')]
case ADMIN = 'admin';
#[Description('A classic user, with classic rights')]
case USER = 'user';
#[Description('Oh, a guest, be nice to him!')]
case GUEST = 'guest';
}
Notre objectif est d’appeler la description du rôle comme ceci dans la vue :
<x-layout>
<h1 class="text-3xl font-bold">Users</h1>
<ul>
@foreach ($users as $user)
<li class="flex justify-between px-4 py-2 my-4 bg-white border border-gray-300 rounded">
<div>
<p class="font-semibold">{{ $user->name }}</p>
<p class=text-gray-500>{{ $user->email }}</p>
</div>
<div class="text-right">
<p class="font-bold text-gray-500">{{ $user->role }}</p>
<!-- Call the attribute description for this Enum case -->
<p class="text-gray-500">{{ $user->role->description() }}</p>
</div>
</li>
@endforeach
</ul>
</x-layout>
Pour cela, on va modifier le trait app/Traits/AttributableEnum.php
et y ajouter la méthode suivante :
<?php
namespace App\Traits;
use App\Attributes\AttributeProperty;
use BadMethodCallException;
use Illuminate\Support\Str;
use ReflectionAttribute;
use ReflectionEnum;
trait AttributableEnum
{
/**
* Call the given method on the enum case
*
*/ public function __call(string $method, array $arguments): mixed
{
// Get attributes of the enum case with reflection API
$reflection = new ReflectionEnum(static::class);
$attributes = $reflection->getCase($this->name)->getAttributes();
// Check if attribute exists in our attributes list
$filtered_attributes = array_filter($attributes, fn (ReflectionAttribute $attribute) => $attribute->getName() === AttributeProperty::ATTRIBUTE_PATH . Str::ucfirst($method));
// If not, throw an exception
if (empty($filtered_attributes)) {
throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', static::class, $method));
}
return array_shift($filtered_attributes)->newInstance()->get();
}
}
Pour détailler le processus :
__call()
nous permet d’intercepter un appel à une méthode qui n’existe pas sur notre classe. Comme il n’existe pas de méthode description()
sur notre Enum, on va pouvoir l’intercepter et faire ce que l’on veut avec.ReflectionEnum
permet de récupérer toutes les informations de notre RoleEnum
. On va pouvoir récupérer les attributs de notre Enum de la manière suivante : $reflection->getCase($this->name)->getAttributes()
. $this->name
correspond au nom du cas de l’Enum.Description
avec la méthode array_filter
. On utilise la constante AttributeProperty::ATTRIBUTE_PATH
pour récupérer le chemin complet de notre attribut Description
qui est App\Attributes\Description
.BadMethodCallException
pour indiquer que la méthode n’existe pas.get()
pour récupérer la valeur qui nous intéresse.Et voilà, avec la combinaison du Trait et des attributs PHP, on a donné des super pouvoirs à nos Enums.
On va maintenant créer un nouvel attribut pour gérer la couleur de fond de chaque rôle. Pour cela, on va ajouter une classe BackgroundColor
dans le dossier app/Attributes
et y ajouter le même code que pour l’attribut Description
pour l’instant.
On va aller un peu plus loin en faisant en sorte de récupérer directement la classe Tailwind pour la couleur de fond.
<?php
namespace App\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS_CONSTANT)]
class BackgroundColor extends AttributeProperty
{
public function __construct(
private mixed $value,
) {
}
/**
* Get the value of the attribute
*/ public function get(): string
{
return 'bg-' . $this->value . '-100';
}
}
Et ça y est, on peut l’utiliser dans notre Enum app/Enums/RoleEnum.php
comme ceci :
enum RoleEnum: string
{
use AttributableEnum;
#[Description('He\'s got full powers')]
#[BackgroundColor('red')]
case ADMIN = 'admin';
#[Description('A classic user, with classic rights')]
#[BackgroundColor('blue')]
case USER = 'user';
#[Description('Oh, a guest, be nice to him!')]
#[BackgroundColor('green')]
case GUEST = 'guest';
}
Et on peut l’utiliser dans notre vue resources/views/users.blade.php
, en modifiant également le style de la page, comme ceci :
<x-layout>
<h1 class="text-3xl font-bold">Users</h1>
<ul>
@foreach ($users as $user)
<li class="flex justify-between px-4 py-2 my-4 bg-white border border-gray-300 rounded">
<div class="flex flex-col justify-around">
<p class="text-lg font-semibold">{{ $user->name }}</p>
<p class=text-gray-500>{{ $user->email }}</p>
</div>
<!-- We add the class generated by the Attribute here -->
<div class="text-right px-4 py-1 mx-1 my-2 rounded-md {{ $user->role->backgroundColor() }}">
<p class="font-bold text-gray-800/50">{{ $user->role }}</p>
<!-- Call the attribute description for this Enum case -->
<p class="text-gray-500">{{ $user->role->description() }}</p>
</div>
</li>
@endforeach
</ul>
</x-layout>
Tailwind, par défaut, génère seulement les classes présentes dans le code. Comme nos classes sont générées dynamiquement, on va devoir rajouter une configuration pour que Tailwind génère les classes de background-color. Pour cela, on va modifier le fichier tailwind.config.js
en y ajoutant une safelist comme ci-dessous :
/** @type {import('tailwindcss').Config} */export default {
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
"./resources/**/*.vue",
],
theme: {
extend: {},
},
safelist: [
{
/* We want any bg color class to be generated */
pattern: /^bg-\w+-\d{2,3}$/,
}
],
plugins: [],
}
Et voilà le résultat:
Pour finir, on voudrait que le nom du rôle soit affiché d’une manière plus jolie. On va donc créer un attribut Label
pour ça. On va créer une classe Label
dans le dossier app/Attributes
et y ajouter le code suivant :
<?php
namespace App\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS_CONSTANT)]
class Label extends AttributeProperty {}
On l’applique à notre Enum app/Enums/RoleEnum.php
comme ceci :
<?php
namespace App\Enums;
use App\Attributes\BackgroundColor;
use App\Attributes\Description;
use App\Attributes\Label;
use App\Traits\AttributableEnum;
enum RoleEnum: string
{
use AttributableEnum;
#[Label('Administrator')]
#[Description('He\'s got full powers')]
#[BackgroundColor('red')]
case ADMIN = 'admin';
#[Label('User')]
#[Description('A classic user, with classic rights')]
#[BackgroundColor('blue')]
case USER = 'user';
#[Label('Guest')]
#[Description('Oh, a guest, be nice to him!')]
#[BackgroundColor('green')]
case GUEST = 'guest';
}
On peut, à présent, l’afficher dans la vue resources/views/users.blade.php
comme ceci :
<x-layout>
<h1 class="text-3xl font-bold">Users</h1>
<ul>
@foreach ($users as $user)
<li class="flex justify-between px-4 py-2 my-4 bg-white border border-gray-300 rounded">
<div class="flex flex-col justify-around">
<p class="text-lg font-semibold">{{ $user->name }}</p>
<p class=text-gray-500>{{ $user->email }}</p>
</div>
<!-- We add the class generated by the Attribute here -->
<div class="text-right px-4 py-1 mx-1 my-2 rounded-md {{ $user->role->backgroundColor() }}">
<!-- We use the value from Label attribute here -->
<p class="font-bold text-gray-800/50">{{ $user->role->label() }}</p>
<!-- Call the attribute description for this Enum case -->
<p class="text-gray-500">{{ $user->role->description() }}</p>
</div>
</li>
@endforeach
</ul>
</x-layout>
Et voilà la version finale :
On peut avoir besoin de greffer pas mal de choses à des Enums, pour ma part, mon besoin était sur un choix de prestation, pour les valeurs d’un select
dans un formulaire de réservation. J’avais besoin d’éléments de contenu formatés, d’informations complémentaires, et d’éléments utiles aux règles de validation. Cette approche a simplifié mon code et a apporté une grande flexibilité dans le développement.
Si vous ne voulez pas le faire vous-même, mais que vous souhaitez utiliser un package, je vous conseille d’aller voir celui-ci, il m’a donné l’idée de creuser ce sujet et propose des ajouts intéressants sur les Enums: https://github.com/archtechx/enums
Pour finir, si vous vous posez des questions sur des sujets de dev, n’hésitez pas à creuser, on apprend beaucoup en le faisant.
À propos de moi :
Développeur Fullstack depuis 3 ans suite à une reconversion professionnelle après 5 ans de travail dans le Webmarketing.
J’ai fait ma formation de développeur chez Oclock Je travaille en CDI dans une entreprise spécialisée en marketing / gestion de projet B2B. Mon quotidien professionnel c’est de développer sur une plateforme d’e-commerce et un ERP maison qui couvre tous les métiers de notre entreprise. Je travaille sur un framework maison, sur Laravel et sur VueJs. Et côté perso je suis passionné par le web plus créatif, je travaille beaucoup avec VueJs, ThreeJs, Gsap pour faire des projets fun.
Retrouver moi sur X
Les maladies inflammatoires chroniques de l’intestin ou "MICI" sont invisibles, mais leurs impacts sur la…
Depuis l'été, j'ai un Pixel qui intègre à la fois un TPU (Tensor Processing Unit)…
On se retrouve dans un nouvel article avec toutes les infos sur cette nouvelle saison…
Pourquoi l’inclusion numérique est essentielle : le point avec Mathieu Froidure. Dans un monde de…
Elles sont passées où les femmes dans la tech ? Entre le manque de représentation…