この記事は ミライトデザイン Advent Calendar 2021 20日目の記事です。
Laravelでカスタムバリデーションを行うときに使うルールオブジェクトについて書いてみます。
TL;DR
ImplicitRule
使おう
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\ImplicitRule;
class PmRequired implements ImplicitRule
{
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return now()->hour < 12 || $value !== null;
}
/**
* @return string
*/
public function message()
{
return ':attributeは午前中は必須です。';
}
}
バージョン
- Laravel 8.76.2
Laravelのルールオブジェクト
Laravelには標準で様々なバリデーションチェックの種類がありますが、独自のバリデーションルールを作成したいことがしばしばあると思います。
公式サイトによると、
- ルールオブジェクトを使う
- クロージャを使う
- Validatorファサードのextendメソッドを使う
という方式が紹介されていますが、今回はルールオブジェクトについてのお話をします。
ルールオブジェクトの使い方
ルールオブジェクトの使い方としては、まずmake:rule
というArtisanコマンドを実行し、新規のルールオブジェクトを生成します。
今回は、奇数かどうかのチェックを行うためのOddNumber
というルールオブジェクトを作ってみます。
php artisan make:rule OddNumber
上記のコマンドを実行すると、app/rules
下にOddNumber.php
が作成されます。
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class OddNumber implements Rule
{
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
//
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return 'The validation error message.';
}
}
出来上がったRuleクラスを修正することで独自のバリデーションルールを作成することができます。
pass
メソッドでvalue
の値をチェックして、true / falseを返しましょう。
例えば奇数かどうかチェックする場合は下記のようにすればOKです。
(行数削減のために、phpdocとコンストラクタを除外しています。)
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class OddNumber implements Rule
{
public function passes($attribute, $value)
{
return ctype_digit($value) && $value % 2 === 1;
}
public function message()
{
return ':attributeは奇数ではありません。';
}
}
Ruleクラスを作成したら、ValidatorやFormRequestに設定することで、独自のバリデーションルールを使用できます。
use App\Rules\OddNumber;
$request->validate([
'name' => ['required', 'string', new OddNumber],
]);
マニュアルや他記事でも詳しく紹介されているので、詳しく知りたい方はぜひ調べてみてください。
ルールオブジェクトは空値をチェックできないらしい
ここからが本題です。
例えば独自ルールとして、午後に入力必須となるバリデーションルールを作りたいとします。
さきほどと同じようにRuleを作成してみます。
php artisan make:rule PmRequired
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Carbon;
class PmRequired implements Rule
{
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return Carbon::now()->hour < 12 || ($value !== null && $value !== '');
}
/**
* @return string
*/
public function message()
{
return ':attributeは午後は必須です。';
}
}
use App\Rules\PmRequired;
$request->validate([
'name' => [new PmRequired],
]);
しかし、実際にテストしてみるとわかるのですが、このルールは午後に空文字列を渡してもエラーになりません。
これは、LaravelのRuleの仕様となっており、公式ドキュメントにも明記されています。
By default, when an attribute being validated is not present or contains an empty string, normal validation rules, including custom rules, are not run.
デフォルトでは、バリデーションされる属性が存在しないか、空の文字列が含まれている場合、カスタムルールを含む通常のバリデーションルールは実行されません。
空文字列でチェック処理が動かないと、必須チェック系のカスタムルールが作成できません。
どうすればいいのでしょうか。
解決方法
実は解決方法は公式ドキュメントに記載されています。
Rule
の代わりにImplicitRule
を継承することで、属性が存在しなかったり空文字列の場合もルールオブジェクトによるチェックが行われるようになります。
作成方法は、artisanコマンドでRuleを作成するときに--implicit
オプションを付けることです。
php artisan make:rule PmRequired --implicit
先ほどと同じようにapp/Rules
下にファイルが作成されています。
先ほどとの違いは、継承元のクラスがIlluminate\Contracts\Validation\Rule
ではなくIlluminate\Contracts\Validation\ImplicitRule
になっていることです。
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\ImplicitRule;
class PmRequired implements ImplicitRule
{
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
//
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return 'The validation error message.';
}
}
このクラスを修正し、先ほどと同じように午後に必須チェックが行われるようにします。
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\ImplicitRule;
use Illuminate\Support\Carbon;
class PmRequired implements ImplicitRule
{
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return Carbon::now()->hour < 12 || ($value !== null && $value !== '');
}
/**
* @return string
*/
public function message()
{
return ':attributeは午後は必須です。';
}
}
これで無事に必須チェック系のルールオブジェクトが作成できました。