RitoLabo

Laravelのフォームリクエストクラスでバリデーションロジックをコントローラから分離する

  • 公開:
  • 更新:
  • カテゴリ: PHP Laravel
  • タグ: PHP,Laravel,artisan,5.5,5.4,5.3,Beginner,validation,Form,FormRequest,Gate,5.6

Laravelではフォームを送信した際に、バリデーションメソッドを使い入力値が適正であるかどうかをチェックできますが、実際の開発現場ではフォームの項目が多かったり、複雑なバリデーションが必要だったりなど、コントローラにそのままバリデーションを書くと無駄に肥大化してしまい、数か月後に見て萎えるなんて状況も少なくはありません。

そこで今回は、フォームリクエストクラスを用いてバリデーションロジックをコントローラから分離させます。ソースコードの見通しが各段に良くなるほか、バリデーションの定義のみに集中できます。

アジェンダ
  1. 開発環境
  2. フォームリクエストクラスの生成
  3. フォームリクエストクラスの実装
  4. コントローラの変更
  5. 動作確認
  6. エラーメッセージのカスタマイズ
  7. 独自バリデーションの追加
  8. 権限バリデーション

開発環境

開発環境に関しては以下の通りです。

  • Linux CentOS 7
  • Apache 2.4
  • PHP 7.2/7.1
  • Laravel

Laravelのバージョンに関しては、5.6/5.5/5.4/5.3にて動作確認済みです。

Laravelのルートディレクトリを「laravel/」とします。

尚、通常のバリデーションメソッドを用いたバリデーションの基本形は以下を参考にしてください。
Laravelのvalidationメソッドでバリデーションを実装する入門編(日本語エラーメッセージ付き)

フォームリクエストクラスの生成

まずは、フォームリクエストクラスを作成します。laravelルートディレクトリに移動し、以下のartisanコマンドを叩きます。

# laravelのルートディレクトリへ移動
cd /path/to/laravel

# リクエストクラス生成
php artisan make:request SampleValiRequest

# 実行結果
[demo@localhost laravel]$ php artisan make:request SampleValiRequest
Request created successfully.

app/Http/Requests 配下へ SampleValiRequest.php が生成されます。

laravel
├─ app
│ ├─ Http
│ │   └─
Requests
│ │   └─
SampleValiRequest.php
laravel/app/Http/Requests/SampleValiRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SampleValiRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return false;
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}

フォームリクエストクラスの実装

それでは、生成したフォームリクエストクラスにバリデーションを実装していきます。

laravel/app/Http/Requests/SampleValiRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SampleValiRequest 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
*/
public function rules()
{
return [
'name' => 'required|string|max:255',
'gender' => 'required',
'age' => 'required|integer',
'file' => 'required|file|image|max:10000',
'comment' => 'required',
];
}
}

rules()メソッドにバリデーションルールを記述しています。そして、authorize()メソッドでは、ひとまずtrueを返すようにしておいてください。

コントローラの変更

バリデーションロジックをフォームリクエストクラスへ移したので、コントローラの内容も以下に変更します。

laravel/app/Http/Controllers/ValiController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\SampleValiRequest;

class ValiController extends Controller
{
public function index()
{
return view('sample_vali');
}

public function receiveData(SampleValiRequest $request)
{
return view('sample_vali', ['status' => true]);
}
}

バリデーションロジックが不要なのでとてもすっきりしています。基本形である、コントローラ内でバリデーションメソッドを用いバリデーションを行う場合と見比べてみてください。

コントローラに追加したのは以下2点です。

use App\Http\Requests\SampleValiRequest;

先ほど作成したフォームリクエストクラスをuseし

public function receiveData(SampleValiRequest $request)

今回POST送信を受ける為に作成したreceiveData()メソッドの引数にSampleValiRequestクラスを渡しています。こうする事で、フォームをreceiveDataへ送信した際に、自動でバリデーションが行われます。つまり、receiveData()メソッドに処理が渡ってくる=フォームリクエストクラスで定義したバリデーションは無事に通過した。という事になります。

ちなみにバリデーションが通らなかった場合は、エラーメッセージを持って送信元のフォームのあるページへリダイレクトされます。つまり自動で戻ってくるという事になります。(HTTPステータスコードは422です)

動作確認

これで作業は完了しました。http://YOURDOMAIN/sample/valiへアクセスし、実際にフォームを送信してエラーを出してみます。

ロジック分離確認画面

これでバリデーションロジックの分離は完了です。

エラーメッセージのカスタマイズ

上記キャプチャのエラーメッセージは日本語言語ファイルから出力していますが、場合によってはフォームごとに独自のエラーメッセージを表示したいという状況もわりとあったりします。

そういう場合も、フォームリクエストクラスに追加実装するだけで実現できます。

SampleValiRequest.php にmessages()メソッドを追加します。

laravel/app/Http/Requests/SampleValiRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SampleValiRequest 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
*/
public function rules()
{
return [
'name' => 'required|string|max:255',
'gender' => 'required',
'age' => 'required|integer',
'file' => 'required|file|image|max:10000',
'comment' => 'required',
];
}

/**
* エラーメッセージのカスタマイズ
* @return array
*/
public function messages()
{
return [
'name.required' => '名前を入力してください',
'name.string' => '名前は文字列で入力してください',
'name.max' => '名前は255文字以内で入力してください',
'gender.required' => '性別を選択してください',
'age.required' => '年齢を選択してください',
'age.integer' => '年齢は数字で入力してください',
'file.required' => 'ファイルを選択してください',
'file.file' => 'ファイルのアップロードに失敗しました',
'file.image' => 'アップロード可能な画像は「jpg」「png」「bmp」「gif」「svg」です',
'file.max' => 'アップロードするファイルは10MBまでです',
'comment.required' => 'コメントを入力してください',
];
}
}

見た感じでなんとなくわかったと思いますが、KEY部分は「name」+「ルール」になっています。

'name' => 'required|string|max:255'  'name.required' => '名前を入力してください'

改めて動作確認を行ってみます。

エラーメッセージ確認画面

エラーメッセージが変更されました。

独自バリデーションの追加

Laravelに限りませんが、PHPフレームワークでWebアプリケーション開発を行っていると、どうしても機能の限界とやらにぶつかる場合も多々あります。

バリデーションとは「妥当性確認」であり、開発案件によって様々な方向でのバリデーションが必要になる場合もあり、フレームワークの機能の限界というよりは、独自のバリデーション実装が必要になるという方が正しいですが、Laravelの場合、そういった状況でも柔軟に対応できます。

以下の記述のようにwithValidator()メソッドを実装する事で、独自の処理を追加する事が出来ます。

laravel/app/Http/Requests/SampleValiRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SampleValiRequest 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
*/
public function rules()
{
return [
'name' => 'required|string|max:255',
'gender' => 'required',
'age' => 'required|integer',
'file' => 'required|file|image|max:10000',
'comment' => 'required',
];
}

/**
* エラーメッセージのカスタマイズ
* @return array
*/
public function messages()
{
return [
'name.required' => '名前を入力してください',
'name.string' => '名前は文字列で入力してください',
'name.max' => '名前は255文字以内で入力してください',
'gender.required' => '性別を選択してください',
'age.required' => '年齢を選択してください',
'age.integer' => '年齢は数字で入力してください',
'file.required' => 'ファイルを選択してください',
'file.file' => 'ファイルのアップロードに失敗しました',
'file.image' => 'アップロード可能な画像は「jpg」「png」「bmp」「gif」「svg」です',
'file.max' => 'アップロードするファイルは10MBまでです',
'comment.required' => 'コメントを入力してください',
];
}

/**
* 独自処理を追加する
* @param $validator
*/
public function withValidator($validator)
{
$validator->after(function ($validator) {
if (
strtotime(date('H:i:s')) >= strtotime('21:00:00')
||
strtotime(date('H:i:s')) <= strtotime('09:00:00')
) {
$validator->errors()->add('field', '投稿できるのは9時から21時までです。');
}
});
}
}

今回は一歩踏み込んだ例として、フォームバリデーションではなく、単純に時刻が9~21以外であればエラーにする(小学生のテレビゲーム制限)処理を実装しましたが、フォームをさらに検査してもいいし、とにかくここは独自処理を追加できます。

改めて動作確認を行ってみます。

独自バリデーション確認画面

フォームのバリデーションには通過していますが、時間に引っかかったのでエラーになりました。

と、こんな実装も行う事が出来ます。

権限バリデーション

フォームリクエストクラスにauthorize()メソッドがありますが、そこでは、ユーザの権限など、認証系のバリデーションを定義する事が出来ます。

例えばリファレンスで紹介されている以下のコードですが

public function authorize()
{
$comment = Comment::find($this->route('comment'));

return $comment && $this->user()->can('update', $comment);
}

「あるコメントが表示される、もしくは編集できるページへアクセスしたユーザが、そのコメントの更新権限を持っているか」というバリデーションを行っています。

もちろん、その権限を持っていればバリデーションに通り、持っていなければバリデーションエラーとなります。

他にも、以下のように定義する事も出来ます。

public function authorize()
{
return Gate::allows('admin-higher');
}

上記は、Gateファサードを使って、権限バリデーション(その権限を持っているか)を行っています。上記例の場合は、管理者権限以上のみバリデーションを通過することができます。

Gateファサードに関しては以下を参考にしてください。
LaravelのGate(ゲート)機能で権限(ロール)によるアクセス制限を実装する

ちなみにauthorize()メソッドを使わない場合は、最初にしたようにtrueを返すようにしておきます。このメソッドでtrueが返らない場合、403エラーが返ります。

まとめ

作業は以上になります。バリデーションのあるところフォームあり送信データの格納ありと、なかなか数が多くなると気分が重たくなりがちですが、こうしてロジックを分離できるとメンテナンスも楽になるので良いですよね。簡単に実装できるので、是非試してみてください。