【Laravel】PHP 8.1 から使える Enumで、選択肢〜バリデーションまで実装してみる

こんにちは❗フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、「プログラミング言語」と一口に言っても世の中にはPHP以外にもPyhonJavaなどたくさんのものが存在しています。

そして、そんな他の言語を使ったことがある人なら、(きっと)思うこと。

それは・・・・・・

あっちの言語だったらできたのにな…😅

です。

結構前ですが、私はAndroidアプリを開発していたのですが、そのとき「これ便利だな」と感じていたのが今回テーマの

Enum(いーなむ)

です。

Enumとは、簡単にいうと「先にグループのメンバーを決めておく」仕組みです。

例えば、十二支で見てみましょう。

もちろんですが、この中には「パンダ」や「カモノハシ」は入ることはできません。もうグループ・メンバーが決定しているからです。

では、なぜグループ化するのかというと「予期せぬ値を扱わないようにできる」というメリットがあるからですね。

そして、長らくPHPにはこのEnumの機能がついていなかったのですが、実は次期バージョン8.1(2021.11.25 リリース予定)から使えるようになります。

そこで❗

今回は、Laravel + Enumでセレクトボックスをつくり、さらに、それを送信したときのバリデーションまで実装してみることにしました。

ぜひ何かの参考になりましたら嬉しいです。

※ なお、PHP 8.1のリリースの関係で記事公開の順番を変更させていただきました。「Laravel + Leaflet 地図で駅名検索できるようにする」は来週公開させていただきます。もし楽しみにしていただいていたらゴメンナサイ。m(_ _)m

「もうすぐ8.1リリースですよ👍」

開発環境: PHP 8.1.0(RC6)、Laravel 8.70

選択肢について

今回Enumでつくる選択肢は「書籍のカテゴリ」で、以下のとおりです。

  • 雑誌
  • 新聞

では実際に作業を進めていきましょう❗

Enumをつくる

では、早速Enumをつくりましょう。

とはいっても、Enumはよく開発でもつかう「クラス」をもっとシンプルにしたものと考えて問題ないので、そんなに複雑ではありません。

実際のコードはこうなります。

app/Enums/Category.php

<?php

namespace App\Enums;

enum Category: int
{
    // 基本情報
    case Book = 1;
    case Magazine = 2;
    case Newspaper = 3;

    // 日本語を追加
    public function label(): string
    {
        return match($this)
        {
            Category::Book => '本',
            Category::Magazine => '雑誌',
            Category::Newspaper => '新聞',
        };
    }
}

まず、Enumで必ず必要になってくるのが「名前」で、今回では以下の3つになります。

  • Book
  • Magazine
  • Newspaper

これだけでもEnumとしては使えるのですが、category_idとしてデータベースに保存することを想定しているので、今回はそれぞれ13のIDを追加しています。

なお、

enum Category: int

と書いているのは、このIDが数値だからです。もし文字列を使いたい場合はstringになりますので、気をつけてください。(enumでは、TypeScriptみたいに型が重要になってきます)

そして、日本語環境ではセレクトボックスの選択肢に「Book」や「Magazine」と表示されるのはあまり使い勝手がいいとはいえません。

そのため、それぞれ日本語名を追加します。

それが、label()の部分です。(なお、labelという名前は好きに変更してOKですよ👍)

そして、以下のようにすると日本語名を取得できるようになります。

$label = Category::Book->label(); // 本

コントローラーをつくる

では、Enumのテスト用にコントローラーをつくります。
以下のコマンドを実行してください。

php artisan make:controller EnumController

すると、ファイルが作成されるので中身を以下のようにしてください。

app/Http/Controllers/EnumController.php

<?php

namespace App\Http\Controllers;

use App\Enums\Category;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Validation\Rule;

class EnumController extends Controller
{
    public function create()
    {
        $categories = Category::cases();

        return view('enum.create', compact('categories'));
    }

    public function store(Request $request)
    {
        $categories = Category::cases();
        $category_ids = Arr::pluck($categories, 'value');

        $request->validate([
            'category_id' => ['required', Rule::in($category_ids)]
        ]);

        return 'バリデーション通過しました!';
    }
}

まず、create()では、先ほどつくったenumをビューに渡しています。

また、store()では選択されたIDが「事前にセットした3つの中に入っているか?」をチェックしています。

ビューをつくる

では、先ほど指定したビューをつくっていきましょう。

resources/views/enum/create.blade.php

<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="p-5">

    <h1 class="mb-5">Enum(PHP 8.1 以上) を使って<br>選択肢をつくるサンプル</h1>

    <div class="row">
        <div class="col-4">

            <form action="/enum" method="POST">
                @csrf

                <select name="category_id" class="form-control">
                    <option value="">▼ 以下から選択</option>
                    @foreach($categories as $category)
                        <option value="{{ $category->value }}">{{ $category->label() }}</option>
                    @endforeach
                </select>

                @error('category_id')
                    <div class="alert alert-danger">{{ $message }}</div>
                @enderror

                <button type="submit" class="btn btn-primary mt-3">送信する</button>

            </form>

        </div>
    </div>
</div>
</body>
</html>

この中で重要なのが、セレクトボックスをループでつくっているところです。

つまり、今回の場合で言うと、以下のようにすることでそれぞれの値を取得することができます。

  • $category->name: Book、Magazine、Newspaper
  • $category->value: 1、2、3
  • $category->label(): 本、雑誌、新聞

そして、これを使えばJavaScript側にも配列やオブジェクトでEnumの中身を使えるようになるというわけですね。

ルートをつくる

では、コントローラーでつくったメソッドをルートとして登録しておきましょう。

use App\Http\Controllers\EnumController;

// 省略

Route::get('enum/create', [EnumController::class, 'create']);
Route::post('enum', [EnumController::class, 'store']);

これで作業は完了です❗

お疲れ様でした😄✨

ちなみに: JavaScript 側へセットするには

ちなみに、例えばVue 3Enumの中身をセットする場合は次のようになります。

Vue.createApp({
    data() {
        return {
            categories: [
                @foreach($categories as $category)
                {
                    id: {{ $category->value }},
                    name: '{{ $category->name }}',
                    label: '{{ $category->label() }}'
                },
                @endforeach
            ]
        }
    },
}).mount('#app');

また、以下のようにkey-value形式のオブジェクトにすることもできます。

Vue.createApp({
    data() {
        return {
            categories: {
                @foreach($categories as $category)
                    {{ $category->value }}: '{{ $category->label() }}',
                @endforeach
            }
        }
    },
}).mount('#app');

テストしてみる

では、実際にテストしてみましょう❗

https://******/enum/create」へブラウザからアクセスしてください。

すると、セレクトボックスが表示されるので、この中から「」を選択肢、送信してみましょう。

すると・・・・・・

はい❗
うまくバリデーションを通過できました。

ただ、これだけでは本当にちゃんとEnum以外のものを拒否してくれるのかわからないので、次に選択肢にない「技術書」をわざとセレクトボックスに追加して送信してみましょう。

<select name="category_id" class="form-control">
    <option value="">▼ 以下から選択</option>
    @foreach($categories as $category)
        <option value="{{ $category->value }}">{{ $category->label() }}</option>
    @endforeach
    <!-- ⚠ これは Enum には入っていない選択肢です! -->
    <option value="999">技術書</option>

</select>

では、もう一度リロードして選択しなおします。

どうなったでしょうか・・・・・・

はい❗

バリデーション・メッセージは英語のままですが、うまくエラーを表示してくれました。

成功です😄✨

企業様へのご提案

今回のように、「これしかありえない選択肢」を使うことで、より想定外の出来事が起きにくいコードを書くことができます。

また、Enumは使い回しもしやすいですので、開発の効率化にも役立つかと思います。

もしそういったご依頼がございましたら、お気軽にお問い合わせからご連絡ください。

どうぞよろしくお願いいたします。m(_ _)m

おわりに

ということで、今回はPHP 8.1から使えるようになるEnumLaravelで使ってみました。

これまでグループ化された定数を使う場合、以下のようにモデルの中に入れるパターンや、Laravelconfigフォルダの中にセットするパターンが多かったかもしれませんが、これからはEnumを使うのもありじゃないでしょうか。

class Category extends Model
{
    use HasFactory;

    const BOOK = 1;
    const MAGAZINE = 2;
    const NEWSPAPER = 3;

    const CATEGORIES = [
        self::BOOK => '本',
        self::MAGAZINE => '雑誌',
        self::NEWSPAPER => '新聞',
    ];
}

ぜひ、みなさんも一度試してみてくださいね。

ではでは〜❗

「この標識に勇気づけられました。
挫折禁止 😂✨」

 

開発のご依頼お待ちしております 😊✨
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly  

開発効率を上げるための機材・まとめ