速習Larave3(その12)、役割の導入No.2

タグ: Laravel3  

前回は、役割を認証に導入する準備を整えました。今回は続いて実際の処理を入れて行きましょう。

前記事:速習Larave3(その12)、役割の導入No.1

まずはRoleモデルです。

<?php

class Role
{
    const GUEST = 0; // ゲスト
    const REGISTER = 1; // 登録者
    const AUTHOR = 10;  //投稿者
    const ADMINISTRATOR = 100;  // 管理者

    public static function check_register()
    {
        return Auth::check() && Auth::user()->role >= self::REGISTER;
    }

    public static function check_author()
    {
        return Auth::check() && Auth::user()->role >= self::AUTHOR;
    }

    public static function check_administrator()
    {
        return Auth::check() && Auth::user()->role >= self::ADMINISTRATOR;
    }

}

役割に関わるところはこのクラスに閉じ込めます。本当はこのクラスを作らず、コーディングし、動作確認した後に見なおしたところ、コードが汚いなあと思い、やはりまとめたほうが良いねという神の声が聞こえたので、クラスに閉じ込めたのです。

check_...Auth::checkにあわせて、この名前にしました。それぞれの名前が示す役割以上の権限を持っている場合、trueが返ります。正直、初めのAuth::check() &&は要らないと言えば、要らないのです。Auth::user()は存在しないプロパティーを指定された場合、nullを返します。ログインしていないゲストの場合、roleは存在していません。ですから、nullになります。nullは右辺の定数より大きな数字ではないので、成り立ちません。ですから、ゲストの場合、いつもfalseになるため、左のAuth::check()は無くても構いません。

なぜ書いたのかと言えば、やっていることを明確にするためです。ログインをチェックしていると、読んで分かるからです。もうひとつの理由は、Auth::user()に存在しないプロパティーを指定した場合の値がいつもnullになるというのが、ある時突然変更になっても動作するようです。(まあ、Laravel4の開発も進んでいるので、Laravel3の仕様が大きく変化するとは考えづらいのですけどね。)

続いて、ルートの定義をしましょう。routes.phpを開いて、ルートの定義の部分を以下のとおりに変更してください。

/*
 * ルーティング定義
 */

// トップページ:一覧表示
Route::get('/', array( 'as' => 'post-index', 'uses' => 'post@index' ));

// 記事表示
Route::get('/post/(:num)', array( 'as' => 'post-show', 'uses' => 'post@show' ));

// ログイン
Route::get('/login', array( 'as' => 'login', 'uses' => 'auth@login' ));
Route::post('/login', array( 'as' => 'login', 'before' => 'csrf', 'uses' => 'auth@login' ));

// ログアウト
Route::get('/logout', array( 'as' => 'logout', 'uses' => 'auth@logout' ));

// 登録者権限が必要な領域
Route::group(array( 'before' => 'register' ), function() {
    // 現状なし
    });

// 投稿者権限が必要な管理者領域
Route::group(array( 'before' => 'author' ),
    function() {
        // 記事追加/編集
        //  フォーム表示
        Route::get('/post/edit/(:num?)', array( 'as' => 'post-edit', 'uses' => 'post@edit' ));
        //  フォーム処理
        Route::post('/post/edit/(:num?)', array( 'as' => 'post-edit', 'before' => 'csrf', 'uses' => 'post@edit' ));

    });

// 管理者権限が必要な管理者領域
Route::group(array( 'before' => 'administrator' ),
    function() {
         // 記事削除
        //  フォーム表示
        Route::get('/post/delete/(:num)', array( 'as' => 'post-delete', 'uses' => 'post@delete' ));
        //  フォーム処理
        Route::post('/post/delete/(:num)', array( 'as' => 'post-delete', 'before' => 'csrf', 'uses' => 'post@delete' ));
    });

あれれ、registerとか、authoradministratorなんて、見慣れないフィルターが増えています。

その通りです。フィルターを追加しました。

Route::filter('register',
    function() {
        if ( ! Role::check_register() )
        {
            return Redirect::to_route('login')
                    ->with('message', 'ログインが必要です。');
        }
    });

Route::filter('author',
    function() {
        if ( ! Role::check_author() )
        {
            return Redirect::to_route('login')
                    ->with('message', 'ログインが必要です。');
        }
    });

Route::filter('administrator',
    function() {
        if ( ! Role::check_administrator() )
        {
            return Redirect::to_route('login')
                    ->with('message', 'ログインが必要です。');
        }
    });

Laravelのルーティングの強力なところは、このように自由にフィルターを定義できることです。自由に定義し、自由に組み合わせることができます。

さらっと書きましたが、とても使いやすく、便利です。このフィルターがあるため、大抵のルートの制限は簡単に既述できます。もちろん、複雑にしたければ、認証ドライバーを作成することもできますし、ルートをDBから読み込んで動的に定義することも可能です。なにせRouteクラスで登録するだけですからね。

さて、動作テストの前に、index.blade.phpも修正しておきましょう。今度は役割に合わせて、できる、できないが変わりました。それに従い、役割に応じて使用できるリンクは表示し、できないリンクは初めから表示しない方針を貫きましょう。

@layout('template')

@section('title')
一覧表示
@endsection

@section('content')
<style>
    .pagination li {
        display: inline;
        margin: 0 5px;
    }
</style>
<table>
    <tr>
        <th>タイトル</th>
        <th>作成日</th>
        @if ( Role::check_author() )
            <th>更新</th>
        @endif
        @if ( Role::check_administrator() )
            <th>削除</th>
        @endif
    </tr>
    @foreach($posts as $post)
    <tr>
        <td>{{ HTML::link_to_route('post-show', $post->title ,array($post->id)) }}</td>
        <td>{{ $post->created_at }}</td>
        @if( Role::check_author() )
            <td>{{ HTML::link_to_route('post-edit', '✜', array($post->id)) }}</td>
        @endif
        @if ( Role::check_administrator() )
            <td>{{ HTML::link_to_route('post-delete', '✖', array($post->id)) }}</td>
        @endif
    </tr>
    @endforeach
</table>
{{ $link }}
@endsection

もうひとつ、レイアウトも一部変更です。template.blade.phpでしたね。

                @section('sidenavi')
                    <li>{{ HTML::link_to_route('post-index', 'ホーム') }}</li>
                    @if ( Auth::guest() )
                        <li>{{ HTML::link_to_route('login','ログイン') }}</li>
                    @else
                        @if ( Role::check_author() )
                            <li>{{ HTML::link_to_route('post-edit', '記事追加') }}</li>
                        @endif
                        <li>{{ HTML::link_to_route('logout', 'ログアウト') }}</li>
                    @endif
                @yield_section

では、動作テストしましょう。ユーザーは3つ用意しました。adminauthregです。

regでは未ログイン時と変わりません。

authでは、記事の追加/更新だけができます。

adminでは、記事削除もできます。

最後に、皆さんに宿題です。ユーザーの追加、変更、削除、一覧表示などを組み込んでください。できる方だけで結構です。これまで行なってきたことが身に付いているか、自分自身でチェックしてください。(私はコーディングしませんから、ソースには入っていませんよ。きっと皆さん、私より上手く作ることでしょう。)

ユーザーに対する操作は、管理者のみができるようにしましょうね。