4
6

LaravelでのEnum型の使い方まとめ

Posted at

記事を書いた動機

以下の経緯でEnumを社内に布教したかったから。

  • Laravelを使う際、社内で定数をどこに書くか明確なルールが無かったので定義したかった
  • configに書くか、Modelに書くか、定数用のクラスを作成してそこに書くか3つ案があったが、以下の理由によりEnumで管理するようにしたいと思った
    • config()は定義ジャンプができないので定数の値を見に行くのが面倒
    • 定数は条件分岐とかでいろんな場所から呼ばれるので、定数使うためだけにModelを呼ぶのはあまりやりたくない
      • 例えばBladeから@foreach(App\Models\Order::STATUS_LIST as $key =>$val) みたいに呼ぶのはいまいちかなと
    • 定数用のクラスで管理する場合、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と違って弱い比較(==)ではなく、 型と値の一致チェック(===) をします

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使うと便利なところ

カーソル合わせるだけで定義を確認できるし、定義ジャンプもできる

スクリーンショット 2024-05-29 145821.png

キャスト

  • DBの値にEnumで定義した値以外が入ることを防げる
    • DBの制約ではtiny intの場合、存在しないRoleである3 が入ることを防げないが、Enumキャストを使うと実行時エラーになり防げます
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
4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6