Help us understand the problem. What is going on with this article?

Laravelのバリデーションにはフォームリクエストを使おう

More than 1 year has passed since last update.

追記(2018-02-09)

Laravel5.2の時代に書いたものです。
時間あるときに5.5にアップデートします。。。

フォームリクエストとは

  • フォームを含む各リクエストに対して、それぞれ固有のバリデーションを設定できる。
  • バリデーションエラー時には、入力値とエラー情報を付与して前のページに自動的にリダイレクトする。
  • バリデーションをパスした時に初めてコントローラー内の処理に移る。

詳しくは公式ドキュメントのフォームリクエストの項を参照。
バリデーション 5.2 Laravel

メリット

  • バリデーションルールやエラー時の処理をコントローラーから完全に分離できる。
  • 使いまわしが効く。
  • 可読性が高い。
  • カスタマイズしやすい。

チュートリアル

App\Http\Requests配下にRequestを拡張したクラスを作る。

手動で作ってもいいが、コマンドの方が楽。
php artisan make:request CreateUserRequest

CreateUserRequest.php
namespace App\Http\Requests;
use App\Http\Requests\Request;

/**
 * 新規ユーザ登録のバリデーションを行うクラス
 */
class CreateUserRequest extends Request
{
    /**
     * 認証関係の判定を行う場合はここに処理を記述する。
     * 特にない場合は常にtrueを返しておく。
     */ 
    public function authorize() {
        return true;
    }

        /**
     * バリデーションルールを記述
     */
    public function rules() {
        return [
            'username' => 'required|max:20',
            'email' => 'required|email|max:100',
            'password' => 'required|min:6|confirmed',
        ];
    }
}

'フィールド名' => 'ルール'の形式で記述する。
'email' => 'required|email|max:100'
これでname='email'が指定されている入力フィールドに「必須」「メール形式」「最大100文字」の制限がかかる。

コントローラーの引数に拡張クラスをあてる

UserController.php
public function postCreate(CreateUserRequest $req) {
    /**
     * 拡張クラスに書いたルールでリクエストが自動的に検証される
      * バリデーションをパスするとこの後の処理が実行される 
      */ 
    $this->userService->createUser($req->all());
    return view('...');
}

エラーメッセージを表示する

バリデーションエラーが起きた場合、エラーメッセージと入力値がフラッシュデータとして保存され、入力を行ったページにリダイレクトする以下のようなレスポンスが生成される。

return redirect()->back()->withErrors($validator)->withInput() 

(※AJAXリクエストの場合はJSONを含んだ422ステータスコードのHTTPレスポンスが返される)

入力された値は{{ old('username') }}
エラーメッセージは{{ $errors->first('username') }}
のように書けば取得できる。

※注意点としてルートに 'web' ミドルウェアを適用しないと、$errorsが使えない。これがLaravelマジックとも言われる所以で、エラーメッセージをフラッシュデータとして$errorsに保存してビューに引き渡す処理は
Illuminate\View\Middleware\ShareErrorsFromSessionミドルウェアで行われている。そのため、フォームリクエストの恩恵を受けさせるためにはルーティングにwebミドルウェアを指定する必要がある。

routes.php
Route::group(['middleware' => ['web']], function () {
  Route::post('/user/create', 'UserController@postCreate');
});
  • すべてのエラーの取得 {{ $errors->all() }}
  • 特定のエラーの取得 {{ $errors->first('username') }} (※配列形式で結果が返ってくるため、first()で最初のものを取得している。)
  • エラーの存在チェック {{ $errors->has('username') }}
  • エラー数の取得{{ count($errors) }}

エラーメッセージの表示例

user_create.blade.php
/** エラーをすべて表示 */
@if(count($errors) > 0)
    <ul>
        @foreach ($errors->all() as $error)
            <li>{{ $error }}</li>
        @endforeach
    </ul>
@endif

/** 特定のエラーの表示 */
@if($errors->has('username'))
    <div class="error">
        <p>{{ $errors->first('username') }}</p>
    </div>
@endif

※エラーメッセージをカスタマイズする場合はmessages()をオーバーライドする。

public function messages() {
    return [
        'username.required' => 'ユーザー名を入力してください。',
        'email.email'  => 'メールアドレスとして正しい形式ではありません。',
    ];
}

authorize()について

Requestの拡張クラスにあったauthorizeというメソッド。これは権限に関する判定を行うもの。使わなければtrueを返しておく。falseを返した場合は403ステータスコードのレスポンスが返される。

(例)管理者権限を持っているユーザにのみ許可

CreateUserRequest.php
public function authorize() {
    if(!session()->has('isAdmin')) {
        return false;
    }
    return true;
}

チュートリアル終わり。

いろんなルール

複数入力の判定

ワイルドカード*で指定できる。

'username.*' => 'required|max:12' 

username[0], username[1], ...に適用される。

また、以下のような入力値があった場合

user = [
    [0] = [
        'username' => 'foo',
        'email' => 'foo',
        'password' => 'foo'
    ],
    [1] = [
        'username' => 'bar',
        'email' => 'bar',
        'password' => 'bar'
    ],
    [2] = [
        'username' => 'fizz',
        'email' => 'fizz',
        'password' => 'fizz'
    ]
]   

これで対応できる。

'user.*.username' => 'required|max:12' 
'user.*.email' => 'required|email|max:100' 
'user.*.password' => 'required|min:8|confirmed' 

フィールド存在時のみバリデーションをかけたい

sometimesを指定すると、その名前の入力値が存在しない時はバリデーションが無視される。ワイルドカードとの合わせ技で割と役立つ。

'article.*.hashtag' => 'sometimes|min:3|max:10' 

(例)ハッシュタグが入力されている記事のみ、ハッシュタグの文字数制限をかけるバリデーション。sometimesがないと、ハッシュタグが何も入力されていない記事で「最低3文字」の制限を満たせずエラーが起きてしまう。

一意性(unique)

unique:テーブル名、カラム名で指定。
カラム名は省略でき、その場合はフィールド名と同じカラムが探索される。

'email' => 'unique:users,email_address'

パスワードなど確認用のフィールド(confirmed)

メールアドレスとかパスワードとか、確認用として入力した値が一致しているかを検証する。name="xxx"のフィールドとname="xxx_confirmation"のフィールドが同一の値か検証される。

'email' => 'confirmed'
<!-- 2つの入力値が同一でないとバリデーションエラー -->
メールアドレス<input type="text" name="email">
メールアドレス(確認用)<input type="text" name="email_confirmation">

セレクトボックスが未選択かどうかを検証

not_inを使います。これ、地味に役立ちます。例えば以下の、職業を選択させるフィールドに必須のバリデーションをかけたい場合。

<select name="job">
   <option value="0">選択してください</option>
    <option value="1">学生</option>
    <option value="2">会社員</option>
    <option value="3">主婦</option>
</select>
'job' => 'required'

にしてしまうと、「選択してください」が選択された状態でもエラーにならずに処理が通ってしまう。「選択してください」以外を選ばせたい場合は以下のようにする。

'job' => 'not_in: 0'

「0以外を入力してくれよ」というバリデーション。「0」の時はエラーが起こる。エラーメッセージを分かりやすくするために、もう一手間かけてみる。カスタムリクエストクラスでmessages()メソッドをオーバーライドする。

public function messages() {
    return [
        'job.not_in' => '職業が選択されていません。',
    ]
}

jobフィールドでnot_inのエラーが発生した場合、「職業が選択されていません。」というメッセージが返される。全部のメッセージを記述する必要はなく、カスタマイズしたいメッセージだけオーバーライドすればOK。

ファイルアップロード

拡張子:mimes:png,jpg,gif,pdfなどなど複数指定可。
ファイルサイズ:max:3000 単位はキロバイト。

bail

bailルールを指定すると、バリデーションエラーが起きた時点で残りの判定が行われなくなる。

'username' => 'bail|required|min:4|max:12'

もっと知りたい方は

公式のドキュメントもいいですが、
Laravel5.2 バリデーション

ルールについては以下の記事が詳しくまとめられていてとてもタメになります。
Laravelのバリデーションで指定できる内容をざっくりまとめ直しました。

次回はエラーメッセージのカスタマイズについて詳しく書こうかと。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした