Laravel RuleIf なるカスタムルールを作ってみた(少し複雑なルール向け)
はじめに
Laravel の既存のルールクラスをパクリ参考にしつつ、RuleIf なるカスタムルールを作ってみました。大したものでは無いですが。(ソースは下です)
使い所としては、多少込み入った場合というか、複数のフィールド(他の項目)との絡みがある時などにいいです。もちろん、本当に込み入った時は、専用のカスタムルールを使って下さい。
Laravel にもコールバックを伴う sometimes() やAfterフックなんてのもありますが、
処理が、ちょっと離れた所に行ったりして、そこはちょっと微妙なんですよね。
それらを置き換える事ができたりします。
日本語ドキュメント:複雑な条件のバリデーション(sometimes)
日本語ドキュメント:フォームリクエストへのAfterフックを追加
Ver.8.43~から動きます。
使い方
基本の使い方は、new RuleIf(コールバック関数) とし、コールバック関数の中で、適用させたいルール名を返すのみです。(コールバック関数では、2つのパラメータを受け取れます)
下記の場合、年齢が30歳を超える場合、仕事欄が必須になります。
'job' => [
new RuleIf(function ($validator, $input) {
return $input->age > 30 ? 'required' : 'nullable';
}),
'max:4', // 追加のルール(おまけ)
],
ただ、上記の場合、あまり使う意味がありません。
三項演算子や RequiredIf を使っても書けます。(下記は、FormRequestを想定)
'job' => [
$this->input('age') > 30 ? 'required' : 'nullable',
'max:4', // 追加のルール
],
このルールの使い所として、コールバックの第1パラメータで、$validator インスタンスを受け取れますので、既に年齢欄でエラーが発生している場合は、仕事欄は必須としない(年齢チェックしない)という事もできます。
'job' => [
new RuleIf(function ($validator, $input) {
if ($validator->errors()->has('age')) {
return 'nullable';
}
return $input->age > 30 ? 'required' : 'nullable';
}),
'max:4', // 追加のルール
],
また、$validator インスタンス がある為、ある条件の時は、ガツッとエラーにするという事もできます。
'job' => [
new RuleIf(function ($validator, $input) {
if (true) {
$validator->errors()->add('job', 'XXXでお願いします。');
}
return 'nullable';
}),
'max:4', // 追加のルール
],
ルールは、|で繋げて複数返してもOKです。
return $input->age > 30 ? 'required|min:2' : 'nullable';
その他細かい話
第2パラメータの $input は、バリデーションの対象となるデータ(入力値)です。
プロパティ形式、配列形式、どちらでもOKです。未定義の場合でもエラーにはならずに、null を返します。
$input->age;
$input['age'];
細かい話をすれば、例えば、FormRequest の validationData() メソッドを使って、バリデーションの対象データを書き換えている時は、そちらのデータが取得されます。
RuleIf コード
以下、コードです。app/Rules に配置します。
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\ImplicitRule;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Fluent;
use InvalidArgumentException;
class RuleIf implements ImplicitRule, ValidatorAwareRule, DataAwareRule
{
/**
* The validator performing the validation.
*
* @var \Illuminate\Validation\Validator
*/
protected $validator;
/**
* The data under validation.
*
* @var array
*/
protected $data;
/**
* The failure messages, if any.
*
* @var array
*/
protected $messages = [];
/**
* Callable
*
* @var callable
*/
protected $callable;
/**
* Create a new required validation rule based on a callable.
*
* @param callable $callable
* @return void
*/
public function __construct($callable)
{
if (is_string($callable) || ! is_callable($callable)) {
throw new InvalidArgumentException('The argument must be a callable.
String type is disabled for security reasons.');
}
$this->callable = $callable;
}
/**
* Set the data under validation.
*
* @param array $data
* @return $this
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Set the current validator.
*
* @param \Illuminate\Validation\Validator $validator
* @return $this
*/
public function setValidator($validator)
{
$this->validator = $validator;
return $this;
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
$rule = call_user_func($this->callable, $this->validator, new Fluent($this->data));
$validator = Validator::make($this->data, [
$attribute => $rule,
]);
if ($validator->fails()) {
return $this->fail($validator->messages()->all());
}
return true;
}
/**
* Get the validation error message.
*
* @return array
*/
public function message()
{
return $this->messages;
}
/**
* Adds the given failures, and return false.
*
* @param array|string $messages
* @return bool
*/
protected function fail($messages)
{
$messages = collect(Arr::wrap($messages))->map(function ($message) {
return $this->validator->getTranslator()->get($message);
})->all();
$this->messages = array_merge($this->messages, $messages);
return false;
}
}
まとめ
もしバグとかあった際は、すいません。予めご了承下さい。(コメント下さい)
自分で作っておきながら、今の所使う予定は無し…。
Discussion