記事を書いた動機
以下の経緯でEnumを社内に布教したかったから。
- Laravelを使う際、社内で定数をどこに書くか明確なルールが無かったので定義したかった
- configに書くか、Modelに書くか、定数用のクラスを作成してそこに書くか3つ案があったが、以下の理由によりEnumで管理するようにしたいと思った
-
config()
は定義ジャンプができないので定数の値を見に行くのが面倒 - 定数は条件分岐とかでいろんな場所から呼ばれるので、定数使うためだけにModelを呼ぶのはあまりやりたくない
- 例えばBladeから
@foreach(App\Models\Order::STATUS_LIST as $key =>$val)
みたいに呼ぶのはいまいちかなと
- 例えばBladeから
- 定数用のクラスで管理する場合、
app/Const/GlobalConst
みたいに普通のクラスで書いてもいいが、PHP8.1以上ならEnum型が使えるので今後は、Enumで管理する方が色々便利かなーと思った
-
この記事で書くこと
- Laravelで定数をEnumで管理する際の具体的な使い方など
この記事で書かないこと
- PHPのEnum型の詳しい仕様
Enumってなに?
参考
要点だけ抜粋すると
- EnumとはPHP8.1から利用できるようになった列挙型(Enumerations) で、取りうる値を限定した独自の型を定義できる
- クラスと同じく、メソッドを定義したり、インタフェースやトレイトを実装可能
- クラスと違って、 newを使って直接インスタンス化や、継承はできない
- スカラー値を持たないPure Enumとスカラー値を持つBacked Enum2種類ある
- 現在config()に書いてるようなものは、基本的にBacked Enumだと思います
- Backed Enumが持てるスカラー値にはいくつかの制約がある
- intまたはstringの値のみを持つことができる(bool, floatは不可)
- 単一の型のみを持つことができる(Union型は不可)
- Backed Caseの値は全てユニークでなければならない
- スカラー値はリテラルかリテラルを表す式でなければならない(定数はサポートしていない)
Pure Enum
enum Suit
{
case Hearts;
case Diamonds;
case Clubs;
case Spades;
}
Backed Enum
enum Suit: int
{
case Hearts = 1;
case Diamonds = 2;
case Clubs = 3;
case Spades = 4;
}
-
Suit::Hearts->name
を呼び出すことで、Hearts
の名前を取得できます -
Suit::Hearts->value
を呼び出すことで、Hearts
のスカラー値を取得できます
LaravelでEnum使うとこんな感じ
前提
- 以降の説明はLaravel9以上の使用を前提としています
実装
1.まずApp/Enums/Role.phpを作成します。
- 日本語名表示用の
label()
メソッドを実装しています。- match式 (PHP8で入ったswitch文みたいなやつ)で書くとbreak要らないのでスッキリ書けます
- switchと違って弱い比較(
==
)ではなく、 型と値の一致チェック(===
) をします
- switchと違って弱い比較(
- match式 (PHP8で入ったswitch文みたいなやつ)で書くとbreak要らないのでスッキリ書けます
namespace App\Enums;
enum Role: int
{
case ADMIN = 1;
case MEMBER = 2;
public function label(): string
{
return match ($this) {
Role::ADMIN => '管理者',
Role::MEMBER => '一般',
};
}
}
2.Enumで定義したいDBのカラムをModelでキャストする
App/Models/User.php
use App\Enums\Role;
class User extends Model
{
protected $casts = [
'role' => Role::class,
];
基本的な使い方
$user = User::find(1);
// キャストされたroleにアクセスするとEnum型で取得される
$user->role
// App\Enums\Role {#7622
// +name: "ADMIN"
// +value: 1
// }
// name と value プロパティがある
$user->role->name // "ADMIN"
$user->role->value // 1
// 実装したlabel()メソッドの実行
$user->role->label() // "管理者"
// スカラー値からEnum~のcaseを返す
Role::tryFrom(1); //Role::ADMIN
Role::from(1); //Role::ADMIN
// スカラー値に対応するcaseが存在しない場合に、fromではErrorが生じますが、tryFromではnullを返します
Role::tryFrom(999); //null
Role::from(999); //Error
定数として値だけ使うとき
use App\Enums\Role;
// 管理者ユーザーだけ取得
$users = User::where('role', Role::ADMIN)->get();
use App\Enums\Role;
if ($user->role === Role::ADMIN) {
// 管理者の場合の処理config
}
セレクトボックスやラジオボタンなどリスト表示したいとき
-
cases()
メソッドを使うと以下のようにEnumの連想配列を全case取得できる - これをループで回してvalueプロパティや日本語名表示用の
label()
を実行して表示する
Role::cases();
// [
// App\Enums\Role {#7620
// +name: "ADMIN",
// +value: 1,
// },
// App\Enums\Role {#7617
// +name: "MEMBER",
// +value: 2,
// },
// ]
<label for="role">権限</label>
@foreach (\App\Enums\Role::cases() as $role)
<div class="custom-control custom-radio @error('role') is-invalid @enderror">
<input type="radio" name="role" value="{{ $role->value }}"
@checked((int) old('role') === $role->value)>
<label>{{ $role->label() }}</label>
</div>
@endforeach
\App\Enums\Role とBladeに書くのがだるいときは、以下のようにControllerから渡してあげても良いかと思います。
public function create(): View
{
$roleList = Role::cases();
return view('admin.user.create', compact('roleList'));
}
@foreach ($roleList as $role)
//
@endforeach
config()には無いEnum使うと便利なところ
カーソル合わせるだけで定義を確認できるし、定義ジャンプもできる
キャスト
- DBの値にEnumで定義した値以外が入ることを防げる
- DBの制約では
tiny int
の場合、存在しないRoleである3
が入ることを防げないが、Enumキャストを使うと実行時エラーになり防げます
- DBの制約では
use App\Enums\Role;
class User extends Model
{
protected $casts = [
'role' => Role::class,
];
$user = find(1);
$user->role = 3; // 3 is not a valid backing value for enum App\Enums\Role.
バリデーション
-
Enum
ルールは対象のフィールドに有効なenumの値が含まれているかどうかをバリデートします-
only
メソッドとexcept
メソッドを使用し、有効な列挙ケースを制限できます
use App\Enums\ServerStatus; use Illuminate\Validation\Rule; $request->validate([ 'status' => [Rule::enum(ServerStatus::class)], ]); Rule::enum(ServerStatus::class) ->only([ServerStatus::Pending, ServerStatus::Active]); Rule::enum(ServerStatus::class) ->except([ServerStatus::Pending, ServerStatus::Active]);
-
ルーティング
- ルート定義に値に依存したBacked Enumsをタイプヒントすることができ、Laravelはそのルートセグメントが有効なEnum値に対応する場合のみルートを呼び出します。そうでない場合は、404 HTTPレスポンスが自動的に返されます
- 下の例は、
{category}
ルートセグメントが、fruits
またはpeople
である場合にのみ呼び出されるルートを定義している。それ以外の値は404になる。
- 下の例は、
<?php
namespace App\Enums;
enum Category: string
{
case Fruits = 'fruits';
case People = 'people';
}
use App\Enums\Category;
use Illuminate\Support\Facades\Route;
Route::get('/categories/{category}', function (Category $category) {
return $category->value;
});
Laravel11からartisanコマンドで生成できる
- 以下のmakeコマンドが追加されたのでenumもコマンドで作成できるようになります
php artisan make:class
php artisan make:enum
php artisan make:interface
php artisan make:trait