速習Larave3(その3)、バリディーションとCSRF対策

タグ: Laravel3  

今回は入力のバリデーションとCSRF対策を追加しましょう。

ソース:github

まずは追加フォームをご覧ください。application/views/add.blade.phpでしたね。

@layout('template')

@section('title')
    記事追加
@endsection

@section('content')
    {{ Form::open() }}
        <div>
            {{ Form::label('title', 'タイトル') }}<br>
            {{ Form::text('title', Input::old('title', '')) }}
            @if ($errors->has('title'))
                <p style="color: red;">{{ $errors->first('title') }}</p>
            @endif
        </div>
        <div>
            {{ Form::label('body', '本文') }}<br>
            {{ Form::textarea('body', Input::old('body', '')) }}
            @if ($errors->has('body'))
                <p style="color: red;">{{ $errors->first('body') }}</p>
            @endif
        </div>
        <div>
            {{ Form::submit('送信') }}
        </div>
    {{ Form::token() }}
    {{ Form::close() }}
@endsection

バリデーションを行い、引っかかった場合のエラーメッセージを表示する処理が追加されています。$errorsはバリデーションを実行することで、Validatorクラスが生成するMessageクラスのインスタンスです。has()メソッドで存在をチェックし、first()メソッドで、最初の一つだけを取り出します。もし、行いたければforeachで回して、その項目に発生したバリデーションエラーを全部表示することも可能です。

Tips!! @ifを使用せずとも、Messageクラスのfirst()は実際のメッセージを持たない場合、何も表示しません。Messageクラスは各メッセージをHTML要素を付け加え出力することもできます。お好みでお好きな手法を取れます。今回はビューで何を行なっているのかが明確なため、@ifを使用する方法を採用しています。

なお、この$errors変数は特別で、Laravelにより自動的にセッションから'errors'というキーを持つ保存値がビューにセットされます。いちいち開発者が自分でビューに渡す必要はありません。

さらに、Formクラスのtoken()を追加しています。これはCSRF対策として隠しフィールドにランダムに生成されたトークンを埋め込んでくれるものです。トークンはセッションに自動保存されます。この2つを比較すれば、送信されたばかりのフレッシュなフォームから入力が返ってきたのか判断がつくわけです。

では、処理を確認するためにroutes.phpをご覧ください。前回から変更するのはpost/addのPOSTメソッドに対するルートです。

// フォーム処理
Route::post('/post/add',
    array( 'as' => 'post-add', 'before' => 'csrf',
    function() {
        // バリディーションルール
        $rules = array(
            'title' => 'required|max:50',
            'body' => 'required|max:8000',
        );

        $input = Input::only(array( 'title', 'body' ));

        $val = Validator::make($input, $rules);

        if ( $val->passes() )
        {
            // バリデーション通過

            // 新ポストの追加
            $post = new Post($input);
            $post->save();

            // 更に追加の可能性があるため、
            // フォームは空のままリダイレクト
            return Redirect::to_route('post-add');
        }
        else
        {
            // バリデーション失敗

            // バリディーションで生成されたエラーと
            // フォームに入力された内容を
            // セッションのフラッシュデーターを通じ
            // フォームに送る
            return Redirect::to_route('post-add')
                ->with_errors($val->errors)
                ->with_input();
        }
    } ));

CSRF対策に入れたコードは'before' => 'csrf',の部分だけです。Laravelではルート実行の前後にフィルターを指定できます。ルート前にcsrfフィルターをチェックするように指定しました。

このフィルターの実装もroutes.phpの中で定義されています。

Route::filter('csrf', function() {
        if ( Request::forged() )
            return Response::error('500');
    });

ビューの所で説明したCSRFトークンを比較しています。もし異なっていれば不正なフォームからの送信だと判断します。デフォルトでは500、つまり内部エラーとして表示されます。これもお好きなように変更できます。

さて、route.phpに戻りましょう。バリディーションのコードも確認してください。ルールを定義し、バリディーションのインスタンスを生成し、実行した結果で、処理を分けています。どのフレームワークでも同じような処理になりますね。Laravelの場合、エラーであることをチェックするfails()だけでなく、逆のpasses()が用意されています。(無駄?ええ、でも皆さん各自のコーディングの好みに合わせるためですよ。)

説明のため、コメントを入れてあり長いですが、コメントを外しても、そのまま読めます。それがLaravelの可読性の良さです。

さて、実行してみましょう。ちゃんとエラーになることを確認してください。

あれ、残念ながらメッセージが英語ですね。それは、Laravelの言語設定がデフォルトの英語のままだからです。application/config/application.phpを開き、languagejaに変更してください。メッセージが日本語に変わります。複数形のlanguagesではありませんよ。注意してください。

エラーメッセージ中に表示される項目名も日本語にしたい場合はapplication/language/ja/validation.phpattributesアイテムに指定します。

    'attributes' => array(
        'title' => 'タイトル',
        'body' => '本文',
    ),

これで全部日本語で表示されます。