PHP8.1からEnum(列挙型)が使えるようになりました。
そのため、Laravel 8.xからEnumを使えるような機能がいくつかマージされてます。その中でも今回はリクエストパラメータなどバリデーションに対して、Enumを用いてチェックできるようになったので、それを試していきたいと思います。
日本語のドキュメントでいうとここになります。ただ、ここだけだと全然情報量が足りないので、ソースも見ながら理解していきたいと思います。
Unit Enum と Pure Enum と Backed Enum の違い
今回試していて、そもそもEnumの種類を理解してないとバリデーションの処理がうまく実装できなかったので、最初にUnit Enum
とPure Enum
とBacked Enum
について触れていきます。
Unit EnumとPure EnumとBacked Enumを並べて書いているが、主にはPure EnumとBacked Enumの違いが重要そうです。
Unit Enum
Enum全体にたいしての言葉っぽい?
Unit Enumインターフェイスは、全ての列挙型に対して、PHP のエンジンが自動的に適用するものです。このインターフェイスは、型チェックのためだけに存在しています。
interface UnitEnum { /* メソッド */ public static cases(): array }
例えば、こういうものになります。
<?php enum Suit { case Hearts; case Diamonds; case Clubs; case Spades; } ?>
Pure Enum
値(value
)を持たないname
というキーだけを持つenum。
そのため、インターフェイスはUnitEnumになる。
Backed Enum
name
とvalue
というキーを持つenum。ドキュメントでは値に依存した列挙型
という表現もされている。
デフォルトではEnumはスカラー値の情報は持たないが、スカラー値をもたせることも可能。そうすることで、データベースなどに読み書きするときに役に立ちますね。
例えば、以下のようなEnumです。
<?php enum Suit: string { case Hearts = 'H'; case Diamonds = 'D'; case Clubs = 'C'; case Spades = 'S'; } ?>
Backed Enumのインターフェイスは異なり、以下のようになる。
interface BackedEnum extends UnitEnum { /* メソッド */ public static from(int|string $value): static public static tryFrom(int|string $value): ?static /* 継承したメソッド */ public static UnitEnum::cases(): array }
このように、スカラー型の値を列挙型にマップするfrom()
やスカラー型の値を列挙型にマップを試みるtryFrom()
が追加されています。
ここまでドキュメントを見るだけでも勉強になりますね。
LaravelのEnumのバリデーションを読み解く
次に、LaravelのEnumバリデーションの処理はどうなっているかを見ていきたいと思います。
Laravelのバージョンは8.78.1で見ていきます。
php artisan --version Laravel Framework 8.78.1
Illuminate\Validation\Rules\Enum
のpasses()
が重要になると思うので、そこを見ていきたいと思います。
/** * Determine if the validation rule passes. * * @param string $attribute * @param mixed $value * @return bool */ public function passes($attribute, $value) { if (is_null($value) || ! function_exists('enum_exists') || ! enum_exists($this->type) || ! method_exists($this->type, 'tryFrom')) { return false; } return ! is_null($this->type::tryFrom($value)); }
ここで、重要なことはif文のところです。
is_null($value)
- valueがnullかどうか
! function_exists('enum_exists')
- enum_exists()メソッドが存在するか
! enum_exists($this->type)
- 指定したenumが存在するか
! method_exists($this->type, 'tryFrom')
- tryFrom()が存在するか
上記のどれかに一致しない場合はバリデーションはコケます。
ここで注目したいのは最後のtryFrom()の存在チェックです。tryFrom()があるかどうかというのは、上の方でのEnumの理解のときに紹介したBacked Enumインターフェイスかどうかにほぼ同等です(現時点では)。すなわち、valueを持たないPure Enumはバリデーションのチェックができない(必ず失敗する)ということです。
値を持たないEnumなのでサーバにどんな値を渡せば良いかがわからないですね。当たり前です。
ということで、Enumのバリデーションを使用したい場合はBacked Enum
のみということになります(現時点で)。
実際にEnumのバリデーションを試す
では、実際にEnumとControllerを作成して試したいと思います。
作成したEnum
今回用意したEnumはPHPのドキュメントでも紹介されたトランプを用います。
Backed EnumとPure Enumを用意しました。
<?php namespace App\Enums; enum SuitBackedType: string { case Hearts = 'H'; case Diamonds = 'D'; case Clubs = 'C'; case Spades = 'S'; }
<?php namespace App\Enums; enum SuitPureType { case Hearts; case Diamonds; case Clubs; case Spades; }
EnumなのでサフィックスにTypeをつけてみました。その直前にBackedかEnumがわかるように名前をつけています。
Pure Enumに対してのバリデーション
雑ですが、バリデーションの処理しかしないControllerです。
<?php namespace App\Http\Controllers; use App\Enums\SuitPureType; use Illuminate\Http\Request; use Illuminate\Validation\Rules\Enum; class EnumPureController extends Controller { public function __invoke(Request $request) { $request->validate([ 'suit_type' => [new Enum(SuitPureType::class)], ]); return response([]); } }
Pure Enumなので、これに対してどんな値を渡してもバリデーション失敗になり、エラーが返ってきてしまいます。
Backed Enumに対してのバリデーション
次に、Backed Enumのバリデーションの処理しかしないControllerです。
<?php namespace App\Http\Controllers; use App\Enums\SuitBackedType; use Illuminate\Http\Request; use Illuminate\Validation\Rules\Enum; class EnumBackedController extends Controller { public function __invoke(Request $request) { $request->validate([ 'suit_type' => [new Enum(SuitBackedType::class)], ]); return response([]); } }
これは、suit_typeにEnumで定義している'H'
, 'D'
, 'C'
, 'S'
以外が渡されるとエラーになります。
動作確認で試したテストコードはこちらになります。
<?php namespace Tests\Feature; use Tests\TestCase; class EnumBackedTest extends TestCase { /** * @dataProvider data_enum * @param string|int $value * @param bool $expected */ public function test_enum(string|int $value, bool $expected) { $response = $this->post('/enum_backed', [ 'suit_type' => $value, ]); self::assertEquals($expected, $response->isSuccessful());; } public function data_enum(): array { return [ '正常系: HeartはEnumに含まれる' => [ 'value' => 'H', 'expected' => true, ], '正常系: ClubsはEnumに含まれる' => [ 'value' => 'C', 'expected' => true, ], '異常系: XはEnumに含まれない' => [ 'value' => 'X', 'expected' => false, ], '異常系: 0はEnumに含まれない' => [ 'value' => 0, 'expected' => false, ], ]; } }
このテストはすべてパスしているので、Enumに含まれるもののみ200で返ってきていることが確認できます。
最後に
Enumのバリデーションについて挙動を確認できました。また、個人的にEnumについてあまり理解できてなかったので、Enumについても少し勉強できたかなと思います。
まだEnum初心者なので、間違えているところがあるかもしれないので、間違いを見つけたらコメントで指摘していただけると嬉しいです。