larave5.2 バリデーション

こういうところがモダンなフレームワークの便利なところですね。

コントローラー

<?php

namespace App\Http\Controllers;

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


class FooController extends Controller
{
    //
    public function index(Request $request) // (1)
    {

        return view('foo');
    }

    public function store(Request $request) // (2)
    {

        // バリデーション
        $this->validate($request, [
            'name' => 'required|unique:posts|max:255',
            'age' => 'required',
        ]);

    }
}

(1)はフォームを表示するだけ。submitでPOSTされたら(2)にくるようにルーティング。

ルート

<?php
Route::group(['$middlewareGroups' => 'web'], function () {
    Route::get('/', 'FooController@index');
    Route::post('/', 'FooController@store');
});

フォームはまぁこんな感じ。CSRFチェックを自動でやってくれるので、そのフィールドを追加するのを忘れずに。

ビュー

<html>
<body>
<form method="POST" action="" accept-charset="UTF-8">
    {{ csrf_field() }}
    お名前:<input name="name" type="text" value="{{ old('name') }}"><br>
    年齢:<input name="age" type="text" value="{{ old('age') }}"><br>
    <input type="submit" value="送信">
</form>
</body>
</html>

この中の old('')ってのが優秀。バリデートに失敗した時の入力値をセッションに自動でセッションに保存してあるのを表示してくれる。

エラーメッセージ

バリデートに失敗した場合、エラーメッセージが生成されてこれもセッションへ保存される。 それがあれば出す、みたいな記述をビューにするだけ。

ビュー

<html>
<body>
@if ($errors->any())
    <div>
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif
<form method="POST" action="http://example.com" accept-charset="UTF-8">
    {{ csrf_field() }}
    お名前:<input name="name" type="text" value="{{ old('name') }}"><br>
    年齢:<input name="age" type="text" value="{{ old('age') }}"><br>
    <input type="submit" value="送信">
</form>
</body>
</html>

メッセージは resources/lang/ ... /validation.php に記述されている。 日本語化は

GitHub - laravel-ja/comja5: コメント日本語変換

これをcomposerでいれて実行すればいい。

フィールド名が英語のままなんだけど

これでも十分なんだけど、このままだと

nameは入力必須です。

的なメッセージになってしまう。 このnameはinputタグのname属性の値みたい。

変えるには、先ほどの valodation.php に attribute を追加。

<?php

    'attributes' => [
        'name' => 'お名前',
        'age' => '年齢',
    ],

これで

お名前は入力必須です。

になる。

コントローラでバリデート書くのなんかイヤ

僕もそのタイプ。そんときは、Requestを継承したオリジナルRequestを生成してそこに書けばいい。それでコントローラに来た時点でバリデートが終わっている。

例のコマンドで生成。

php artisan make:request FooRequest
<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class FooRequest extends Request
{
    /**
     * 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()
    {
        // ここにValidationルールを記載
        return [
            'name' => 'required',
            'age' => 'required',
        ];
    }

}

rules()メソッドにバリデーションルールを書く。Controllerには不要になる。 ちなみに、ここにエラーメッセージやattribute書くこともできる。

修正後のコントローラ

<?php

namespace App\Http\Controllers;

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


class FooController extends Controller
{
    //
    public function index(Request $request)
    {

        return view('foo');
    }


    public function store(FooRequest $request) {

        // バリデート済みしかこない

    }
}

CSRFは?

エラーメッセージでない!Exceptionが投げられます。 app/Exceptions/Handler.php が例外ハンドリングなので、そこで補足して、エラーメッセージにする、みたいなことをしなきゃならない。

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    /**
     * レポートしない例外タイプのリスト
     *
     * @var array
     */
    protected $dontReport = [
        AuthorizationException::class,
        HttpException::class,
        ModelNotFoundException::class,
        ValidationException::class,
    ];

    /**
     * 例外をレポート、もしくはログ
     *
     * ここはSentryやBugsnagなどに例外を送るために良い場所
     *
     * @param  \Exception  $e
     * @return void
     */
    public function report(Exception $e)
    {
        parent::report($e);
    }

    /**
     * HTTPレスポンスに対応する例外をレンダー
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $e)
    {
         if ($e instanceof TokenMismatchException){
            return redirect()->back()->with('csrf_error','ページの有効期限が切れました');
        }
        return parent::render($request, $e);
    }
}

そもそも、ここは本番環境にあげるまえに修正がいろいろ必要。 このままだと.envのDEBUGをfalseにしてもデバッグトレースが消えるだけで、symphony のエラーメッセージがでるw

APIからのCSRFやそもそもCSRFを止める方法はネットにたくさんありました。

カスタム、フック

基本は上記でだいたいなんとかなりそうだけど、なんか他にもいろいろできるっぽい。 詳しくは

バリデーション 5.1 Laravel