戏里戏外

Laravel 中的枚举

2024-10-23#Laravel

枚举是在 PHP 8.1 中引入的,Laravel 充分利用了它的强大功能,这篇文章一起来看看如何在 Laravel 项目中使用枚举。

基础

在 Laravel 迁移中,可以定义一个可以保存多个预定义值的数据库列,列的值用于存储枚举的值。

<?php enum PostStatus: string { case DRAFT = 'draft'; case PUBLISHED = 'published'; }

在示例中,此枚举类 PostStatus 为博客文章定义了两种状态,它的状态可以是draftpublished

下面是一个示例,说明如何设置迁移以根据这些值存储博客文章的状态:

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 值包含枚举中定义的 draftpublished

也可以指定限制某些枚举值:

\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 枚举类中。