Laravel4、メッセージの表示

タグ: Laravel4  

バリデーションのメッセージは、対象の入力項目の近くで表示したほうが親切ですね。しかし、通常のシステムならば、バリデーション以外のメッセージも表示したいですね。

このサイトをLaravel3の頃からご覧の方は、もうお馴染みになっていると思いますが、ビューコンポーサーとレイアウトのネストを利用して、実現する方法を紹介します。(Laravel4のドキュメントでは、レイアウトは、Laravel3での共通テンプレートという意味合いより、Bladeテンプレートで表したビューという意味合いのようです。共通テンプレート的な表現は使わず、レイアウトの親子関係として、表現されています。しかし、基本的な考えは、Laravel3より、変更されていません。)

レイアウトのネスト

LaravelのBlade(ブレード)テンプレートは、親子関係を持ちます。

通常、Webページは各ページ共通のデザインを持ちます。その共通部分を、親のレイアウトとしてまとめ、各ページごとに異なる部分を子供のレイアウトとして持ちます。

今回は、単純なビューをBladeテンプレートで、表してみます。まずは、全ページ共通で使用するテンプレートの役割を持つ親のレイアウト、app/views/baseView.blade.phpファイルです。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>@yield('title')</title>
</head>
<body>
    <h1>@yield('title')</h1>
    <div id="messages">
        @if ( $message )
            <p style="color:green;">{{ $message }}</p>
        @endif
        @if ( $caution )
            <p style="color:red;">{{ $caution }}</p>
        @endif
        @if ( $warning )
            <p style="color:blue;">{{ $warning }}</p>
        @endif
    </div>
    @yield('content')
</body>
</html>

ページ間で、いつも同じになるだろう、共通部分を抜き出した構成です。メッセージの出力コードも入れています。

これを利用する、子のレイアウト、app/views/sampleForm.blade.phpファイルをご覧ください。

@extends('baseView')

@section('title')
  サンプルフォーム
@stop

@section('content')
    {{ Form::open() }}
    {{ Form::label('age', '年齢:') }}
    {{ Form::text('age', Input::old('age', '')) }}
    @if ( $errors->has('age') )
        <p style="color:red;">{{ $errors->first('age') }}</p>
    @endif
    <br>
    {{ Form::submit('送信') }}
    {{ Form::close() }}
@stop

年齢を入力するだけのサンプルフォームです。

子供のレイアウトでは、まず最初に@extendsで、親のレイアウトを指定しています。

残りの部分はセクションごとに区切られています。ページタイトルとして"title"、ページの内容として"content"の二つを使用しています。

親のレイアウトでは、@yieldで対応するセクションをその場所へ取り込みます。

さて、親のレイアウトを直接表示するのは意味がありません。ですからもちろん、表示するために指定するのは、子のレイアウトになります。

ルートの定義やコントローラーのアクションメソッド中から、return View::make('sampleForm')すると、サンプルフォームが表示されます。

親のレイアウトをよく見てもらうと、message、caution、warningの三種類のメッセージを表示できるようにしています。

もし単純に渡すのであれば:

return View::make('sampleForm')
    ->with('message', 'これはメッセージです。');

この様に、必要なメッセージを渡すことになります。しかし…これですと、使用していないcautionとwarningが未定義のため、ビューを表示するときに例外が投げられることになります。

改造するには、親のレイアウトでisset()を使用してチェックすることができます。

しかし、まあ、ことはそう単純でありません。この3つのメッセージは直接ビューに指定する場合は少なく、実際はフォームを処理するルートから、セッション経由で、渡される方が多いでしょう。

ルート定義

ルートを紹介します。シンプルにするために、コントローラーは使用せず、routes.phpへ記述しています。

<?php

Route::get( '/', function()
    {
        return View::make( 'home' );
    } );

Route::get( 'sample', function()
    {
        return View::make( 'sampleForm' );
    } );
Route::post( 'sample', function()
    {
        $rules = array(
            'age' => array(
                'required',
                'integer',
                'between:18,100',
            ),
        );

        $inputs = Input::only( 'age' );
        $val = Validator::make( $inputs, $rules );

        if( $val->passes() )
        {
            return Redirect::to( '/' )
                ->with( 'message', 'OKでーす。' );
        }

        return Redirect::back()
                ->with( 'warning', '入力を確認してください。' )
                ->withErrors( $val )
                ->withInput();
    } );

"sample"ルートに対する、POST処理で、フォームを処理し、結果によりメッセージを使い分けています。

Laravelではリダイレクトのクラスに対し、withメソッドを使うと、次のセッション期間の間だけ有効なフラッシュデーターとして、指定された内容がセッションに保存されます。ほぼ、メッセージ用の機能です。

"sample"ルートのGETメソッドのルートでフォームを表示しています。単にリターンしています。

ここに、先程の3メッセージの処理を書こうとすると:

return View::make('sampleForm')
          ->with('message', Session::get('message', false)
          ->with('caution', Session::get('caution', false)
          ->with('warning', Session::get('warning', false);

これでよし。いや。良くないですね。このチュートリアルでは、ルートを"sample"しか使っていませんが、ルートごと、そしてレイアウトごとに、同じようなメッセージの処理を書く必要が出てきます。1ページ構成のアプリならともかく、URIルーティングやその他の条件により、別の子レイアウトを使用する場合、それら全部にいちいちこのメッセージを渡さなくなりません。レイアウトの中には表示する内容を別に渡す必要があるものもあるはずです。

そんな手間は、たまったものではありません。

ビューコンポーサー

Laravelでは、特定のビューに対して、表示内容を準備するためのコードを指定できます。

どのレイアウトであろうと、この機能を利用できます。Bladeテンプレートだろうと、PHPあテンプレートであろうと、使用できます。これを親レイアウトに対し、使用すれば簡単に準備ができます。

場所はroutes.phpでも良いのですが、このファイルは原則ルート定義専用にしましょう。理想的には、Laravel4からサービスプロバイダーという機能が用意され、準備のためのコードは、このクラスを利用して記述するのが標準となりました。

ただ、機能をちょっと試すには、準備の手間が大変すぎます。

そんな時は、start/global.phpファイルを使用しましょう。このファイルは、リクエストの処理前に実行が必要な、準備コードを書くためのものです。とはいえ、準備を全部このファイルにまとめてしまうと、それもごちゃごちゃになるため、機能単位で準備を分類するために、サービスプロバイダーがLaravel4では提供されています。今回は、チュートリアルのために、コンポーサー一つ指定するだけですから、遠慮無く使用しましょう。

では、start/global.phpを開き、一番最後に以下のコードを追加してください。

View::composer('baseView', function($view){
    $view->message = isset($view->message) ? $view->message : Session::get('message', false);
    $view->caution = isset($view->caution) ? $view->caution : Session::get('caution', false);
    $view->warning = isset($view->warning) ? $view->warning : Session::get('warning', false);
});

ビューを指定し、実際の準備はクロージャー内で行なっています。テスタビリティーを上げるため、Laravel4からは、独立したクラスにすることもできるようになりました。

子レイアウトから、親のbaseViewが呼び出されると、このビューコンポーサーが、最初に起動されます。指定されたコードにより、プロパティーがセットされます。ビューのオブジェクトのプロパティーは、ビューから変数としてアクセスできます。そのため、ここでセットしているmessage、caution、warningプロパティは、いつもビューから$message、$caution、$warningとしてアクセス可能になります。

もちろん、テーブルやファイルにアクセスして、必要な情報を取得、整形、加工して、最終的にビューに渡す処理も記述できます。コントローラーから、こうしたロジックを分離する一手として利用もできます。

さらに、同じビューに対し、ビューコンポーサーはいくつも指定できますから、ロジックを更に細かく分けることも可能です。

ルートやコントローラーのアクションメソッドなどの中で、ビューに対して渡す情報をwithメソッドで指定すると(他にもビューに値を渡す方法はありますが、同様です)、それはビューのプロパティーとして扱えます。もし存在するならば、そのメッセージをそのままセットし直します。

プロパティーとして存在しない、つまりビューに対してwithで指定されていない場合は、セッションから取得を試みます。存在している場合は、リダイレクトに対して、withメソッドが指定されています。その内容をビューのプロパティとして設定し、存在しない場合はfalseを返します。

親のレイアウトに戻りましょう。各メッセージを表す変数がfalse以外の場合は、メッセージを表示します。falseの場合は表示しません。まあ、withで数字の0を渡す人もいるかもしれませんね。その場合は、意図通りに動かないかも知れません。しかし、通常はメッセージとして文字列が渡されますから、問題は無いでしょう。

もちろん、もっと厳密にコーディングすることも可能です。今回は、レイアウトのネストとビューコンポーサーの説明のため、分かりやすさ重視のサンプルコードでした。

実際は、皆さんのご希望に添って、コーディングしてください。

ホームのレイアウト

ルートページで表示するapp/views/home.blade.phpファイルを紹介します。まあ、適当で良いとは思います。

@extends('baseView')

@section('title')
    ホーム
@stop

@section('content')
    いらっしゃいませ。ルートページです。
@stop

本格的に使用するなら

もし、本格的に使用するならば、メッセージをメッセージバッククラスで扱うように改良すると良いかも知れません。

現状では、メッセージは各種類ごとに一つしか指定できません。同じ種類のメッセージを複数、表示できるようにしたほうが実用性は高くなるでしょう。メッセージバックはメッセージの集合体を扱うクラスです。このクラスを使用するも良し、独自に作成するも良し。たかが、メッセージにこだわっていられねえやと、このまま使用するも良し。

まあ、親子レイアウトとビューコンポーサーの使い方を覚えてもらえば、チュートリアルの目的は達成できました。