はじめに
Laravelを触り始めて1年半ほどたったので、自分の健忘録を兼ねて、バリデーション周りで覚えておきたいことをまとめます。
公式ドキュメントや他の記事を参考にしつつ、独自に必要と思うものを追加しています。
Laravelのバージョンは6系です。
間違いなどコメント欄で指摘してもらえると助かります。
1. 基本的な書き方
Laravelのバリデーションは複数の書き方があるが、Validateメソッドを使うとシンプルに書ける。
また、細かな挙動をマニュアルに設定したい場合は、Validatorファサードを使うこともできる。
1-1. Validateメソッドを使ったController
ブログ記事投稿用のバリデーションを例にした簡単なサンプル。
<?php
namespace App\Http\Controllers;
use App\Models\Blog;
use Illuminate\Http\Request;
class BlogController extends Controller
{
// ... 省略 ...
/**
* ブログ記事投稿
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
// 簡単なバリデーション
$validated = $request->validate([
'title' => 'required|string|max:255',
'body' => 'required|string|max:1000',
]);
// バリデーション後の処理
$blog = new Blog();
$blog->fill($validated)->save();
コントローラーで受け取ったリクエストを$request->validate([ルールの配列])
とするだけで、バリデーションが行える。
1-2. ルールの書き方
バリデーションロジックは、デリミタを使う方法か配列での指定が存在している。
// デリミタを使用
'title' => 'required|string|max:255',
'body' => 'required|string|max:1000',
// 配列で指定
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string', 'max:1000'],
インプットされたデータが配列の場合、キーをxxx.yyy
の形で記述出来る。
'category.child' => 'string',
'category.parent' => 'string',
'tags.*' => 'string', // * でワイルドカードが使える
1-2. エラーを画面に表示する
バリデーションエラーがある場合、リクエストを送った画面にリダイレクトされ、セッションを使ってエラーが返される。
以下は、エラーをブログ投稿画面に表示する例。
<h1>ブログ記事投稿</h1>
<!-- エラーをまとめて表示する記述 -->
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- 送信フォーム部分 -->
<form action="/blogs" method="post">
@csrf
<p>タイトル:<input type="text" name="title"></p>
<p>本文:<textarea name="body"></textarea></p>
<input type="submit" value="送信">
</form>
個別に表示させることも下のような感じで簡単にできる。
<!-- 個別にエラーを表示する例 -->
<input type="text" name="title" class="@error('title') is-invalid @enderror">
@error('title')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
1-3. エラーをJSONで返す(API向け)
Validationメソッドでは内部でリクエストを判定して、APIへのリクエストはエラーをJSONで返すことができる。
※ コントローラーのバリデーション部分は、同じコードを使うことができる。
リクエストヘッダーにAccept:application/json
を指定すると、下のような形式でエラーが返される。
{
"message": "The given data was invalid.",
"errors": {
"title": [
"The title must be a string."
]
}
}
1-4. さまざまなバリデーションルール
Laravelにはたくさんのバリデーションルールが用意されていて、公式ドキュメントに一覧がある。
以下は、個人的によく使うもののリスト。
// 必須か否かなど
'key' => 'required', // 必須。nullや空文字・空の配列などもエラーとなる
'key' => 'nullable', // 不要。nullや空文字・空の配列などが許される
'key' => 'sometimes', // フィールドがあれば、後述のルールが適応される。なければ適応されない。
// 数値系
'key' => 'boolean', // 0か1
'key' => 'integer', // 整数。'1'や'-2'など
'key' => 'numeric', // 数値。'0.1'や'-0.333'など
'key' => 'min:0', // 0以上。
'key' => 'max:10', // 10以下。
// 文字列系
'key' => 'string', // 文字列。
'key' => 'string|min:3', // 3文字以上。
'key' => 'string|max:10', // 10文字以下。
'key' => 'digits:3', // 数字のみの文字列。この場合3桁。'001'など最初が0でもいい
'key' => 'email', // メールアドレスとして正しいか。
// 日付系
'key' => 'date', // 日時として認識できる文字列。'2000-01-01'や'2020-05-15T01:04:54+09:00'など。PHPのstrtotimeで認識できるフォーマットが許容される。
'key' => 'date_format:Y-m', // 日時のフォーマットを指定。この場合'2000-01'などの年月に限定している
'key' => 'after_or_equal:2000-01-01', // 指定した日時以降の日時
'key' => 'before:2000-01-01', // 指定した日時よりも前の日時
1-5. ユーザー登録で使うバリデーション例(unique
,confirmed
)
unique
,confirmed
は、下のような感じで使える。
<input type=text name=email>
<input type=text name=password>
<input type=text name=password_confirmation>
// 指定したテーブルで一意。この場合、Usersテーブルで同じ値がないことが要求される。
'email' => 'unique:users',
// 入力ミスを防ぐための確認用フィールド。この場合、passwordとpassword_confirmationの値が同じであること。
'password' => 'confirmed',
// xxxxx_confirmationのルールは記述しない。
'password_confirmation' => 'confirmed', // これはNG
// 実践的にはこんな感じ
'email' => 'required|unique:users|email|max:255',
'password' => 'required|confirmed|min:8|max:255|alpha_num',
1-6. Validatorファサード使ったバリデーション
$request->validate()
の書き方はシンプルでよいが、挙動が内部で決まっているので、エラーの返し方やリダイレクト先などをマニュアルに変更したいなどのときはValidator
ファサードを使うことができる。
<?php
namespace App\Http\Controllers;
use App\Models\Blog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator; // 追加
class BlogController extends Controller
{
// ... 省略 ...
/**
* ブログ記事投稿
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required|string|max:255',
'body' => 'required|string|max:1000',
]);
if ($validator->fails()) {
return redirect('blogs/create')
->withErrors($validator)
->withInput();
}
// バリデーション後の処理
$blog = new Blog();
$blog->fill($request->validated())->save();
こちらの記事により詳しい解説が載っています。
【laravel】Validatorによるバリデーション - Qiita
2. FormRequestを利用したバリデーション
FormRequestクラスを使うと、コントローラーでバリデーションを書かなくて済む(0行になる)。
下は、実際にFormRequestを使ったブログ記事投稿コントローラーの例。
/**
* ブログ記事投稿
*
* @param StoreBlogPost $request
* @return Response
*/
public function store(StoreBlogPost $request) // 独自に作成したStoreBlogPostを使っている
{
// バリデーション後の処理(すでにリクエストはバリデート済み)
$blog = new Blog();
$blog->fill($request->validated())->save();
// ... 省略 ...
2-1. FormRequestの作成
下のコマンドでFormRequestを継承したクラスがapp/Http/Requests
に作成される
php artisan make:request StoreBlogPost
2-2. FormRequestの編集
作成されたFormRequestのrules
メソッドにバリデーションルールを記述する。
authorize
メソッドには、認証ロジックを記述できる。
認証をここで行わない場合はtrueを返すか、メソッドごと削除すればよい。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreBlogPost extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true; // 認証をここで行わないときはtrueを返せばOK
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
// ここでルールを記述すればOK
return [
'title' => 'required|string|max:255',
'body' => 'required|string|max:1000',
];
}
}
2-3. FormRequestを使ってエラーをJSONで返す(API向け)
FormRequestの場合も、内部でリクエストを判定してAPIへのリクエストにはJSONでエラーを返すことができる。
リクエストのヘッダーにAccept:application/json
を指定するだけなので、FormRequestのコードは同じものが使える。
2-4. JSONのエラーメッセージをカスタマイズする(API向け)
エラーメッセージの内容やレスポンスフォーマットを変えたい場合は、下のように親クラスを作って継承することもできる。
STEP1. 親クラスを作る
php artisan make:request ApiRequest
STEP2. 親クラスを編集する
<?php
namespace App\Http\Requests;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
abstract class ApiRequest extends FormRequest // フォームリクエストを継承した抽象クラス
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
abstract public function rules();
/**
* Handle a failed validation attempt.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return void
*
* @throws \Illuminate\Http\Exceptions\HttpResponseException
*/
public function failedValidation(Validator $validator)
{
$res = response()->json([
'message' => 'インプットされたデータが不正です。', // messageの中身を日本語にする例
'errors' => $validator->errors(),
], 422);
throw new HttpResponseException($res);
}
}
STEP3. 子クラスを作る
php artisan make:request Api/StoreBlogPost
STEP4. 子クラスを編集する
<?php
namespace App\Http\Requests\Api;
use App\Http\Requests\ApiRequest; // 追加
class StoreBlogPost extends ApiRequest // 作成したApiRequestを継承
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules() // rulesの記述でだけでOK
{
return [
'title' => 'required|string|max:255',
'body' => 'required|string|max:1000',
];
}
}
バリデーションを新しく作るときは、毎回、このApiRequest
を継承したclassを作る。
これでrules()
メソッドを書くだけで、カスタマイズしたフォーマットでエラーを返せるようになる。
{
"message": "インプットされたデータが不正です。",
"errors": {
"title": [
"titleは、必ず指定してください。"
]
}
}
3. その他の必要なテクニック
個人的に、開発で必要になることが多いテクニック。
3-1. 編集リクエストのUniqueルール
登録済みのデータを編集するときに、登録時と同じようにUniqueルールを使うと実はNG。
というのも、編集対象のデータがすでにデータベースに存在しているため、一意でない判定になるから。
下のように、編集対象のデータをUniqueルールの対象外にすると、編集でUniqueが正しく動作するようになる。
// unique:[テーブル名],[カラム名],[除外するID]
'nickname' => 'unique:users,nickname,' . $this->user->id,
上は編集のrouteが/users/{user}
で、モデル結合ルートを使っている例なので、注意。
例えば、編集のrouteが/users/{id}
で、モデル結合ルートを使っていない場合は下。
'nickname' => 'unique:users,nickname,' . $this->id,
3-2. 編集リクエストのUniqueルール(複合ユニーク)
テーブルに複合ユニークが使われていると、より複雑になる。
salesテーブルで、yearカラムとmonthカラムの両方が同じ場合に、バリデーションエラーを出す例(複合ユニーク)。
// unique:[テーブル名],[カラム名],[除外する値],[除外するカラム名],[where制約カラム名],[where制約値]
'year' => 'unique:sales,year,' . $this->sale->id . ',id,month,' . $this->month,
詳しくは下の記事が参考になります。
【Laravel】複数カラムのユニーク制約のバリデーションを実装する方法 - Qiita
3-3. バリデーション前後に入力されたデータを変換する
バリデートする前に入力されたデータを変換したい場合は、FormRequestのprepareForValidation
をオーバーライドすればOK。
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'phone_number' => str_replace("-", "", $this->phone_number),
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
...
'phone_number' => 'digits_between:10,11',
...
];
}
バリデーション後にデータ加工したい場合はpassedValidation
をオーバーライドしましょう。
3-4. より複雑なバリデーションを追加する
Laravelで用意された標準ルールでは表現できない複雑なバリデーションを行いたい場合は、FormRequestのwithValidator
を使えば、独自のロジックを実装できる。
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'array' => 'array|min:3', // 配列の要素が3個以上存在する
];
}
/**
* Configure the validator instance.
*
* @param \Illuminate\Validation\Validator $validator
* @return void
*/
public function withValidator($validator) // ここでバリデーションのロジックを定義
{
$validator->after(function ($validator) {
$arr = $this->array;
for ($i = 2; $i < count($arr); $i++) {
$sum = $arr[$i - 1] + $arr[$i - 2];
if ($arr[$i] !== $sum) {
// バリデーションエラーを出している
$validator->errors()->add('array', 'Something is wrong with this array!');
break;
}
}
});
}
4. アプリ全体で使えるルールの追加・カスタマイズ
用意されたバリデーションルール以外に、アプリケーション全体で使えるルールを追加することもできる。
4-1. ルールを追加する(カタカナに限定するルール)
氏名のフリガナをカタカナで入力してもらいたいときなど、カタカナルールを作ってみる。
方法はいろいろあるが、ここでは公式ドキュメントにある方法(Validator::extend
)を使う。
STEP1. ルールをプロバイダに登録
アプリ全体で使えるように、app/Providers/AppServiceProvider.php
を下のように編集。
use Illuminate\Support\Facades\Validator; // 追加
// ... 省略 ...
public function boot()
{
// bootメソッドの中に登録したいルールとロジックを記述する
Validator::extend('katakana', function ($attribute, $value, $parameters, $validator) {
return preg_match('/^[ァ-ヾー]+$/u', $value); // 正規表現でカタカナのみマッチさせている
});
}
STEP2. エラーメッセージを登録
エラーメッセージをresources/lang/en/validation.php
に登録します。
※ ここでは英語。日本語にする方法は後述。
return [
// ... 省略 ...
'katakana' => 'The :attribute must be a katakana.', // return内のどこでもいい
STEP3. ルールを使う
いつものように使えます。
'myouzi_katakana' => 'katakana|max:255'
{
"message": "リクエストデータが不正です。",
"errors": {
"myouji_katakana": [
"The myouji katakana must be a katakana."
]
}
}
4-2. 半角英数ルール(alpha_xxx)をマルチバイト文字に対応させる
Laravelには半角英数系のルールとして、alpha
,alpha_num
,alpha_dash
があるが、日本語などマルチバイト文字に対応していない。
たとえば、alpha
ルールを指定したフィールドに日本語が入力されてリクエストされた場合、エラーが出ないので困ったことになってしまう。
これを機能させるには、下のようにValidator::resolver
で標準ルールを上書きすることで対応できる。
※ 上書きするのにValidator::extend
は使えない。
STEP1. 上書き用のクラスを作成する。
どこに作ってもいいが、今回はapp/Validators/CustomValidator.php
として作成する。
作成するクラスは、Illuminate\Validation\Validator
を継承する。
namespace App\Validators;
use Illuminate\Validation\Validator;
class CustomValidator extends Validator
{
/**
* alpah
*
* @param string $attribute
* @param string $value
* @return true
*/
public function validateAlpha($attribute, $value)
{
return preg_match("/^[a-z]+$/i", $value);
}
/**
* alpah_num
*
* @param string $attribute
* @param string $value
* @return true
*/
public function validateAlphaNum($attribute, $value)
{
return preg_match("/^[a-z0-9]+$/i", $value);
}
/**
* alpah_dash
*
* @param string $attribute
* @param string $value
* @return true
*/
public function validateAlphaDash($attribute, $value)
{
return preg_match("/^[a-z0-9_-]+$/i", $value);
}
}
STEP2. ルールをプロバイダに登録
use App\Validators\CustomValidator; // 追加
// ... 省略 ...
public function boot()
{
// 追加
Validator::resolver(function ($translator, $data, $rules, $messages, $attributes) {
return new CustomValidator($translator, $data, $rules, $messages, $attributes);
});
}
これで完了。
すでにalpha
,alpha_num
,alpha_dash
が、それぞれ日本語などのマルチバイト文字でエラーを出すようになっている。
※ 標準ルールには半角英数系以外にも地雷があるようなので注意!
参考: 使っちゃダメ!Laravelの地雷バリデーションと標準ルールを禁止(オーバーライド)する方法 - Qiita
5. エラーメッセージのカスタマイズ
個別に変更したり、全体を変更したりできる。
5-1. デフォルトのメッセージを個別にカスタマイズする
エラーメッセージをコントローラーやFormRequestクラスで個別に変更したい場合は、下のような方法が使える。
$messages = [
'required' => 'The :attribute field is required.',
];
$validator = Validator::make($input, $rules, $messages);
// 追加
public function messages()
{
return [
'title.required' => 'A title is required',
'body.required' => 'A message is required',
];
}
参考:FormRequest
5-2. バリデーションメッセージ全体を日本語化する
GithubにLaravel-Langという世界各国の言語に対応するためのパッケージがあり、日本語のファイルも用意されている(ここ)ので、これを利用する。
STEP1. 日本語用ファイルをダウンロード
日本語のバリデーション用ファイルをダウンロードして、/resources/lang/ja
フォルダを作成して設置する。
上で独自に作成したkatakana
ルールに対応したい場合は、下を追加することで対応できる。
'katakana' => ':attributeは、カタカナでなければなりません。', // 追加
STEP2. 設定を日本語に変更する
/config/app.php
を下のように編集。
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => 'ja', // ここを変更
これで完了。
エラーメッセージが日本語で表示される。
{
"message": "リクエストデータが不正です。",
"errors": {
"password": [
"passwordには、アルファベッドのみ使用できます。"
]
}
}
"passwordには、アルファベッドのみ使用できます。"
のpasswordなどを日本語化したい場合には、/resources/lang/ja/validation.php
内のattributes
に'日本語化したいキー名'=>'日本語訳'
を追加していく。
// ユーザー登録用の'attributes'設定例
'attributes' => [
'email' => 'メールアドレス',
'password' => 'パスワード',
'password_confirmation' => 'パスワード(確認用)',
'name' => 'お名前'
],
これで、下のようなエラーメッセージを表示することができる。
{
"message": "リクエストデータが不正です。",
"errors": {
"password": [
"パスワードには、アルファベッドのみ使用できます。"
]
}
}
おわりに
- バリデーション周りでだいたい困らない
- 体系的にまとまっている
を目指したら長くなりました。