福岡拠点の野田です。
秋雨や台風が気になりますが、過ごしやすい日々が増えてきたと思います。
今日は、Laravel の値検査の仕組みについて取り上げようと思います。
Laravel には値検査を行う設計の方向性として大きく3つの選択肢があります。
・FormRequest を継承してリクエストフォームオブジェクトを作る
・ValidatesRequestsトレイトを使う
・Validator ファサードを使う
FormRequestを作る場合
Controller に対して Illuminate\Foundation\Http\FormRequest 型のメソッドインジェクションを指定しておくとそのメソッドに対応するURLが呼び出された際に値検査がかかるという仕組みになります。バリデーションの結果が false の場合、リクエスト値をセッションに保存して、リクエストを投げた前のページに戻ります。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |     /**      * 入力画面を表示する(Request型は、バリデーションはかからない)      * @param Request $request      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View      */     public function edit(Request $request) {         return $this->view('sample.input', $data);     }     /**      * 入力確認画面を表示する(FormRequestを継承したクラスは入力検査がかかる)      * @param MailUpdateRequest $request      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View      */      public function editConfirm(MailUpdateRequest $request) {         return $this->view('sample.check', $data);      }     /**      * 完了画面を表示する(FormRequestを継承したクラスは入力検査がかかる)      * @param MailUpdateRequest $request      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View      */      public function update(MailUpdateRequest $request) {         // DB更新処理など         return $this->view('sample.done', $data);      } | 
とても便利な仕組みですが、都度、FormRequestを実装することになります。毎回似たようなコードを書くのは煩わしいですよね。そこで以下のような基底クラスを作成して継承して使いまわすことにします。この基底クラスは、自分のクラス名からバリデーションの設定を読み込んで動作する仕組みで動いています。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | <?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use Log; /**  * フォームリクエストのベースクラス  * ※継承して使います。  * Class BaseRequest  * @package App\Http\Requests  */ class BaseRequest extends FormRequest {     protected $className = null;     /**      * クラス名を取得保持      * @return null|string      */     public function getClass() {         if (is_null($this->className)) {             $className = preg_replace('/^.+\\\\/', '', get_class($this));             $this->className = $className;         }         return $this->className;     }     /**      * リクエストに対する認証可否      * @return bool      */     public function authorize()     {         return true;     }     /**      * 入力検査ルール      * @return array      */     public function rules()     {         return config('validation.'.$this->getClass().'.rules');     }     /**      * 項目名      * @return array      */     public function attributes()     {         return config('validation.'.$this->getClass().'.attributes');     }     /**      * 特別エラーメッセージ      * @return array|mixed      */     public function messages()     {         return config('validation.'.$this->getClass().'.messages');     } } | 
以下は、config/validation.php の記述例です。FormRequest実装クラス名をキーに設定を記述するとそれを読み込んでくれる仕掛けになっています。クラスの型を利用しつつ、リクエスト値の検査は設定ファイルを利用してカスタマイズしやすくします。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?php return [     'MailUpdateRequest' => [         'rules' => [             'email' => 'required|max:100',             'email_conf' => 'required|same:email|max:100',             'confirm' => 'required',         ],         'attributes' => [             'email' => 'メールアドレス',             'email_conf' => 'メールアドレス(確認)',         ],         'messages' => [             'confirm.required' => 'チェックを入れてください',         ],     ], ]; | 
さらにテンプレート側に目を向けてみましょう。入力フォームのテンプレートは、エラー表示と「戻る」ボタンで遷移の両方を共通で実現している場合がほとんどだと思います。Laravel で用意されている old ヘルパーメソッドで取得できるのは、エラー時のセッションしかありませんので、以下のような記述をすることでエラー時のセッション値があれば、セッション値、なければリクエスト値をとる実装が可能です(old メソッドのデフォルト値にリクエスト値を設定する実装になります)。
| 1 | {{ old('email',$request->get('email')) }} | 
エラーを表示する場合は以下で対応できます。
| 1 | {!! $errors->first('email', '<span class="error">:message</span>') !!} | 
$errors->has(’email’)や$errors->count()などもよく使うメソッドだと思います。エラーは MessageBag というクラスで定義されていますので、以下を参考にしていただければ幸いです。
https://laravel.com/api/5.6/Illuminate/Support/MessageBag.html
ValidatesRequestsトレイトを使う場合
FormRequest の処理のうち Controller 側の処理を切り出したものが ValidatesRequest になります。
Controller で use ValidatesRequest をすることで Controller 中で $this->validate() メソッドなどが使えるようになります。
以下 Laravel マニュアルページより
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /**  * Store the incoming blog post.  *  * @param  Request  $request  * @return Response  */ public function store(Request $request) {     $this->validate($request, [         'title' => 'required|unique|max:255',         'body' => 'required',     ]);     // } | 
値検査に失敗すると Illuminate\Contracts\Validation\ValidationException が発生し、FormRequest 同様、前のページに戻ります。validateをかけるタイミングを調整できるため、リクエスト値の前処理が必要な場面や FormRequest の作成をしたくない場合に使われると思いますが、Validator ファサードの選択肢もあるため、相対的に利用する場面は少ないと思われます。
Validatorファサードを使う場合
Validator ファサードを使えば値検査部分だけを部品化して値検査をすることが可能です。バッチ系で値検査を行うときは直に呼び出して使うことが多いと思います。例えばCSVインポートバッチの入力値チェックなどはこの Validatorファサードの出番です。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $input = $request->all(); $rules = config('validation.'.$key .'.rules'); $attributes = config('validation.'.$key .'.attributes'); $messages = config('validation.'.$key .'.messages'); $validator = Validator::make(     $input,     $rules,     array_merge(config('validation.common.errors'), $messages),     $attributes ); if ($validator->fails()) {     // エラー処理 } // 正常処理 | 
上記は、config/validation.php で設定を外だししている例です。個別実装することももちろん可能ですが、ある程度共通化できるところは共通化しておくと良いでしょう。
まとめ
なんだかんだいいましたが、場面場面に応じて最適な値検査の方法を選択して実装していくとよいでしょう。その中で特に伝えたかったことが「設定ファイル化」という部分になります。値検査(バリデーション)というプログラムの課題の中でconfig/validation.php にまとめておく、HTML部品という課題の中で config/form.php にまとめておく、アプリケーション設定として、config/const.php にまとめておく、などなどです。後で入力チェックをまとめるときに個別の実装をみなくてよいのは、大きな助けになると思います。まだの方は、実装時の良い習慣としてプログラムの課題の設定ファイル化を是非やってみてください。
補足
以下補足になります。入力、確認、完了のような遷移がある場合のルーティングのTIPSです。通常、入力画面は、GETリクエスト、それ以外はPOSTリクエストとしていると思います。つい忘れがちですが、保存ページについては、GETも受け付けて入力画面に逃がすとよいと思います。戻すときの遷移でリロードして、MethodNotAllowedのエラーが見えてしまうとちょっとカッコ悪いですよね。
| 1 2 3 4 5 6 |     Route::get('/mail/edit', 'MemberController@edit');     Route::post('/mail/edit', 'MemberController@editConfirm');     Route::post('/mail/update', 'MemberController@update');     // URLを直叩きされたら、入力画面へ     Route::get('/mail/update', 'MemberController@edit');  | 














