FormRequest
クラスを利用しての認可・バリデーションは、Webアプリケーションで非常によく使われます。私の場合、特に後者は使わないケースが無いと言いきって良いくらい使います。
しかしながら、毎回型にはまった利用で「何となく」で使っていませんか。
利用頻度が高い=きちんと理解しておきたい。私も含めてそんな方も多いのではないでしょうか。
自身の勉強もかねて、本ページではFormRequest
を徹底的にまとめましたので参考にしていただければ幸いです。
FormRequestとは
Laravelを始めたばかりで、そもそものFormRequest
についての認識が曖昧な方向けに書いています。すでにご存じの方は次項に飛ばしてください。
概要
FormRequest
は、ユーザーがフォーム等から送信してきた項目に対し、
- 認可(オーソライゼイション)
- 検証(バリデーション)
をするために使います。
Laravelが用意するFormRequest
クラスには、それらを行うための機能がすでに備わっています。
私たちはFormRequest
クラスを継承したクラスを利用することで、簡単に認可・バリデーションを行うことが可能です。
Requestクラスを継承している
FormRequest
クラスは Illuminate\Http\Request
クラスを継承しています。したがって、FormRequest
は Request
クラスの拡張版と考えることができ、Request
クラスで提供される機能は FormRequest
で直接利用できます。
例えば、$this->name
や $this->input('name')
などのデータアクセスも同じように可能です。
FormRequestの何が良い?
実は、FormRequest
クラスを利用する以外にも認可・バリデーションを行う方法はLaravelに用意されています。
例えばバリデーションを例に考えてみましょう。Validator
ファサードを使う方法、Request
クラスのvalidate
メソッドを使うなどがあります。
以下でも解説していますので、ご興味ある方はご覧になってみてください。
これらの方法ではなく、あえてFormRequest
クラスを選択するには理由があります。それは、コントローラーからバリデーション処理を完全に切り離す(カプセル化する)ことが可能だからです。
実際、コントローラーにはバリデーションの処理はあまり書きたくありません。できるだけシンプルにした方がコードの見通しがよくなりますし、余計なことを考えたくないからです。
そのため、FormRequest
クラスの利用は、LaravelでのWebアプリケーション開発では重要になってきます。
簡単なバリデーションだったり、コード例を挙げるだけなら他の方法ももちろんありです。
FormRequestの子クラス作成
最初に書いた通り、FormRequest
自体ではなく、FormRequest
を継承した子クラスを作り利用します。基本的な作り方は2種類あります。
コマンドで作る
以下のようなLaravelのartisanコマンドで作成可能です。
php artisan make:request [任意のクラス名]
クラス名は、○○Requestという名称にすることが慣例になっています。ここでは、InquiryRequest
というクラスを作りたいので、以下の様にしてコマンドを発行してみます。
php artisan make:request InquiryRequest
これにより、app/Http/Requests/InquiryRequest.php
が作成されます。
以下の様に、/
(スラッシュ)で区切ることで、ディレクトリを分けて指定することも可能です。
php artisan make:request Contact/InquiryRequest
これにより、app/Http/Requests/Contact/InquiryRequest.php
が作成されます。
複製する
Laravelコマンドは便利ですが、忘れがちですよね。「なんだっけか……」と調べていたら時間がかかってしまいます。
すでに一度コマンドでFormRequest
の子クラスが作成されている場合、調べる必要はありません。複製して別のクラスを作ることも可能だからです。
具体的には、複製したファイルのファイル名&クラス名を、新しいものに変えます。
例えばInquiryRequest.php
→ApplyRequest.php
にする場合、クラス名もInquiryRequest
→ApplyRequest
となります。
注意点としては、ファイルを別のディレクトリに移動する場合は、名前空間の変更が必要になります。
それがよく分からない内は、コマンドで作った方が無難かもしれませんね。
そもそものコマンドの入力を楽にするツールもありますので、いつかご紹介したいと思います。
認可とバリデーションの基本設定
初期状態のコード
コマンドで作成したInquiryRequest
クラスを例に見てみましょう。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class InquiryRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
*/
public function rules(): array
{
return [
//
];
}
}
ここで定義されている以下の2つのメソッドを編集すれば、すぐにでも利用可能です。
- authrize()
- rules()
authrize()
authorize()
メソッドは、リクエストが認可されるかどうかを決定するためにあります。このメソッドは、リクエストが実際にバリデーションを受ける前に評価されます。
戻り値としてtrue
または false
を返す必要があります。
public function authorize(): bool
{
return false;
}
初期状態では、上記のようにfalse
を返すため、バリデーションすら行わず403 Forbidden
を返しリクエストが拒否されます。
これをそのままtrue
に変更するか、条件によりtrue
を返すようにすればそのまま処理が進みます。つまり認可(許可)されたことになりますね。
public function authorize(): bool
{
return true;
}
実際には条件分岐して値を返したり、パラメーターにタイプヒントして依存注入したりすることも可能です。
authrize()
メソッドについて詳しく知りたい方は、以下ページで解説していますのでご覧ください。条件分岐の例などもあります。
rules()
rules()
メソッドは、バリデーション用の配列を戻り値として定義します。ここで定義したルールが、コントローラーの処理が行われる前にそのままバリデーションとして実行します。
以下の様に、初期状態では空の配列=バリデーションは設定されていません。
public function rules(): array
{
return [
//
];
}
ここでは例として、以下の様に定義してみます。
public function rules(): array
{
return [
'name' => 'required|max:20',
'tel' => 'nullable|regex:/^[0-9]{2,4}-[0-9]{2,4}-[0-9]{3,4}$/',
'email' => 'required|email',
'body' => 'required|max:1000',
];
}
これは、以下の様な意味を持ちます。
- name=必須入力で最大20文字
- tel=入力任意、入力された時はハイフン区切りの電話番号の形式で
- email=必須入力でメールアドレスの形式
- body=必須入力で最大1000文字まで
直感的に理解しやすくなっていますね。
配列のキーがフォーム要素名、値がバリデーションルールです。ルールは|
(パイプ)で区切ることも可能です。
このバリデーションが通らなければ、デフォルトでは直前の画面にリダイレクトされます。
こちらの指定は特に奥が深いため、いずれ解説ページを設けたいと思います。
Controllerで読み込んで利用する
利用はとても簡単。コントローラの使いたいメソッドのパラメータとして埋め込むだけです。これはLaravelのサービスコンテナという機能による「依存性の注入」と言います。
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\InquiryRequest;
class InquiryController extends Controller
{
/**
* フォーム表示
* @return \Illuminate\View\View
*/
public function index()
{
return view('web.inquiry.index');
}
/**
* フォームPost
* @param InquiryRequest $request
* @return \Illuminate\Http\Response
*/
public function post(InquiryRequest $request)
{
// ~以下省略~
}
}
6行目のuse
文で、InquiryRequest
クラスを使用するための記述をします。
use App\Http\Requests\InquiryRequest;
次に、24行目のpost()
メソッド(自作)のパラメータとして、InquiryRequestを読み込むように設定します。
public function post(InquiryRequest $request)
{
// ここを通る時点で認可&バリデーションが通っている
たったのこれだけで、コントローラのメソッド(ここではpost()
)が呼び出される前にauthorize()
、通った場合はrules()
の処理が実行されます。
復習になりますが、以下です。
- authorize()が通らなければ403エラーを返す
- rules()のバリデーションが通らなければ直前のページにリダイレクト
(Ajaxの場合はエラーのレスポンスを返す)
つまりこの場合、コントローラーのpost()
メソッド内部に処理が移った時点で、認可&バリデーションが通っていることが保証されます。
コントローラ内でバリデーションのことを考えなくていいので、とても良いですね!
その他基本的な使い方&設定
FormRequest
が実装しているプロパティやメソッドをオーバーライド(上書き)することで、動作を変えることが可能です。
また、Request
クラスを継承しているので、同じように使用することが可能です。
リクエストデータを取得する
認可やバリデーションに関するクラスにつき、リクエストデータの取得・利用はよく取り扱います。
例えば以下では、authorize()
メソッド内の7行目に$this->input('action_type')
でリクエストデータを取得しています。
public function authorize()
{
// 認証されたユーザーを取得
$user = auth()->user();
// リクエストデータの 'action_type' を取得
$actionType = $this->input('action_type'); // または $this->action_type
// 例: ユーザーが管理者であり、かつリクエストの 'action_type' が特定の値かどうかを確認
return $user && $user->is_admin && $actionType === 'approve';
}
コメントでもあるように、$this->action_type
でも同様に取得可能です。
バリデーションの最初の失敗で停止させる
通常、バリデーションは途中でエラーが生じても最後まで実施します。
例えばバリデーション項目にnameとemailがあったとします。しかしながらnameにエラーがあればemailはバリデーションさせたくない……なんて場合があるかもしれません。
以下の指定でそれが実現可能です。
/**
* 最初のルールの失敗でバリデーションが停止します。
* @var bool
*/
protected $stopOnFirstFailure = true;
このようにすると、先ほどの例で言えばnameでエラーがあった場合、即座にエラーのレスポンスに移ります。emailはバリデーションされません。
リダイレクト先を変更する
通常、フォームをPostリクエストされた際、エラーが発生すると、リクエスト元のフォームがあったページへリダイレクトされます。
これを以下の様に任意のリダイレクト先へ変更することが可能です。
/**
* バリデーション失敗時に、/dashboardにリダイレクトさせる場合
* @var string
*/
protected $redirect = '/dashboard';
上記では'/dashboard'
へリダイレクトしますが、名前付きルートで指定したい場合は、$redirectRoute
を使用します。
/**
* バリデーション失敗時に、dashboardへリダイレクトさせる場合
* @var string
*/
protected $redirectRoute = 'dashboard';
バリデーション前に処理を行う
バリデーション前に、データを加工したい場合があります。アプリケーション側の都合でフォーマットを整えたい場合です。
これらはprepareForValidation()
メソッドをオーバーライドすることで実現可能です。
例えば以下の場合、ハイフンありの電話番号を想定した際、ハイフンに似た文字を半角ハイフンにし、全角数字を半角にしています。
protected function prepareForValidation()
{
// 入力データ取得 (例 03-1111ー2222
$tel = $this->input('tel');
// ハイフンに似た文字を半角ハイフンに変換(この処理はちゃんとやるともっと複雑)
$tel = str_replace(['-', '―', '‐', 'ー'], '-', $tel);
// 半角数字を全角数字に変換:結果→03-1111-2222
$tel = mb_convert_kana($tel, 'n');
// 変換後のtelをリクエストデータにセット
$this->merge(['tel' => $tel]);
}
追加バリデーションの実行
LaravelのFormRequest
は、単にバリデーションルールを提供するだけでなく、追加のカスタムバリデーションロジックも実装することができます。
これにより、標準的なバリデーションルールに収まらない複雑な条件を簡単に実装することができます。
FormRequest
にはそのための方法の一つとしてwithValidator()
メソッドが用意されています。このメソッド内で$validator->after()
フックメソッドを定義することで、追加バリデーション機能を実現可能です。
以下の例では、emailが'admin@example.com'
だった際にエラーを表示するようにしています。
use Illuminate\Validation\Validator;
class InquiryRequest extends FormRequest
{
// ~略~
public function withValidator(Validator $validator): void
{
$validator->after(function (Validator $validator) {
if ($this->input('email') === 'admin@example.com') {
$validator->errors()->add('email', 'このメールアドレスは使用できません。');
}
});
}
}
Validator
クラスをパラメータに取りますので、1行目のように、use
文で読み込んでおきましょう。
あとは9~13行目にて、$validator->after()
メソッドでバリデーション処理を定義しています。この部分の’email’やエラーメッセージを変えれば、どのようにも応用できそうですね。
バリデーション成功時に処理を行う
バリデーションの後にデータを加工したい、という場面もあるかもしれません。
このような処理は passedValidation()
メソッドの中で行うことで、コントローラのアクションに到達する前にデータを加工することができます。
protected function passedValidation()
{
// 'price' の値を整数として扱いたい場合
$this->merge([
'price' => intval($this->price)
]);
}
passedValidation()
メソッドは、バリデーションエラーが存在する場合実行されません。
ただしバリデーションが成功して初めて実行されることを念頭に置く必要があります。
バリデーション失敗時に処理を行う
failedValidation()
メソッドをオーバーライドすることで、バリデーション失敗時に処理を行うことが可能です。
以下例では、カスタムのエラーレスポンスを生成しています。
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Validation\ValidationException;
class CustomRequest extends FormRequest
{
// ~略~
protected function failedValidation(Validator $validator)
{
// バリデーション失敗時のレスポンスデータ
$response = [
'status' => 422,
'message' => 'Validation failed!',
'errors' => $validator->errors()
];
// ValidationExceptionをスローし、カスタムレスポンスを返す
throw new ValidationException($validator, response()->json($response, 422));
// parent::failedValidation($validator);
}
}
ここでは17行目で独自のレスポンスを返しています。
自分で例外をスローしない場合は、親クラスの同メソッドを呼ぶのを忘れないでくださいね(16行目のコメントアウトしている行のように)。
エラーメッセージの日本語化&カスタマイズ
Laravelのインストール直後では、バリデーションエラーが英語で表示されます。そのため、日本語でのWebアプリケーションの場合、日本語化することが必須になってきます。
日本語化するには、以下の2つの方法があります。
- Laravelの言語ファイルを作成&設定する
- FormRequestの子クラスで定義する
Laravelの言語ファイルを作成&設定する
Webアプリケーション全体で利用するエラーメッセージの日本語化であれば、言語ファイルが好ましいでしょう。少し長くなるため、以下ページの専用解説ページをご覧ください。
FormRequestの子クラスで定義する
FormRequest
の子クラス内に記述する方法もあります。ここで定義する日本語のエラーメッセージは、Laravelの言語ファイルの指定よりも優先されます。
そのため、そのクラスでのみ使いたいエラー表示には適しているでしょう。
messages()
メソッドをオーバーライドし、そこで定義可能です。
public function messages()
{
return [
'title.required' => 'タイトルは必須です。',
'body.min' => '本文は最低10文字必要です。',
];
}
上記では'title'
というバリデーション項目名の、'required'
ルールのエラー等を指定しています。
以下のようにmessages()
メソッドにプレースホルダー(:attribute
)を用意し、attribute()
メソッドで置き換える方法もあります。
public function messages()
{
return [
'title.required' => ':attribute は必須です。',
'body.min' => ':attribute は最低 :min 文字必要です。',
];
}
public function attributes()
{
return [
'title' => 'タイトル',
'body' => '本文',
];
}
こちらも指定には長い説明を要するため、詳しくは以下のページをご参照ください。
コントローラでデータを利用する
FormRequest
は、認可とバリデーションのみを行うわけではありません。
コントローラーに処理が移った後で、バリデーションに利用したデータを取得することはごく普通にあります。
バリデーションに通したデータの取得
以下の様に、FormRequest
のvalidated
メソッドでバリデーション済みデータを取得可能です。
public function post(InquiryRequest $request)
{
// ここを通る時点で認可&バリデーションが通っている
// バリデーション済みデータの取得
$validated = $request->validated();
dd($validated);
$validated
は配列で返されます。dd($validated);
の出力例は以下です。
array:4 [▼ // app\Http\Controllers\InquiryController.php:30
"name" => "鈴木"
"tel" => "03-1111-2222"
"email" => "info@exmple.jp"
"body" => """
お問い合せです。
改行が含まれます。
"""
]
つまりはコントローラー内で$validated['name']
としてアクセス可能です。
バリデーションに通した一部のデータの取得
$request->validated();
は全てのバリデーション済みデータを取得しますが、余計なデータは不要なケースもあります。
例えば「会員規約に同意する」の値は、バリデーションには使うけどデータベースには登録しないので不要……という場合です。
こういったケースでは一部のデータを指定して取得する方法もあります。
具体的には$request->safe()
メソッドが返すIlluminate\Support\ValidatedInput
オブジェクトが持つonly()
やexcept()
メソッドを使います。
public function post(InquiryRequest $request)
{
// ここを通る時点で認可&バリデーションが通っている
// バリデーション済みの'name'と'email’のみ取得
$validated = $request->safe()->only(['name', 'email']);
// バリデーション済みの'name'と'email’以外を取得
$validated = $request->safe()->except(['name', 'email']);
safe()
という名称の通り、バリデーションに通っているので安心して利用可能です。
応用的な使い方
これまでに解説した情報だけで、FormRequest
を使って大抵の認可・バリデーションや、その後のデータの利用ができると思います。
しかしながら、実際のWebアプリケーションでは「ああしたい」「こうしたい」と細かい要望が発生するのはめずらしくありません。
ここではそんな応用編の使い方をご紹介します。それぞれが丁寧に解説すると長くなってしまうので、ここでは簡単にご説明します。
ルールを分岐させる
ルールは固定で定義しても良いですが、多くの場合はそうはいきません。
リソースの作成(POSTリクエスト)と更新(PUTまたはPATCHリクエスト)で異なるバリデーションルールを適用するシーンは割とよくあります。
例えば以下では、新規登録時はメールアドレスをuniqueに。更新時は、該当ユーザーのemailはuniqueから除外します。
public function rules()
{
if ($this->isMethod('post')) {
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
// ...
];
}
if ($this->isMethod('patch')) {
return [
'name' => 'sometimes|required|string|max:255',
'email' => 'sometimes|required|email|unique:users,email,' . $this->route('user'),
// ...
];
}
// 本来はデフォルトのエラーも定義が必要
return [];
}
prepareForValidation()で加工したデータをold()で表示させたい
例えば、前項のようにprepareForValidation()
でデータを加工したとします。しかしその後バリデーションエラーで直前のページにリダイレクトされた際、ビューのold()
ヘルパー関数で使用することはできません。
しかしながら、ユーザビリティ向上のためなどの理由によりold()
でも加工済みデータを使いたい場合もあるでしょう。
そんな場合はfailedValidation()
をオーバーライドし、リクエストを上書きすることで実現可能です。
条件として、前項のprepareForValidation()
でデータを加工します。その後、13行目のように、加工後のデータを使ってリクエストオブジェクトの値を書き換えます。
protected function prepareForValidation()
{
// データを加工する
$tel = $this->input('tel');
$tel = str_replace(['-', '―', '‐', 'ー'], '-', $tel);
$tel = mb_convert_kana($tel, 'n');
$this->merge(['tel' => $tel]);
}
protected function failedValidation(Validator $validator)
{
// 加工後のデータを使って値を書き換える
request()->merge($this->input());
// 親クラスのメソッドを実行(例外をスローさせる)
parent::failedValidation($validator);
}
コントローラーの中で手動でバリデーションしたい
すでに書いた通り、FormRequestはコントローラーの中に処理が移った時点で、バリデーションに通っています。
しかし、コントローラー内でバリデーションしたいことは普通にあります。例えば複数のステップがあるフォームにて、最後にも念のためバリデーションしたいなどです。
いろいろなやり方があると思いますが、私は現在は以下の方法を利用しています。
public function send()
{
// 以前にバリデーション済みのセッションデータを念のため最終行程で再バリデーション
$datas = session('firstform', []);
$request = new InquiryRequest();
$validator = \Validator::make(
$datas,
$request->rules(),
$request->messages(),
$request->attributes()
);
if ($validator->fails()) {
// ここでエラー時の処理
}
わりと試行錯誤したので、別記事でネタにしたいと思います。
403エラーのページをカスタマイズしたい
厳密にはFormRequest
の問題ではありませんが、関連情報なので簡単にご案内しておきます。
resources/views/errors
ディレクトリに403.blade.php
という名前で新しいビューファイルを作成すればOKです。
まとめ
FormRequest
クラスはなかなかできることも多く、ボリュームがありましたね。本ページでは伏せませんでしたが、発生したエラーをビューで表示する方法については以下ページをご覧ください。
認可やバリデーションはウェブアプリケーションにとっては重要な要素ですので、しっかりとした知識が必要になります。
Larave公式マニュアルよりも、ちょっとでも分かりやすくと思いまとめました。学習のお手伝いができれば幸いです。
私自身もまとめることで理解を深めることができました!
コメント