Laravel4、ロールと許可ベースのルート制御(2)

タグ: Laravel4  

この記事は以下の続きです。

テーブルの初期値設定

テーブルに初期値を設定しましょう。

テーブルへの初期値設定はシーディング(seeding)と呼ばれています。そのためのクラスファイルは、app/database/seeds下に設置します。

以降のシード(seed)クラスは、クラス名.phpファイルとして保存してください。

最初にusersテーブルの初期値です。

<?php

class UsersTableSeeder extends Seeder
{

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Eloquent::unguard();

        DB::table( 'users' )->delete();

        // スーパーユーザー
        User::create( array(
            'username' => 'super-user',
            'password' => 'super-man',
            'email' => 'super-user@role.tuto'
        ) );

        // 管理者
        User::create( array(
            'username' => 'admin-user',
            'password' => 'admin-man',
            'email' => 'admin-user@role.tuto'
        ) );

        // モデレーター
        User::create( array(
            'username' => 'mod-user',
            'password' => 'mod-man',
            'email' => 'mod-user@role.tuto'
        ) );

        // 一般ユーザー
        User::create( array(
            'username' => 'normal-user',
            'password' => 'normal-man',
            'email' => 'normal-user@role.tuto'
        ) );

        // 利用禁止ユーザー
        User::create( array(
            'username' => 'ban-user',
            'password' => 'ban-man',
            'email' => 'ban-user@role.tuto'
        ) );
    }

}

シーディングにEloquentを使用する場合は、最初にEloquent::unguard();を宣言しましょう。Eloquentはセキュリティーを向上させるため、Eloquentモデルのフィールドに対する複数代入に制限を付けられるようになっています。

こうした複数代入への制限は、初期値を設定する場合は邪魔になりますので、それを無効にするように宣言します。

Userモデルを利用し、ユーザーを作成しています。Eloquentの通常のinsert方法です。

続いて、rolesテーブルの初期値です。

<?php

class RolesTableSeeder extends Seeder
{

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Eloquent::unguard();

        DB::table( 'roles' )->delete();

        Role::create( array( 'rolename' => 'super' ) );
        Role::create( array( 'rolename' => 'システム管理者' ) );
        Role::create( array( 'rolename' => 'モデレーター' ) );
        Role::create( array( 'rolename' => '一般ユーザー' ) );
        Role::create( array( 'rolename' => 'ban' ) );
    }

}

これも、難しくありません。一件ずつ、レコードを追加しています。

permissionsテーブルの初期値設定も行いましょう。

<?php

class PermissionsTableSeeder extends Seeder
{

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Eloquent::unguard();

        DB::table( 'permissions' )->delete();

        Permission::create( array( 'allow' => 'super' ) );
        Permission::create( array( 'allow' => '管理者エリア読み書き' ) );
        Permission::create( array( 'allow' => '記事修正' ) );
        Permission::create( array( 'allow' => '記事投稿' ) );
        Permission::create( array( 'allow' => '記事削除' ) );
        Permission::create( array( 'allow' => '自投稿編集' ) );
        Permission::create( array( 'allow' => '自投稿削除' ) );
        Permission::create( array( 'allow' => 'ban' ) );
    }

}

前の2テーブルと同様に、一件ずつレコードを追加しています。許可を分かりやすいように日本語名にしています。

これで、主要な3テーブルの初期値設定クラスが用意できました。続いて、中間テーブルの設定です。

role_userテーブルの設定です。

<?php

class RoleUserTableSeeder extends Seeder
{

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Eloquent::unguard();

        DB::table( 'role_user' )->delete();

        $user = User::whereUsername( 'super-user' )->first();

        $role = Role::whereRolename( 'super' )->first();
        $user->roles()->attach( $role->id );


        $user = User::whereUsername( 'admin-user' )->first();

        $roles = Role::whereIn( 'rolename', array( 'システム管理者', '一般ユーザー' ) )
            ->lists( 'id' );
        $user->roles()->sync( $roles );


        $user = User::whereUsername( 'mod-user' )->first();

        $roles = Role::whereIn( 'rolename', array( 'モデレーター', '一般ユーザー' ) )
            ->lists( 'id' );
        $user->roles()->sync( $roles );


        $user = User::whereUsername( 'normal-user' )->first();

        $role = Role::whereRolename( '一般ユーザー' )->first();
        $user->roles()->attach( $role->id );


        $user = User::whereUsername( 'ban-user' )->first();

        $roles = Role::whereIn( 'rolename', array( '一般ユーザー', 'ban' ) )
            ->lists( 'id' );
        $user->roles()->sync( $roles );
    }

}

このコードは、多少テクニックが含まれています。

Laravel4のドキュメントから抜け落ちているようですが、Laravel3に存在した動的whereが、バージョン4でも使用できます。

コード中、以下のコードに注目しましょう。

$user = User::whereUsername( 'super-user' )->first();

APIを見ようと、コードを探そうとwhereUsernameは見つかりません。whereで始まるが、クエリービルダーやEloqeuntに用意されていないメソッド名の場合、それ以降の大文字で始まる名前はフィールド名とみなされ、そのフィールド名の値と一致するレコードを探します。つまり、以下のコードと同じ働きをします。

$user = User::where('username', '=', 'super-user')->first();

もしくは、

$user = User::where('username', 'super-user')->first();

個人的には、読みやすさは同じようなものだと思います。しかし、動的whereはタイプを減らせるため、私は頻繁に使用しています。

ただし、コード中のwhereInメソッドはクエリービルダーのメソッド名です。

中間テーブルの更新を見て行きましょう。

$userIdが示す、あるユーザーに対し、新規のロールを作成し、さらに中間テーブルにも登録する場合は、以下のように書けます。

$user = User::find($userId);

$user->roles()->save(new Role('rolename'=>'新規役割名'));

ユーザのroles()はRoleに対する「多対多」関係を表しています。ですから、roles()に対して新しいRoleモデルを保存(save)すれば、Role自身も保存され、さらに中間テーブルに両者を紐付けるため、両方のIDがペアで保存されるわけです。

これは便利ですが、使い所が難しいですね。通常ユーザーと役割は別々に登録されるパターンが多いでしょう。そして、毎回新しい役割を作成し、関係づけるシナリオよりも、既に存在している役割を指定することのほうが多くなることでしょう。

今回の中間コードのシーディングも、既に作成していたテーブルのレコード値をもとに、関係つけています。

$user = User::whereUsername( 'super-user' )->first();

$role = Role::whereRolename( 'super' )->first();
$user->roles()->attach( $role->id );

$userと$roleに紐つけたいモデルをセレクトしています。first()が示す通り、変数の中にはモデル一件分のオブジェクトが代入されています。

つまり、今、$userはユーザー名に'super-user'を持つユーザーを表しています。同様に、$roleは役割名'super'を持つ役割を表しています。

中間テーブルへレコードを一件、つまり多対多野関係を一つ登録するには、attachを使用します。

$user->roles()->attach( $role->id );

これは、あるユーザーのrolesリレーションに、$roleのidを追加するという意味になります。つまり、中間テーブルに$userのidと$roleのidが登録されます。

逆に$roleを起点にして、$userのidを登録しても、同じ結果になります。

$role->users()->attach($user->id);

中間テーブルに登録するのは、モデル自身ではなく、モデルのidであると考えれば、attachに渡す引数は、登録したいidであるのが、概念的に理解できるかと思います。

今回は一つの関係を登録しました。では、複数登録する場合は、どうしましょうか。あるユーザーに対して複数の役割を登録したい場合は、どのようにすれば良いのでしょう。

$user = User::whereUsername( 'admin-user' )->first();

$roles = Role::whereIn( 'rolename', array( 'システム管理者', '一般ユーザー' ) )
    ->lists( 'id' );
$user->roles()->sync( $roles );

これは管理者ユーザー(admin-user)へ役割を付与しています。ご覧の通り、2つの役割をつけています。

whereInメソッドは、SQLのWHERE INです。配列に渡された値に一致するレコードをセレクトするために使用します。通常、レコードのセレクト結果が複数の場合、getメソッドで受けます。しかし、今回はlistsメソッドを使用しています。

listsメソッドは、指定したフィールドの値を配列で受け取るために使用します。今回は、2レコードをセレクトし、そのidだけを配列で受け取っています。

続いて中間テーブルを書き込んでいます。attachメソッドの代わりに、syncメソッドを使用しています。attachメソッドは、中間テーブルへ追加するメソッドです。syncはパラメーターに指定された配列の値をidとして、テーブルの登録内容をその配列のid通りにするメソッドです。配列中に存在するが中間テーブルに存在していなければ追加されます。逆に、配列に存在していないのに、中間テーブル中に存在していれば、削除されます。結果、syncメソッドに渡した配列と同じ状態になります。

この、syncのほうが、使い勝手は良いかと思います。

最後に、permission_roleテーブルをご覧ください。

<?php

class PermissionRoleTableSeeder extends Seeder
{

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Eloquent::unguard();

        DB::table( 'permission_role' )->delete();

        $role = Role::whereRolename( 'super' )->first();
        $permission = Permission::whereAllow( 'super' )->first();
        $role->permissions()->attach( $permission->id );

        $role = Role::whereRolename( 'システム管理者' )->first();
        $permission = Permission::whereAllow( '管理者エリア読み書き' )->first();
        $role->permissions()->attach( $permission->id );

        $role = Role::whereRolename( 'モデレーター' )->first();
        $permissions = Permission::whereIn( 'allow', array( '記事修正', '記事削除' ) )->lists( 'id' );
        $role->permissions()->sync( $permissions );

        $role = Role::whereRolename( '一般ユーザー' )->first();
        $permissions = Permission::whereIn( 'allow', array( '記事投稿', '自投稿編集', '自投稿削除' ) )->lists( 'id' );
        $role->permissions()->sync( $permissions );

        $role = Role::whereRolename( 'ban' )->first();
        $permission = Permission::whereAllow( 'ban' )->first();
        $role->permissions()->attach( $permission->id );
    }

}

やっていることは、role_userテーブルの時と同じです。

さて、これらのシーディングを一つ一つ指定しながら行なっても良いのですが、面倒ですね。Laravelインストール時に存在している、DatabaseSeederクラスを編集しましょう。

<?php

class DatabaseSeeder extends Seeder {

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Eloquent::unguard();

        $this->call('UsersTableSeeder');
        $this->call('RolesTableSeeder');
        $this->call('PermissionsTableSeeder');
        $this->call('RoleUserTableSeeder');
        $this->call('PermissionRoleTableSeeder');
    }

}

artisan db:seedを引数なしで実行する場合、デフォルトとして、このクラスのrunメソッドが実行されます。callメソッドで、他のメソッドを呼び出しています。

呼び出す順番に気をつけてください。中間テーブルは主要な3つのテーブルが存在していることを前提にEloquentを利用して設定しているため、終わりの方で実行しています。

では、実際にシーディングを行いましょう。

php artisan db:seed

以下に続く