どーも!
今回はlaravelでバリデーションを実装してみたいと思います!
最近あまりフロント側を触ってないのでバリデーションのコードも忘れてきて...いるので...
初心者向けにもわかりやすいよう、改めて説明しながら再確認しようかなーと。
目次
バリデーションって?
Webページでは様々な値を入力して受け取り、その入力値を使っていろいろなサービスが実現されています。
ただ、年齢で数値を入力して欲しいのに間違って氏名が入力されたりしたら正しくサービスの提供ができなくなってしまいますよね。
そのように入力される値が期待する型やある一定のルールにのっとっているかどうかを調べることをバリデーションと言います。
よくWebサービスの新規会員登録で空欄のまま次へを押すと、”空欄じゃダメだぜー!”とか怒られたりするあれですね。
Laravelでやってみる!
さて、それでは早速Laravelで試してみようと思います!
面倒なのでプロジェクトを立ち上げたデフォルト画面で試してみます。
試してみるのは以下のようなものです。
値を入力してその値が出力されるようにする
まずはバリデーションと全く関係ないんですが...w
とりあえず入力欄を作ってその入力欄に値が入れられ、ボタンが押されたらすぐ上の"Laravel"がいい感じに入力された値に書き換わるといった動作を作っていきます。
早速ですが、"resources/views/welcome.blade.php"のLaravelを表示していた箇所を以下のように修正します。
divタグのcontentの中身で、linksより上の部分を変更してもらえれば大丈夫です。
<div class="content">
<div class="title m-b-md">
{{ $comment ?? 'Laravel' }}
</div>
<form action="validation" method="POST">
<input name="comment"></input>
{{ csrf_field() }}
<br>
<button> Send </button>
</form>
<div class="links">
...
</div>
</div>
やっていることは、$commentという変数としてあとで入力された値を受け取り、Laravelの代わりに表示させるようにしています。
"??"はエルビス演算子といって、$commentが"null"の場合、変わりにLaravelという文字を表示しますよ〜っていう意味です。
一番最初にこの画面にアクセスする際、何も入力されていないはずなので、$commentも何も受け取っておらず、それが原因で$commentなんてねーぞー!と怒られてしまいます。
そのため、$commentがちゃんと値が入っているか確認した上で表示するようにしているわけです。
次にformタグで"validation"という名前のルートに対して、commentという入力をポストで送るようにしました。
Sendというボタンが押下されたら入力された内容がPostで"validation"というルートに送信されるようになりました!
Laravelの画面を更新するとちゃんと入力欄などが表示されているはずです。
ただ、これだけだと"validation"ルートを定義していないので、ボタンを押しても404エラーになってしまいます。
それではルートを定義しましょう!
ルートは"routes/web.php"の中身に以下を追加します。
Route::post('validation', 'techController@index');
先ほどbladeからpostで値を渡したので、それに合わせてpostで、そしてformのアクションで"validation"というルートへのアクセスをするように設定していたので、それに合わせてrouteの名前も"validation"にしています。
また、アクセスされた場合、techControllerのindexメソッドが実行されるように指定しました。
これで、ルート定義に従ってformのボタンが押されたら入力値がtechControllerのindexメソッドへ渡されるようになりました!
しかしtechControllerはまだ作成していませんね!
次はそれを作ります!
以下のコマンドをlaravelのプロジェクトフォルダにて実行しましょう!
php artisan make:controller techController
すると"app/Http/Controllers"に"techController.php"が作成されます。
(上記コマンドを実行せず、コードをコピペしてファイル名を合わせて作成してもOKです)
そのファイルを以下のように書き換えます。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TechController extends Controller
{
public function index(Request $request)
{
$comment = $request->comment;
return view('welcome')->with([
'comment' => $comment
]);
}
}
さて、先ほどtechControllerのindexメソッドに渡された値が渡ってくると言いました。
その値は上記で定義したindexメソッドに渡され、"$request"という変数で使用することができます。
実際はbladeで定義したinputのname属性の値と同じ名前のプロパティ(ここではcomment)として扱うことができます。
そのため、"$request->comment"という風に$requestのcommentというプロパティへアクセスすれば入力された値を取得することができます。
そして、indexメソッドで"welcome.blade.php"のviewを表示するようにし、 ついでにwithで入力された値が入っている"$comment"をview側で$commentとして扱えるように名前をつけて渡しています。
これで入力フォームに値を入れた状態で送信をすると、Laravelの箇所にその入力した値が表示されるようになりました。
いい感じですね!
さて、やっと本題に入れます...!
では次からバリデーションをやってみましょう!
バリデーションを追加する
ではバリデーションを試してみます!
とはいったものの...バリデーションにも実にたくさんの種類の方法が存在するので、それぞれ解説していこうと思います。
①ではバリデーションをする際に必要なエラー表示等も実装、よく引っかかる問題についても解説します。
できれば①から読み、理解した上で②以降を試してみるのが理解しやすいかと思います!
(本当はもっと綺麗に書くべきなんだけど...w)
①コントローラーで直接実行する
まず一つ目はコントローラーのメソッドで直接実行してみます。
先ほど作成したtechController.phpに少しだけ追加します。
以下のように修正しましょう!
public function index (Request $request)
{
$validatedData = $request->validate([
'comment' => 'required|string|max:10',
]);
$comment = $validatedData['comment'];
return view('welcome')->with([
'comment' => $comment
]);
}
$requestの中身に対してバリデーションをかけ、問題がない場合はそのまま値を$validatedDataに入れています。
バリデーションの内容は、commentっていう値は入力必須(require)で、文字列型(string)で、10文字以下(max:10)のやつだったらいいよ〜!って指定しました。
$validatedDataには配列として入力した値が入るので、配列の要素としてcommentの値を取得後、welcome.blade.phpへ値を渡しています。
もしもバリデーションで問題が生じた場合は、直前のページのURLに対してGETリクエストが行われます。
今回、公式の手法にのっとって$validatedDataとして保存しているんですが、実際のところ、以下でも問題なさそうです。
public ...
$request->validate([
'comment' => 'required|string|max:10',
]);
$comment = $request->comment;
return ...
$validatedDataを使わず、$requestの値をバリデーションし、問題なければ後続処理が行われるため、そのあとの
$comment = $request->comment;
の処理を行う際、$requestの値はバリデーションをクリア(問題が無い)していることが保証されています。
なので、これでも大丈夫なわけですね。
以下、公式のリファレンスです。
バリデーションは追加できました。
次はバリデーションで通らなかった場合(値が正しくなかった場合)の警告文を表示する部分を作っていきましょう!
とはいってもコピペするだけです!
以下をwelcome.blade.phpにコピペしましょう!
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
バリデーションに失敗したら、blade上で使用できる"$errors"という変数にそのバリデーションのエラーメッセージが入れられます。
その$errorsから中身をいい感じに出して表示しているのが上記のコードです。(公式のままです)
場合によっては複数のエラーが含まれるため、foreachで一個づつ表示するようになっています。
さて、これで準備は整いました!
では試しに入力してみましょうか!
できましたね!
実は...
さて、少し余談になりますが、大切なので...
実は先ほど作成したものはまだ問題があります...
一度正しい値を入力してからバリデーションに引っかかる値を入力してみましょう。
おおおっ!?
んー・・・。
ボタンを押した時のリクエストはPOSTリクエストになっているはずです。
ですが...
なぜかGETメソッドはこのルートで使えないよ〜〜〜!ってなっていますね。
これはバリデーションの仕様によるものです。
実はバリデーションに引っ掛かった場合の挙動は、"直前のルートへGETアクセス"します。
一番最初の元のページは"/"のrouteで表示されたbladeで、この時点でバリデーションに失敗をすると"/"のルートへGETでアクセスされます。
最初のページへのアクセスは以下のようにgetでrouteを登録しているので、バリデーションに引っ掛かってGETアクセスがきてもちゃんと動作します。
Route::get('/', function () {
return view('welcome');
})->name('welcome');
さて、では次に"/"のルートで表示された画面で正しい値を入れると、URLが"/validation"になるはずです。
formのactionで"validation"を指定してるため、ボタンアクセス後のルートは"validation"になるわけです。
先ほどの画像でいくと、”aaa”が大きく表示されている画面のURLは"http://localhost:8000/validation"になっています。
その状態で、バリデーションに失敗をすると、"validation"というルートに対してGETリクエストされます。
そう、今回validationのルートはPOSTでしか定義していませんでした。
そのため、先ほどのエラーが出てきてしまったわけです。
解決方法
先ほどの問題を解決していきましょう!
GETリクエストするようにしてあげればまぁ、良かったりはするんですが、仮に以下のようにルートを新しく定義したとします。
Route::get('validation', 'techController@index');
するとどうなるでしょう・・・?
んん・・・?
エラーはでなくなりましたが、何か変ですね...
これもややこしいんですが、getで"techController@index"を実行はできているんです。
ただ、バリデーションでリダイレクトされる場合、そのリクエストには"$comment"という値が存在しないいんです。
本来フォームから入力されたらもちろん存在しますが、バリデーションに失敗するとその値は無くなって、NULLになります。
そして、indexメソッドでは"$command"のバリデーションを行っていました。
そのバリデーションでNULLが許容されず、再度バリデーションに引っ掛かってまたGETリクエストが送られ、さらにまたindexメソッドへNULLで値が渡され...の無限ループに陥ってしまっています。
そのため、redirect(バリデーション失敗時のアクセス)が多すぎるよー!
って怒られているんです。
つまり、バリデーションをするメソッドAがあって、そのバリデーションが引っ掛かった場合に呼び出されるメソッドが同じくメソッドAになってしまう場合、無限ループになります。
そのため、バリデーションをするメソッドAがあり、そのバリデーションが引っ掛かった場合に呼び出されるメソッドを変えてあげれば言い訳ですね。
やっていきましょう!
indexメソッドはただwelcomeページにアクセスするだけのメソッドにします。
そして、バリデーションはvalidationメソッドを新しく定義し、そちらで行うようにしました。
どちらも呼び出すviewは'welcome'ページです。
public function index ()
{
return view('welcome');
}
public function validation(Request $request)
{
$request->validate([
'comment' => 'required|string|max:10',
]);
$comment = $request->comment;
return view('welcome')->with([
'comment' => $comment
]);
}
次に、ルートですが、postの際(フォームから入力された場合)は今まで通りvalidationメソッドが呼び出されるように、そしてリダイレクト(バリデーションに引っかかる)ではindexにアクセスするようにします。
Route::post('validation', 'techController@validation');
Route::get('validation', 'techController@index');
こうすることで、バリデーションに失敗した場合はバリデーションをせずにそのままwelcomeページを表示するようにできます。
では動作を確認してみましょう!
きっとできるはずです!
(さっきの最後がちゃんとエラー表示されるだけなので省略しまーす!)
余分な内容も書いてしまいめちゃくちゃ長くなってしまいましたが...
バリデーションでよく引っかかりがちなので、上記理解しておくと今後焦ることも減ると思います!
余談 : エラーメッセージってどうやって決まってるの?
さて、今回表示されていた"The comment may not be greater than 10 characters"っていうメッセージ、どこで決まってるの?って思いますよね。
実は"resources/lang/en/validation.php"というファイルで定義されています。
例えば先ほどのメッセージなら以下のmaxというバリデーションルールに引っ掛かった場合で、尚且つ型はstringだった場合です。
(maxはその値の型によっていい感じに解釈を変えてくれます。数値なら10以下、文字なら10文字以下等...)
'max' => [
'numeric' => 'The :attribute may not be greater than :max.',
'file' => 'The :attribute may not be greater than :max kilobytes.',
'string' => 'The :attribute may not be greater than :max characters.',
'array' => 'The :attribute may not have more than :max items.',
],
今回はstring型だったので、その警告文が出ているわけです。
ちなみに、":attribute"と":max"にはそれぞれルールを指定した値の名前(今回はcomment)とmaxで指定した値(今回は10)が置換されるようになっています。
そのため、
"The comment may not be greater than 10 characters."になるわけですね。
もちろん、この箇所を書き換えれば...?
'max' => [
'numeric' => 'The :attribute may not be greater than :max.',
'file' => 'The :attribute may not be greater than :max kilobytes.',
'string' => ':attribute は :max 文字以下で指定してください.',
'array' => 'The :attribute may not have more than :max items.',
],
このようにカスタマイズもできます。
ただ、一般的には"resources/lang/en/validation.php"は英語版のサービス用メッセージとして扱われます。(enはenglishの略)
そのため、別で"resources/lang/jp/validation.php"を作成してそちらで日本語版のメッセージを設定されることが当たり前になっています。
そうしておけば英語版サービスと日本語版サービスでの使い分けもやりやすいです。
ちなみに、このファイルはlaravelの全てのバリデーションで使用されます。
あるバリデーションメッセージに合わせて書き換えると、別の場所でのバリデーションメッセージも同じく変わってしまうので少し注意が必要です。
基本的にこのファイルは最初に決めたものから変更せず、フォームごとにこのメッセージを変えたいなら別途②で説明する手法を使うようにしたほうがいいでしょう。
②フォームリクエストを使ってバリデーションをする
さて、次に別のバリデーションの実装方法をみてみます。
本手法だと先ほどのバリデーションよりもより複雑なバリデーションを実装できます。
ではまず、バリデーションのルールを書くためのファイルを作成します。
以下のコマンドを実行してみてください。
php artisan make:request ValidationRequest
すると、"App/Http/Requests/ValidationRequest.php"が作成されます。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ValidationRequest 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 [
//
];
}
}
authorizeは認可と呼ばれるもので、そのユーザーがこのリクエストを通す権利を持っているかどうかを判定する際に使用します。(今回は省略)
"true"を返すことで常に許可することができるので、今回はtrueを返すようにします。
また、rulesは実際に先ほどの入力必須〜とか、10文字以下〜とかを指定する場所です。
ではそれぞれのメソッドを以下のように修正してください。
public function authorize()
{
return true;
}
public function rules()
{
return [
'comment' => 'required|string|max:10'
];
}
これで、バリデーションの準備はOKです次はコントローラーで呼び出すようにしましょう。
コントローラーの先ほど定義したvalidationメソッドを以下のように書き換えます。
public function validation(ValidationRequest $request)
{
$comment = $request->comment;
return view('welcome')->with([
'comment' => $comment
]);
}
そして、同じTechControllerの上部にて、useを追加して以下のようにします。
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests\ValidationRequest; // <-これを追加
class TechController extends Controller
端的にいうと先ほど作ったValidationRequestというバリデーションをvalidationメソッドの引数に渡ってきた値に対して適応するよ〜ってことをやってます。
useを使うことで、先ほど作ったValidationRequestを参照できるようにして、validationの引数として渡されてくる$requestの型宣言をValidationRequestにしてあげればいい感じにvalidationをやってくれるんです。
より細かいお話をすると長くなってしまうので省略します...
(気になるかたはDi, 依存性注入等で検索してみてください)
さて、この時点でもう動くようになっているはずです。
実際にお手元の画面でも試してみてください。
うまくできましたかね...?
ではでは、次はメッセージの内容を変えてみます。
実はこの手法では①の最後にお話しした"validation.php"を変えずとも自由なメッセージが作成できます。
早速やってみます。
先ほど作ったValidationRequestのrule()メソッドのすぐ後ろ辺りに以下を追加してください。
public function rules()
{
return [
'comment' => 'required|string|max:10'
];
}
public function messages()
{
return [
'comment.max' => 'commentの値がなにかおかしいよ〜!',
];
}
このmessagesメソッドを使うことで、本来"validation.php"で表示されるところを上書きしてメッセージを書き換えることができます。
指定の仕方なんですが、上記の書き方だと、commentのmaxルールに引っ掛かった場合、'commentの値がなにかおかしいよ〜!'と表示されるようにする。という意味になります。
stringの場合は
'comment.string' => 'commentが文字型じゃないよ〜!'
といった具合です。
複数してももちろん可能で、その場合は
public function messages()
{
return [
'comment.max' => 'commentの値がなにかおかしいよ〜!',
'comment.string' => 'commentが文字型じゃないよ〜!'
];
}
としておけば大丈夫です。
配列なので、最後の要素以外はちゃんとカンマで区切りを忘れないようにしましょう。
全てを文字列で指定していましたが、validation.phpと同様で、いい感じに置換してあげることも可能です。
'comment.max' => ':attribute の値が :max 超えてるよ〜!'
こうすればそれぞれ"comment", "10"が入ります。
この方法でValidationのルールを決めておけば、メソッドで指定するだけで再利用ができるので、汎用的なルールを作った場合、再利用がしやすいという利点もありますね。
まとめ
実はもう一つカスタムバリデーションと呼ばれる手法があるんですが...
ちょっと長くなりすぎている気がしますので、割愛します...orz
とりあえず今回解説した二つの手法を理解しておけば基本的なバリデーションは可能です。
特にリダイレクト周りはしっかりと理解しておかないとドツボにハマりがち(実際僕も昔ハマりました...)なので、この際に理解しておきましょう!
一応、カスタムバリデーションも知りたいんだ!
って方はこちらから...
(そのうちやる気が出たら僕も記事を書くつもりです...w)
それでは!