枚举是在 PHP 8.1 中引入的,Laravel 充分利用了它的强大功能,这篇文章一起来看看如何在 Laravel 项目中使用枚举。
基础
在 Laravel 迁移中,可以定义一个可以保存多个预定义值的数据库列,列的值用于存储枚举的值。
<?php
enum PostStatus: string
{
case DRAFT = 'draft';
case PUBLISHED = 'published';
}
在示例中,此枚举类 PostStatus
为博客文章定义了两种状态,它的状态可以是draft
或 published
。
下面是一个示例,说明如何设置迁移以根据这些值存储博客文章的状态:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('status');
$table->timestamps();
});
以下是将枚举值 draft
存储到 status
列上。
Post::create([
'title' => 'A new post',
'status' => PostStatus::DRAFT,
]);
枚举强制转换
在上面 Post
模型上的 PostStatus
枚举示例,当打印 status
列时会发生什么:
$post = Post::find(1);
dd($post->status); // 'draft'
假设将 status
字段的值存储为 draft
,实际上会得到字符串 draft
,这并不是很有帮助,如果充分利用了枚举,理想情况下希望将 PostStatus
枚举实例返回给我们。
所以在模型中需要这样设置:
class Post extends Model
{
protected function cases(): array
{
return [
'status' => PostStatus::class,
];
}
}
现在,当获取 status
列时,会返回 PostStatus
实例。
$post = Post::find(1);
dd($post->status);
//App\Enums\PostStatus {
// +name: "DRAFT"
// +value: "draft"
//}
这样可以快速轻松的根据代码中定义的状态进行判定
if ($post->status === PostStatus::DRAFT) {
// Post is a draft
}
为了后续的功能也可以为每个枚举值扩展一个获取标签的 getLabel()
方法。
<?php
enum PostStatus: string
{
case DRAFT = 'draft';
case PUBLISHED = 'published';
public function getLabel(): string {
return match($this) {
PostStatus::DRAFT => 'Draft',
PostStatus::PUBLISHED => 'Published',
};
}
}
现在可以很容易的获得应该根据状态显示的标签:
$post = Post::find(1);
dd($post->status->getLabel()); // 'Draft'
枚举数组
如果模型可以包含多个状态,则还可以转换为一个枚举集合 Collection
,比如 Post
模型添加一个 review_flags
字段用于存储包含各种审查标识。
Schema::table('posts', function (Blueprint $table) {
$table->json('review_flags');
});
下面是一个新的枚举 PostReviewFlags
它包含所有可能的审查标识:
enum PostReviewFlags: string
{
case OUTDATED = 'outdated';
case SPELL_CHECK = 'spell_check';
}
在模型中可以这样转换 review_flags
字段:
class Post extends Model
{
public function casts()
{
return [
'status' => PostStatus::class,
'review_flags' => AsEnumCollection::of(PostReviewFlags::class)
];
}
}
一旦创建的文章过时了并且需要重新检查,现在可以像这样更新它:
$post = Post::find(1);
$post->update([
'review_flags' => [
PostReviewFlags::OUTDATED,
PostReviewFlags::SPELL_CHECK,
]
]);
这将像这样将值存储在数据库中:
["outdated", "spell_check"]
由于使用的模型进行强制类型转换,因此尝试访问模型的 review_flags
属性时,会得到以下结果:
$post = Post::find(1);
dd($post->review_flags);
//Illuminate\Support\Collection {
// #items: array:2 [
// 0 => App\Enums\PostReviewFlags {#1074 ▶}
// 1 => App\Enums\PostReviewFlags {#1004 ▶}
// ]
//}
可以通过执行以下操作来检查帖子是否被标记为 outdated
:
if ($post->review_flags->contains(PostReviewFlags::OUTDATED)) {
// It's outdated!
}
使用枚举验证数据
按照这个 Post
例子,如果管理界面允许为文章选择状态。可以这样编写验证规则:
use App\Enums\PostStatus;
use Illuminate\Validation\Rule;
Route::post('/posts', function (Request $request) {
$request->validate([
'status' => ['required', Rule::enum(PostStatus::class)]
]);
});
这可确保在表单中提交的 status
值包含枚举中定义的 draft
或 published
。
也可以指定限制某些枚举值:
\Illuminate\Validation\Rule::enum(PostStatus::class)->only([PostStatus::DRAFT]); // 仅包含 draft
\Illuminate\Validation\Rule::enum(PostStatus::class)->except([PostStatus::PUBLISHED]); // 忽略 published
更多个复杂验证逻辑可以参考官方文档 Validation。
隐式枚举绑定
与路由模型绑定一样,Laravel
也支持使用枚举值在路由中进行绑定。假设专门创建了一个路由来显示所有状态为 draft
帖子:
Route::get('/posts/{status}', function (PostStatus $status) {
$posts = Post::where('status', $status->value)->get();
dd($posts);
});
此时无论访问 /posts/published
还是 /posts/draft
这都将馈送到查询生成器中,并根据选择的状态返回帖子列表。
如果访问 /posts/nope
,这将失败并返回 404。因为 nope
值不存在于 PostStatus
枚举类中。