Laravelのバリデーション機能は多様なチェック機能がデフォルトで備わっておりとても便利です。
ですが、実際に使用していく中で入力チェックの内容に漏れがあったり抜け穴があったりと気を付けなければならない点がありましたので、バリデーションの使い方とともにまとめました。
今回まとめた内容の動作の確認はLaravel5.8にて行っております。
バリデーション機能の使い方
ここではLaravelバリデーションを使用するうえで作成するファイルや設定する箇所等についてまとめております。
バリデーション言語ファイルの設定
バリデーション言語ファイルはバリデーションエラー時のメッセージを定義するファイルになります。
バリデーションエラーメッセージは後で設定するフォームリクエストバリデーションでも設定が可能ですが、多言語化対応を行うとなった場合に対応しやすいようこのファイルで設定するのがベストです。
このファイルを作成しなくてもバリデーションチェックはできますので、使用しないのであれば読み飛ばしてください。
ファイル作成
resources\lang配下に言語ごとのディレクトリを作成し、その中にvalidation.phpを作成します。
日本語であればresources\lang\ja\validation.phpを作成します。
バリデーション言語ファイルはこちらの日本語翻訳をもとにアプリケーションに合わせて文言を設定するとよいです。
(バージョン6のものですが、5.8のものだとルールが不足しているのでこちらを提示しています)
このファイルを作成した状態でconfig/app.phpのlocaleの設定をjaに設定するとエラー時に表示されるようになります。
また、リクエストに応じて言語設定する場合はApp::setLocaleで設定した言語に応じたメッセージが表示されます。
'locale' => 'ja',
バリデーションメッセージ設定
バリデーション言語行の設定では特定のバリデーションルールでエラーになった場合に表示するメッセージを設定します。
以下はdigitsのメッセージ設定の例になります。
'digits' => ':attributeは:digits桁で指定してください。',
上記の「:attribute」には表示する際にチェックする項目の属性名が入ります。
また、「:digits」にはバリデーションチェックの指定時の引数値が入ります。
引数名はバリデーションルールによって異なりますので、resources\lang\en\validation.phpなどを参考に作成します。
形式によって変わるsize系のバリデーションルールは以下のように配列で数値(numeric)、ファイル(file)、文字列(string)、配列(array)の形式ごとにメッセージを指定します。
'between' => [
'numeric' => ':attributeは:min~:maxで指定してください。',
'file' => ':attributeは:min~:max kBのファイルで指定してください。',
'string' => ':attributeは:min~:max文字で指定してください。',
'array' => ':attributeは:min~:max個で指定してください。',
],
カスタムバリデーションメッセージ設定
カスタムバリデーション言語行の設定では特定の属性名の特定のバリデーションルールを使用した際のメッセージを指定します。
例えば、以下のように設定するとupdated_atでexistsのエラーとなった場合に指定したメッセージが表示されるようになります。
バリデーションメッセージ設定と同様に:attributeなどの引数も使用できます。
'custom' => [
'updated_at' => [
'exists' => 'データが更新されています。はじめから操作をやり直してください。',
],
],
カスタムバリデーション属性名設定
カスタムバリデーション属性名では:attributeに設定される名前を指定することができます。
例えば、以下のように設定すると、設定しない場合メッセージの:attributeにはemailと設定されるところ、メールアドレスと設定されるようになります。
'attributes' => [
'email'=>'メールアドレス',
],
カスタム値設定
バリデーションで指定する引数の値をメッセージに表示する際に別の値に置き換えることができます。
例えば、以下のように設定すると、:valuesに「クレジットカード」が設定されるようになり、card_noの項目が未入力の場合に「お支払いがクレジットカードの場合、カード番号も指定してください。」となります。
'required_if' => ':otherが:valueの場合、:attributeも指定してください。',
'attributes' => [
'card_no'=>'カード番号',
'payment_type'=>'お支払い',
],
'values' => [
'payment_type' => [
'1' => 'クレジットカード'
],
],
'card_no' => 'nullable|required_if:payment_type,1|numeric|digits_between:14,16',
バリデーションチェックの設定
バリデーションチェックを行う方法としてはRequestオブジェクトのvalidateメソッドを使う方法とフォームリクエストバリデーションを使用する方法があります。
フォームリクエストバリデーションの方が複雑なチェックを行うことができ、ソースコードもきれいにできるので、フォームリクエストバリデーションでチェックを行うことをおススメします。
そのため、Requestオブジェクトのvalidateメソッドを使う方法については割愛しますが、後に紹介するrulesメソッドとルールの設定方法は基本的に同じとなります。
フォームリクエストバリデーション
フォームリクエストバリデーションはFormRequestクラスを継承したクラスにバリデーションチェックルールを定義し、コントローラーのメソッドの引数のクラスに指定して使用します。
コントローラーの処理が呼ばれる前にバリデーションチェックが行われ、エラーがあった場合はメソッドの処理は行われず自動的に前のページヘリダイレクトするようになっています。
FormRequestファイル作成
作成は「php artisan make:request クラス名」のコマンドで作成します。
(この方法で作成しなくても問題はありません)
app/Http/Request配下に作成されますが、ディレクトリ分けを行う場合はディレクトリも指定します
以下のコマンドを実行するとAdminディレクトリ配下にProductRequest.phpを作成します。
php artisan make:request Admin/ProductRequest
authorizeメソッド
このメソッドではboolean型の値を返し、ユーザーがこのリクエストを行える権限を持っているかを判断するメソッドです。
falseが返るとリクエストが403 Forbiddenとなります。
この処理で権限のチェックを行う必要がない場合はtrueを返すかメソッド自体を実装しないようにしてください。
rulesメソッド
このメソッドではルールを指定した連想配列を返します。
基本は以下のようにチェックする項目名に対してルールごとにパイプ分け書き方になるかと思います。
return [
'id' => 'nullable|exists:products,id,deleted_at,NULL',
'tel' => 'required|numeric|digits_between:10,11',
];
regexでパイプを使用する場合やRuleクラスを使用する場合はパイプ分けを行わず、配列で指定します。
return [
'id' => [
'required',
'integer',
Rule::exists('t_goods', 'id')->where(function ($query) {
$query->where('type', $this->type);
$query->where('deleted_at', NULL);
})
],
'sort_by' => [
'required',
'string',
'regex:/\A(created_at|amount|price|type)\z/u',
],
];
配列の中身に対してバリデーションチェックを行う場合は下記のようにドット記法で対象を指定します。
下記は「images[id]」のように指定されたパラメータをチェックする方法になります。
return [
'images' => 'nullable|array'
'images.id' => 'required|image',
];
配列の全ての中身に対してバリデーションチェックを行う場合は下記のように「*」を指定します。
下記は「images[]」もしくは「images[0]」のように指定されたパラメータをチェックする方法になります。
return [
'images' => 'nullable|array'
'images.*' => 'required|image',
];
連想配列の中身に対してバリデーションチェックを行う場合は下記のように「*」の後に属性名を指定します。
下記は「images[][id]」もしくは「images[0][id]」のように指定されたパラメータをチェックする方法になります。
return [
'images' => 'nullable|array'
'images.*.id' => 'required|distinct|exists:t_images,id,deleted_at,NULL'
];
$this->属性名でリクエストの値を取得できるので、送られてきた値によってチェックする内容を変更することも可能です。
(この方法では配列中の値ごとに配列のチェックを変えるといったことはできません)
詳細は後ほど説明しますが、リクエストの種類が複数あるような場合は以下のように種類ごとにチェックルールの指定を変えることで他の種類で扱うデータをコントローラーで扱わなくて済みます。
$rules = [
'payment_type' => 'required|integer|between:1,5',
];
switch ($this->type) {
case 1:
$rules += array (
'card_no' => 'required|numeric|digits_between:14,16',
);
break;
case 2:
$rules += array (
'bank_code' => 'required|numeric|digits:4',
);
break;
}
return $rules;
messagesメソッド
このメソッドはメッセージのカスタマイズが必要な場合に実装します。
validation.phpファイルに定義されている場合でもこちらで定義したルールが優先で反映されます。
多言語対応などでvalidation.phpファイルでエラーメッセージを一元管理する場合は実装しないようにします。
このメソッドでは特定のエラーの際のメッセージを指定した配列を返します。
配列の添え字に指定した条件に当てはまる場合に値に設定したメッセージが表示されるようになります。
添え字には基本「ruleで指定した属性名.ルール名」になりますが、属性名のみ、ルール名のみも可能です。
return [
'digits_between' => ':attributeは数字:max桁以下にしてください。',
'phone.digits_between' => ':attributeは数字:min桁以上:max桁以下にしてください。',
'phone' => ':attributeが正しくありません。',
'images.*.id' => ':attributeが不正です。'
'images.*.id.required' => ':attributeは必須です。'
];
attributesメソッド
バリデーションメッセージの:attributeに設定される名前を指定することができます。
validation.phpファイルに定義されている場合でもこちらで定義したルールが優先で反映されます。
多言語対応などでvalidation.phpファイルで属性名を一元管理する場合は実装しないようにします。
このメソッドでは属性名に対するカスタム属性名を指定した配列を返します。
添え字の指定の仕方はrulesメソッドと同様です。
return [
'goods_cd' => '商品コード',
];
withValidatorメソッド
フォームリクエストでバリデーション実行前のバリデータに対して追加の操作を行う場合に実装します。
特定の入力の際にチェックを追加するsometimesメソッドや、バリデーションルールでできないチェックを行うためのafterフックを追加できます。
ただし、エラーメッセージを直接指定する必要がありますので、多言語化対応が必要な場合はApp::isLocaleの判定で言語ごとにメッセージを定義するか、messages.phpで定義したメッセージを指定することになります。
public function withValidator($validator) {
// from_dateが入力されている場合のみto_dateとの関係をチェック
$validator->sometimes('to_date', 'after_or_equal:from_date', function () {
return !empty($this->from_date);
});
$validator->after(function ($validator) {
// fieldにバリデーションエラーがない場合のみチェック
if (!$validator->errors()->has('field')) {
if (!isset($this->field)) {
$validator->errors()->add('field', 'フィールドが不正です。');
return;
}
}
});
}
failedValidationメソッド
通常のHTTPリクエストの場合はリダイレクトレスポンス(リクエスト時の画面に戻ってエラーメッセージ表示)が生成され、
AJAXリクエスト時はJSONレスポンスが返されますが、それ以外の形式で失敗時のレスポンスを指定したい場合はこのメソッドを実装します。
下記はエラーをJSON形式でレスポンスする例になります。
protected function failedValidation( Validator $validator ) {
$response['data'] = [];
$response['status'] = 'NG';
$response['summary'] = 'Failed validation.';
$response['errors'] = $validator->errors()->toArray();
throw new HttpResponseException(
response()->json( $response, 422 )
);
}
validationData(protected)メソッド
バリデーションのチェックを行う前に値に何かしらの処理を行いたい場合に実装します。
例えば、年月日が別々に送られてくる場合にこれらを結合した上で存在する日付かをバリデーションチェックで行いたいといったケースで使えます。
validationDataメソッドはバリデーションする対象となるデータを返すメソッドで、スーパークラスでは$this->all()をそのまま返しています。
これを継承してリクエストの内容を編集することができます。
このメソッドで編集した内容をコントローラ側で取得するには$request->validated()で取得します。
protected function validationData() {
$data = $this->all();
if (isset($data['phone'])) {
$data['phone'] = str_replace("-", "", $data['phone']);
}
return $data;
}
FormRequestコントローラ設定
コントローラのリクエストを受け取るメソッドのリクエスト引数のクラスにFormRequestのクラスを指定することでバリデーションが実行されます。
バリデーションに成功した場合のみコントローラのメソッドの処理が呼び出されます。
バリデーションに失敗した場合はリダイレクトレスポンスとなり、AJAXの場合はエラーJSONでエラー内容が返されます。
リクエストの値は$request->idの形で取得できますが、$requestには想定していないパラメータも格納されるので注意が必要です。
$request->validated()でデータを取得することでバリデーションチェックを行った項目のみを取得できるので、不要なデータを扱わずに済みます。
ですので、コントローラでは$requestから直接データを取得せず$request->validated()で取得したデータ配列から常に取得することを推奨します。
ただし、配列内の項目についてはチェックしていない項目でも配列の中に入ったままになるので注意が必要です。
public function store(ProductRequest $request) {
// バリデーション済みデータの取得
$validated = $request->validated();
$id = $validated['id'];
}
エラーメッセージ設定
バリデーションチェックでエラーとなった場合にそのメッセージをBladeテンプレートで表示する際の設定になります。
下記のようにanyメソッドでエラーがあるかを判断できます。
全エラーメッセージを一箇所で表示するのであればallメソッドで全エラーメッセージを配列で取得し表示することもできます。
@if ($errors->any())
<div class="alert alert-danger">入力内容に不備があります。エラー内容をご確認ください。</div>
@endif
下記は指定フィールドにhasメソッドでエラーがあるかを判定し、ある場合にエラーメッセージをfirstメソッドで1件表示する設定になります。
指定フィールドの全エラーメッセージを表示するのであればgetメソッドでエラーメッセージを配列で取得し表示することもできます。
配列形式のフィールドのエラーメッセージはバリデーションの指定時と同じように引数に「*」を使用すれば対象のフィールドのエラーメッセージを取得できます。
配列のエラーメッセージを個別に取得したい場合は「images.0.id」のように指定すれば取得できます。
@if ($errors->has('id'))
<small class="form-text text-danger">{{ $errors->first('id') }}</small>
@endif
バリデーション使用する際のノウハウ
ここからはバリデーションチェックの実装を行ってきた中で得たノウハウをまとめております。
バリデーション設定時に考慮すべきこと
Laravelのバリデーションで漏れなく入力チェックを行うために必ずルール設定でやるべきこととして次の3点挙げておきます。
送られてくるパラメータは必ずチェック項目として設定する
FormRequestでバリデーションチェックを行う場合は必ずすべてのリクエスト内容に対してルールを設定します。
前章で記述の通り、ルールを設定しないと$request->validated()で取得できないからです。
また、ルールを設定していない項目は$request->validated()で扱われないようにできるので、リクエストの内容をそのままEloquentでデータベースのテーブルを登録・更新するような場合は意図しない項目の変更を防ぐことができます。
(配列項目の場合は想定しない項目もそのまま入ってくるので別途対応が必要です)
必ずrequiredかnullableを最初に指定する
バリデーションチェックを行う項目は必ず以下のいずれかのチェックを最初に入れて下さい。
こちらの記事で確認されているように、チェックルールによっては想定外の動きをする場合があるので、指定を明確にします。
ルール | 動作 |
---|---|
required | データがなければエラー |
filled | フィールドが存在する場合にデータがなければエラー |
present | フィールドが存在しなければエラー |
nullable | nullでもエラーにならない |
基本的には必須の場合にrequired、そうでない場合はnullableを指定すれば問題ないです。
required_ifなどの条件必須ルールを使用する場合もnullableを必ず最初に指定してください。
'card_no' => 'nullable|required_if:payment_type,1|numeric|digits_between:14,16',
必ず型となるルールを指定する
そのリクエストの型を決めるルールを必ず指定してください。
数値であればintegerかnumelic、日付であればdateかdate_format、ファイルであればfileやimage、配列であればarray、それ以外の文字列であればstringを指定します。
フィールドが配列で送られてきた場合にエラーとなるようにするためです。
特に自由な入力を行うような項目の場合、下記のようにstringの設定を入れ忘れがちですが、この設定だとフィールド名がcomment[]と配列でデータが送られてきた場合にエラーとならないので必ず指定しましょう。
'comment' => 'required|max:200',
digitsなどもintegerなどを指定しなくても非数値でエラーとなりますが、配列の指定で送られるとエラーにならないので、numeric等を指定しましょう。
また、size、min、max、between、gt、gte、lt、lteは型によって動作が違うので注意してください。
バリデーションルール例
ここでは入力項目の形式や型に応じたバリデーションルールの設定例を挙げております。
ID系
更新などの際に使用するIDはexistsでテーブルへの存在チェックを行います。
Ruleクラスのexistsメソッドを使えば複雑な存在チェックが可能ですが、existsのルールは以下の形式で指定できるので大半はexistsメソッドを使わないで済む場合がほとんどだと思います。
exists:テーブル名,対象カラム名[,条件カラム,条件値,...]
以下のように基本削除されていないことも確認します。
(削除の場合は削除チェックせずあえてスルーしてもよいかと思います)
'id' => 'required|exists:t_reports,id,deleted_at,NULL',
区分値
ラジオボタン等の区分値は以下のように想定内の範囲であることを確認します。
'type' => 'required|integer|between:1,5',
booleanやacceptedは文字列、数値、boolean型と異なる型でチェックを通すので、これらを使う場合は受け入れられる値全てで問題がないことを確認しておく必要があります。
テキスト
基本的なテキスト入力の項目は以下のようになるかと思います。
stringを指定している場合のsize、min、max、betweenの指定は文字数となります。
'comment' => 'nullable|string|max:200',
alpha、alpha_dash、alpha_numはアルファベット以外の日本語などの文字も対象となる範囲が広いチェックになっているので使用する際は要注意です。
英数字等のチェックする場合は正規表現を使うようにしましょう。
数値入力
数値判定系のものとしては以下のものがあります
ルール | 動作 |
---|---|
integer | 整数(小数点不可、Long値の範囲内) |
numeric | 数値(is_numeric関数での判定) |
digits:桁数 | 指定した桁数の数値(小数点、マイナス不可) |
digits_between:最少桁,最大桁 | 最少桁~最大桁の範囲の数値(小数点、マイナス不可) |
integerやnumericを指定した場合はsize、min、max、betweenが値の範囲指定になります。
人数などの整数値の場合は以下のような指定になるかと思います。
betweenはminとmax、場合によってはdigits_betweenでも問題ないです。
'customer_num' => 'nullable|integer|between:0,9999',
ハイフンなしの電話番号のような場合はdigits_betweenのチェックを使います。
'phone' =>'required|numeric|digits_between:10,11',
小数点以下の桁数等を制御する場合は下記のように正規表現での制御で対応します。
'distance' => 'nullable|numeric|regex:/\A\d{1,4}(\.\d{1,3})?\z/',
数値の大小を比較する場合はgt、gte、lt、lteを使いますが、比較する対象の値が空の場合にExceptionが発生するので項目が必須でない場合はsometimesメソッドでチェックします。
$validator->sometimes('to_customer_num', 'gte:from_customer_num', function () {
return !empty($this->from_customer_num);
});
日付
日付のチェックはdateとdate_formatがあるが、どちらかしか使えないので注意してください。
期間指定の場合はbefore、before_or_equal、after、after_or_equalで日付の比較を行うようにしましょう。
'end_date' => 'nullable|date_format:Y/m/d h:i:s|after:start_date'
ファイル
ファイル形式の場合はfileを指定します。
画像の場合はimageだけを指定すれば問題はないです。
これらを指定した場合はsize、min、max、betweenがファイルサイズ(kb)のチェックになります。
ファイルの形式を指定したい場合はmimetypesかmimesでmimetypeのチェックを行うことができます。
基本どちらかを指定すれば問題ないかと思います。
あとは、無制限に大容量のファイルをアップロードできないよう必ずファイルサイズの最大値を指定しましょう。
(サーバーソフトやPHPのアップロード容量上限にもよりますが)
'image' => 'nullable|image|mimes:jpeg,bmp,png|max:1024'
配列
リクエストが配列の場合はarrayでチェックします。
arrayを指定した場合はsize、min、max、betweenが配列の数になります。
配列の中身は必要でない場合を除きrequiredを指定し、重複が想定されない値についてはdistinctを指定しましょう。
'image' => 'nullable|array',
'image.*.id' => 'required|integer|digits_between:1,10|exists:t_images,id',
配列の中のデータの条件に応じてチェックするようなことはできないので、そのようなチェックが必要な場合はafterフックで対応してください。
正規表現
Laravelで用意されているルールでチェックできないような内容は正規表現でのチェックを検討をしましょう。
正規表現でチェックする場合は必ず文字列の開始終端の\A\zで指定します。
日本語等の2バイト文字を使用する場合はパターン修飾子uを指定します。
'reservation_number' => 'required|string|regex:/\A[a-zA-Z0-9]+\z/|size:8',
ペジネーション
Laravelの標準のペジネーションはオフセット値(表示件数×ページ)がLong最大値を超えるとエラーになるのでそうならないようバリデーションチェックを指定しておくと良いかと思います。
Intの上限値程度を上限で指定しておけば問題はないかと思います。
'page' => 'nullable|integer|between:1,9999999'
排他制御(楽観ロック)
updated_atで楽観的排他制御を行う方法になります。
ただ、この時点でトランザクションは行われていないので、更新時のトランザクション中に再度チェックを行ったほうが良いかと思います。
'updated_at' => [
'required',
Rule::exists('goods', 'updated_at')->where(function ($query) {
$query->where('id', $this->report_id);
$query->where('deleted_at', NULL);
})],
その他
バリデーションチェックの処理に関連するノウハウになります。
バリデーションエラー時に特定の項目をフラッシュしない
通常バリデーションエラーが発生した場合はリクエストされた内容がフラッシュ(一時保存)され、入力した内容を入れた状態で元の画面に戻れるようになっています。
パスワードのようにバリデーションエラーが発生した際にその入力内容を未入力状態にするには
App\Exceptions\Handlerクラスに$dontFlashが定義されており、この配列に指定されているフィールド名が指定された場合はフラッシュされません。
protected $dontFlash = [
'password',
'password_confirmation',
];
リダイレクト先がおかしくなる
バリデーションでエラーになった際、リクエスト時の画面に戻ってエラーメッセージ表示とならない現象が発生することがあります。
過去に遭遇したのは、axiosのGETメソッドでAPIにリクエストを送信した後にフォームリクエストでPOSTメソッドでリクエストした内容がバリデーションエラーとなると、axiosで送った処理にリダイレクトされてしまうというものでした。
原因は、axiosのAPIへのリクエストがLaravelで直前の画面だと判断されてしまっていたことでした。
エラー時のリダイレクト先はIlluminate\Session\Middleware\StartSessionのstoreCurrentUrlの処理で記憶しており、下記の条件文に当てはまる場合に保存されます。
if ($request->method() === 'GET' &&
$request->route() &&
! $request->ajax() &&
! $request->prefetch())
この時はaxiosのAPIへのリクエストをPOSTメソッドでの送信に変更するという方法で対処しました。
参考
-
5.8ドキュメント
https://readouble.com/laravel/5.8/ja/validation.html -
全54種類!Laravel 5.7のバリデーション実例
https://blog.capilano-fw.com/?p=341 -
Laravelのバリデーションで指定できる内容をざっくりまとめ直しました。
https://qiita.com/fagai/items/9904409d3703ef6f79a2 -
Laravelの標準バリデーションのわかりにくい挙動を実験して確かめたまとめ
https://qiita.com/kd9951/items/abd063828e33a61c8c58 -
【Laravel5】FormRequestのバリデーション結果をJSON APIで返す
https://qiita.com/junsan50/items/ec7f810decd3b82d3d76 -
Laravel5でのValidationMessageを日本語化してみた
https://qiita.com/Rock22/items/1db96c801d78ebafb7cc -
[Laravel] バリデーションデータに前処理したい
https://qiita.com/toshikish/items/f38b691adbebd7ba7720 -
Laravel バリデーションが変な場所へリダイレクトしてしまう
https://blog.capilano-fw.com/?p=18