概要
- Laravelのデフォルトのバリデーションでは対応できない場合に、カスタムのバリデーションを作成する方法について
環境
- Laravel Framework 10.34.2
- PHP 8.0
非推奨ver
php artisan make:rule AllowedFields
これを行うことで、AllowedFields.phpファイルを作成する。
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class AllowedFields implements Rule
{
protected $allowedFields;
public function __construct(array $allowedFields)
{
$this->allowedFields = $allowedFields;
}
// 既定のpassesをオーバーラップすることで、独自のチェックを作成する。
public function passes($attribute, $value)
{
// リクエストのキーが許可されたフィールドの中にあるかチェック
return in_array($attribute, $this->allowedFields);
}
// 既定のmessageを、、(以下同)
public function message()
{
// :attributeは検証したフィールドに置き換えられる。
return 'The :attribute field is not allowed.';
}
}
ファイルにこちらを記述する。
use App\Rules\AllowedFields;
// 省略
public function rules(): array
{
$allowedFields = [
'paymentName',
];
return [
'paymentName' => 'required|string|min:1|max:50',
'*' => [new AllowedFields($allowedFields)]
];
}
ルールに記述をすることで、許可されたフィールド以外の場合はエラーレスポンスを返すように変更する。
public function passes($attribute, $value)
{
// リクエストのキーが許可されたフィールドの中にあるかチェック
return in_array($attribute, $this->allowedFields);
}
今回はこの部分で、配列で指定した要素以外が存在する場合にエラーになるようにしている。
ただしこのままやると、実際に動作はするものの非推奨部分が存在してしまう。
use Illuminate\Contracts\Validation\Rule;
class AllowedFields implements Rule
'Illuminate\Contracts\Validation\Rule' is deprecated.intelephense(P1007) このような具合で非推奨が表示される。
参考
推奨ver
自分の今後の参考ように、公式の例をそのまま引用する。
一次情報が欲しい場合は下記リンクを踏んでください。
Laravel provides a variety of helpful validation rules; however, you may wish to specify some of your own. One method of registering custom validation rules is using rule objects. To generate a new rule object, you may use the make:rule Artisan command. Let's use this command to generate a rule that verifies a string is uppercase. Laravel will place the new rule in the app/Rules directory. If this directory does not exist, Laravel will create it when you execute the Artisan command to create your rule:
Laravelには役立つ様々なバリデーションルールが用意されていますが、独自のルールを指定したい場合もあるでしょう。カスタム検証ルールを登録する方法の1つに、ルールオブジェクトを使用する方法があります。新しいルール・オブジェクトを生成するには、make:rule Artisanコマンドを使用します。このコマンドを使って、文字列が大文字であることを検証するルールを生成してみましょう。Laravelは新しいルールをapp/Rulesディレクトリに配置します。このディレクトリが存在しない場合、Artisanコマンドを実行してルールを作成すると、Laravelが作成します:
php artisan make:rule 好きなファイル名
Once the rule has been created, we are ready to define its behavior. A rule object contains a single method: validate. This method receives the attribute name, its value, and a callback that should be invoked on failure with the validation error message:
ルールの作成が完了したら、次はその振る舞いを定義する。ルール・オブジェクトには、validateというメソッドがひとつあります。このメソッドは、属性名とその値、そして検証エラーメッセージとともに失敗時に呼び出されるコールバックを受け取ります:
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class Uppercase implements ValidationRule
{
/**
* Run the validation rule.
*/
// $attribute 属性名、$value 値、$fail 失敗時のメッセージ
public function validate(string $attribute, mixed $value, Closure $fail): void
{
// このコードは、値を大文字にした結果と一致するかどうかを検証している
// 一致しない場合は、:attribute(属性名)が大文字であるというエラーメッセージをセットで返す。
if (strtoupper($value) !== $value) {
$fail('The :attribute must be uppercase.');
}
}
}
以前のコンストラクターの定義などが必要でなくなった分、大分直感的に使えるようになったと思う。
しかし、一方でChatGPTのサポートに関してはなくなってしまったので自力作成になる分、若干面倒くさくなったのも事実としてありそうだと感じた。
少なくとも一発でカスタムバリデーションに関するver10以上の書き方は出ないため、調整が必要である、、
Once the rule has been defined, you may attach it to a validator by passing an instance of the rule object with your other validation rules:
ルールを定義したら、他のバリデーションルールと一緒にルールオブジェクトのインスタンスを渡すことで、そのルールをバリデータにアタッチすることができます:
要は、インスタンス化さえしてしまえば普段利用しているstringなどと同じように、利用が可能である。
use App\Rules\Uppercase;
$request->validate([
'name' => ['required', 'string', new Uppercase],
]);
return [
'paymentName' => 'required|string|min:1|max:50',
'uniqueText' => 'required|string|min:1|max:255',
'regexs' => 'required|array',
'regexs.spentAmount' => 'required|string|min:1',
'regexs.point' => 'nullable|string',
'*' => [new AllowedFields($allowedFields)]
];
これに準じて、先ほどのファイルを修正すると、、
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class AllowedFields implements ValidationRule
{
protected $allowedFields;
// setData メソッドではうまくいかなかったため、継続して利用
public function __construct(array $allowedFields)
{
$this->allowedFields = $allowedFields;
}
/**
* Run the validation rule.
*
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (!in_array($attribute, $this->allowedFields)) {
$fail("The :attribute field is not allowed. Allowed fields");
}
}
}
さっくりこのような形になりました!!
コンストラクターの処理を後述するsetDataメソッドを利用して行おうとしたのですが、あらかじめDataをセットしてもリクエストを受け付けた段階でリクエストボディに上書きされてしまう問題をうまく解決できなかったため、コンストラクターを使用しています。
また、継続してコンストラクターを使用していることもあり、利用方法も非推奨のバージョンと何ら変わりはありません。
おまけ1 エラーメッセージの翻訳
Translating Validation Messages
Instead of providing a literal error message to the $fail closure, you may also provide a translation string key and instruct Laravel to translate the error message:
バリデーションメッセージの翻訳
リテラルのエラーメッセージを$failクロージャーに提供する代わりに、翻訳文字列キーを提供し、エラーメッセージを翻訳するようにLaravelに指示することもできます:
どうやら、Laravelはエラーメッセージの翻訳もサポートしているようだ。
こちらに必要な設定が記載されているが、必要と感じた場合は調整するとよいかもしれない。
if (strtoupper($value) !== $value) {
$fail('validation.uppercase')->translate();
}
If necessary, you may provide placeholder replacements and the preferred language as the first and second arguments to the translate method:
必要であれば、translateメソッドの第1引数および第2引数に、プレースホルダーの置換と希望する言語を指定することができる:
translateメソッドの引数を以下のように調整することで、直接言語変換も可能なようである。
$fail('validation.location')->translate([
'value' => $this->value,
], 'fr')
おまけ2 setData 使い方等情報募集
If your custom validation rule class needs to access all of the other data undergoing validation, your rule class may implement the Illuminate\Contracts\Validation\DataAwareRule interface. This interface requires your class to define a setData method. This method will automatically be invoked by Laravel (before validation proceeds) with all of the data under validation:
カスタムのバリデーション・ルール・クラスが、バリデーション中の他のデータすべてにアクセスする必要がある場合、ルール・クラスはIluminateContractioneCustaridiondataAwareRuleインタフェースを実装することができる。このインタフェースは、クラスがsetDataメソッドを定義することを要求する。このメソッドは、(バリデーションが進む前に)Laravelによって、バリデーション中の全てのデータで自動的に呼び出されます:
正しいか怪しいが、おそらく事前に検証用のデータを設定するのに利用する??っぽい、、
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\ValidationRule;
class Uppercase implements DataAwareRule, ValidationRule
{
/**
* All of the data under validation.
*
* @var array<string, mixed>
*/
protected $data = [];
// ...
/**
* Set the data under validation.
*
* @param array<string, mixed> $data
*/
public function setData(array $data): static
{
$this->data = $data;
return $this;
}
}
Or, if your validation rule requires access to the validator instance performing the validation, you may implement the ValidatorAwareRule interface:
あるいは、バリデーションルールがバリデータのインスタンスへのアクセスを必要とする場合は、 ValidatorAwareRule インターフェイスを実装します:
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Validation\Validator;
class Uppercase implements ValidationRule, ValidatorAwareRule
{
/**
* The validator instance.
*
* @var \Illuminate\Validation\Validator
*/
protected $validator;
// ...
/**
* Set the current validator.
*/
public function setValidator(Validator $validator): static
{
$this->validator = $validator;
return $this;
}
}
おまけ3 アプリケーション全体で1度だけ使用するバリデーションの場合
正直に言うと、個人的には今後の要件変更も考えて、あえてアプリケーション全体で1度しか使えないバリデーションを実装することは、あまり推奨のものではないと考えるがLaravelが機能として持っていることを知るのは良いことだと感じるため、ほかと変わらずに記載する。
If you only need the functionality of a custom rule once throughout your application, you may use a closure instead of a rule object. The closure receives the attribute's name, the attribute's value, and a $fail callback that should be called if validation fails:
カスタムルールの機能がアプリケーション全体で一度だけ必要な場合は、 ルールオブジェクトの代わりにクロージャを使用します。クロージャは、属性の名前と値、そしてバリデーションに失敗した場合にコールされる $fail コールバックを受け取ります:
use Illuminate\Support\Facades\Validator;
use Closure;
$validator = Validator::make($request->all(), [
'title' => [
'required',
'max:255',
function (string $attribute, mixed $value, Closure $fail) {
if ($value === 'foo') {
$fail("The {$attribute} is invalid.");
}
},
],
]);
記法も含めて、先述したのと同じであるため特にまとめないが、今回の場合は一度だけの実行であるため、クラスではなく、即時関数を利用しているのが特徴であろう。
おまけ4 暗黙のルール(空文字は検証しない)を検証させる方法
By default, when an attribute being validated is not present or contains an empty string, normal validation rules, including custom rules, are not run. For example, the unique rule will not be run against an empty string:
デフォルトでは、検証対象の属性が存在しないか空文字列を含む場合、カスタムルールを含む通常の検証ルールは実行されません。例えば、ユニークルールは空文字列に対しては実行されません:
例にあるように、空の場合はルールが指定されていても検証が実行されないため、自動的にtrueになってしまう。
use Illuminate\Support\Facades\Validator;
$rules = ['name' => 'unique:users,name'];
$input = ['name' => ''];
Validator::make($input, $rules)->passes(); // true
For a custom rule to run even when an attribute is empty, the rule must imply that the attribute is required. To quickly generate a new implicit rule object, you may use the make:rule Artisan command with the --implicit option:
属性が空の場合でもカスタムルールを実行するには、ルールがその属性を必須であることを暗示する必要があります。新しい暗黙のルールオブジェクトを素早く生成するには、make:rule Artisanコマンドに--implicitオプションを指定します:
php artisan make:rule Uppercase --implicit
まとめ
カスタムバリデーションのLaravel 10の方法についてまとめた。おまけでも扱ったsetDataメソッドの用法がいまいちわからないものの、記法としてはよりシンプルになり使いやすくなったと感じる。
ChatGPTが対応していないため、適当に書かせることはできないが、passesの中身を変えれば基本的には問題がなさそうでもあるため、工夫次第では問題も少ないだろう。
不明点も多少あるので、今後も調べつつ利用していこうと思う。