Enumerations: We have time for them. Crafting clearer, more reliable code
Published on March 21st, 2024.
Questions
If you find yourself needing to categorize roles like 'admin', 'user', 'manager' or 'user non classified' within your user table in a Laravel application.
What approach would you take in your Laravel application? 🤔
- Should you use a native PHP Enum?
- Should you use a tool made for Laravel called spatie/laravel-enum?
- Maybe you could make a relationship table to show the roles?
- Or should you just create a column in you database and expects that everyone undestands?
Each of these approaches has its own pros and cons. In this particular case, I chose to use a Laravel Enum.
The goal
The goal of this article is to show you how to use Laravel Enum to create a more reliable and clear code. We will also see how to use it in migration, model, factory and how to test it.
What is an Enum?
<?php
enum UserRole: string
{
case 'ADM' = 'Admin',
case 'USR' = 'User',
case 'MNG' = 'Manager',
case 'NON' = 'None',
public function label(): string
{
return match($this) {
static::Admin => 'Admin',
static::User => 'User',
static::Manager => 'Manager',
static::None => 'User non classified',
};
}
}
?>
Why Laravel Enum?
Laravel Enum is a package that allows you to create and use enumerations in your Laravel application. It is a simple and powerful package that can help you to create more reliable and clear code. It is also allows you to use pollimorphic relationships instead of native PHP enums that you can't.
"Enums cannot be extended, and must not inherit"
Instalation
In this example I'll use a empty laravel application.
Then, install the package:
Inputs
Enum
Use the following command to create a new enum: Past this code to the new file:
<?php
namespace App\Enums;
use Spatie\Enum\Laravel\Enum;
/**
* The Status enum.
*
* @method static self ADM()
* @method static self USR()
* @method static self MNG()
* @method static self NON()
*/
class UserRoleEnum extends Enum
{
const DEFAULT = 'NON';
protected static function values(): array
{
return [
'ADM' => 'Admin',
'USR' => 'User',
'MNG' => 'Manager',
'NON' => 'User non classified',
];
}
}
Migration
Let's create a new migration to add the role column to the users table: Past this code to the new file:
<?php
use App\Enums\UserRoleEnum;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->enum(role, UserRoleEnum::toArray())->default(UserRoleEnum::DEFAULT)->after('password');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(role);
});
}
};
Factory
<?php
namespace Database\Factories;
use App\Enums\UserRoleEnum; // ADD THIS
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
...
public function definition(): array
{
return [
'name' => fake()->name(),
...
'role'' => fake()->randomElement(UserRoleEnum::toArray()), // ADD THIS
];
}
...
}
Model
I like to adjust the model to use the enum description. For example:
If the users role is 'ADM' the description will be 'Admin'. And I also adjust set method to ensure that will convert to database required enum.
<?php
namespace App\Models;
use App\Enums\UserRoleEnum; // ADD THIS
class User
{
...
public function getRoleAttribute($value): string
{
return UserRoleEnum::from($value)->value;
}
public function setRoleAttribute($value): void
{
if (!empty($value))
// check if value is a value or label
if (in_array($value, UserRoleEnum::toValues()))
$this->attributes[role] = UserRoleEnum::from($value)->label;
else if (in_array($value, UserRoleEnum::toLabels()))
$this->attributes[role] = $value;
else
throw new \InvalidArgumentException('Invalid User role value.');
else
$this->attributes[role] = UserRoleEnum::DEFAULT;
}
}
Let's check with tinker
Lets open it and check if everything is working as expected:
$users = User::find(3);
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
= App\Models\User {#5050
id: 3,
name: "Tamia Borer",
email: "brenna13@example.org",
email_verified_at: "2024-03-21 20:04:50",
#password: "$2y$12$S5bDxjRM4E6nuXQzTX2BjeJKVWhvzi5sMe0UZiLs5PxB0fg7y.Y4O",
#remember_token: "xyOyRzeuMV",
created_at: "2024-03-21 20:04:50",
updated_at: "2024-03-21 20:04:50",
role: "NON",
}
> $users->role
= "User non classified"
> App\Enums\UserRoleEnum::ADM()->label
= "ADM"
> App\Enums\UserRoleEnum::ADM()->value
= "Admin"
Testing
I will create a Test to ensure that the enum is working as expected.
Let's install Pest;
Past this code to the new file:
<?php
//create a new users and check if the users role is Ini
use App\Models\User;
use App\Enums\UserRoleEnum;
test('create users random role', function () {
$users = User::factory()->create();
expect($users->role)->toBeString();
})->skip('This test is skipped because the users role is random');
test('create users with role ADM', function () {
$users = User::factory()->create([role => UserRoleEnum::ADM()->label]);
expect($users->role)->toBe('Admin');
});
test('create users with role Admin', function () {
$users = User::factory()->create([role => UserRoleEnum::ADM()->value]);
expect($users->role)->toBe('Admin');
});
test('does not set an invalid role attribute', function () {
$users = new User();
$invalidRole = 'InvalidRole';
// Act & Assert
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid users role value.');
// Attempt to set an invalid role attribute
$users->setroleAttribute($invalidRole);
});
Run the test:
Conclusion
Enumerations are a great way to create more reliable and clear code. Laravel Enum is a powerful package that can help you to create enumerations in your Laravel application. It is simple to use and can help you to create more reliable and clear code. I hope this article has helped you to understand how to use Laravel Enum to create more reliable and clear code.