LoginSignup
8
15

More than 3 years have passed since last update.

Laravel 5.7 + laravel-permission + laravel-adminで、ユーザー編集画面で権限も編集可能にする

Last updated at Posted at 2018-12-21

「laravel-adminで、追加パラメータのあるピボットテーブルをコーディングなしで保存する。」
「assign()の第2パラメータなしでピボットテーブルの追加パラメータを扱う」
「モデルにbelongsToManyピボットテーブルの追加パラメータを指定して保存まで自動化する」
といったタイトルでもいいですね。

※ここで出てくる「ユーザー」は、管理画面ではなくフロント側のユーザーです。

Laravel 5.7を導入して、laravel-permission + laravel-adminで快適開発!と思ってたら、ユーザーのRoleとPermission編集画面作成でハマった話。日本語でも英語でもあまり情報がなかったのでコツだけ紹介。

普通に作るとエラーが出る。

この記事などを参考にしながら進めてみる。
laravel-permission + laravel-adminを導入して、ユーザーモデルのUser.phplaravel-permissionを割り当て。さらにrole()permission()を実装して、

app/User.php
...
    use HasRoles;
...
    public function role()
    {
        return $this->belongsToMany('Spatie\Permission\Models\Role', 'model_has_roles', 'model_id', 'role_id');
    }

    public function permission()
    {
        return $this->belongsToMany('Spatie\Permission\Models\Permission', 'model_has_permissions', 'model_id', 'permission_id');
    }
...

※もしこちらの記事でマルチテナント化してる場合は以下のようにモデルを変更してください。

app/User.php
    public function role(){
        return $this->belongsToMany('App\Role', 'model_has_roles', 'model_id', 'role_id');
    }

    public function permission(){
        return $this->belongsToMany('App\Permission', 'model_has_permissions', 'model_id', 'permission_id');
    }

app/Admin/Controllers/UserController.phpをコマンドで作って、grid()にユーザーのRoleとPermissionが表示されるように、form()にユーザーのRoleとPermissionが編集できるように記述。

php artisan admin:make UserController --model=App\User
app/Admin/Controllers/UserController.php
...
    protected function grid()
    {
        $grid = new Grid(new User);
...
        $grid->role()->display(function ($role) {
            $role = array_map(function ($role) {
                return "<span class='label label-success'>{$role['name']}</span>";
            }, $role);
            return join('&nbsp;', $role);
        });
        $grid->permission()->display(function ($permission) {
            $permission = array_map(function ($permission) {
                return "<span class='label label-success'>{$permission['name']}</span>";
            }, $permission);
            return join('&nbsp;', $permission);
        });
...
        return $grid;
    }
...
    protected function form()
    {
        $roles = Role::pluck('name', 'id');
        $permissions = Permission::pluck('name', 'id');

        $form = new Form(new User);
...
        $form->multipleSelect('role')->options($roles);
        $form->multipleSelect('permission')->options($permissions);
...
        return $form;
    }
}

あとはRoute通して、メニューに登録して。

app/Admin/routes.php
    $router->resource('users', UserController::class);

表示できるかなー?

image.png
image.png

ここまでは楽勝。で、試しにuser Roleを追加して、保存。(デフォルトの状態だとパスワード入力も必須。)

image.png

image.png

model_typeにデフォルト値がありません。

エラーの理由の解析

ですよねー。Laravelのピボットテーブルの設計でいうと

  • user_roleテーブル(名前も重要)
    • id (プライマリキー)
    • user_id
    • role_id

という設計であるべきところが、laravel-permissionのピボットテーブルの設計って、

  • model_has_roleテーブル
    • role_id (プライマリキー)
    • model_type
    • model_id (プライマリキー)

みたいになってて、model_typeApp\Userなどとモデル名を入れるという仕様なわけで、このmodel_typeはもちろんデフォルト値がないし、Laravelもそんな設計知りません。
 本来であれば、そもそもlaravel-permissionをコールしてRole設定すべきなんでしょうが、めんどくさい!(←重要)
 app/Admin/Controllers/UserController.phpupdate()を新設していろいろ書くのすらめんどくさい!(←重要)

ということで、問題は2つあります。

  • 一覧表示できてたRoleってそもそもmodel_type考慮してないよね?
  • 保存時に自動でmodel_type取り扱ってよ!

解決方法

問題を順番に解決していきます。

一覧表示でmodel_type考慮

ユーザーモデルでは、ピボットテーブルのmodel_typeApp\Userだけになるように絞り込む。

app/User.php
...
    public function role(){
        return $this->belongsToMany('App\Role', 'model_has_roles', 'model_id', 'role_id')
            ->wherePivot('model_type', 'App\User');
    }

    public function permission(){
        return $this->belongsToMany('App\Permission', 'model_has_permissions', 'model_id', 'permission_id')
            ->wherePivot('model_type', 'App\User');
    }
...

これで一覧表はOKだし、編集画面を立ち上げた瞬間もOKになるのですが、保存でコケる。。。

保存にも有効なwithPivotValueを使う!

今年ここで議論の末にLaravel 5.6から実装されたのがwithPivotValue!仕様はこちら(本家の説明が手抜き過ぎて用途がわからないけど・・・)

app/User.php
...
    public function role(){
        return $this->belongsToMany('App\Role', 'model_has_roles', 'model_id', 'role_id')
            ->withPivotValue('model_type', 'App\User');
    }

    public function permission(){
        return $this->belongsToMany('App\Permission', 'model_has_permissions', 'model_id', 'permission_id')
            ->withPivotValue('model_type', 'App\User');
    }
...

実行。

image.png

user Roleも保存できた!

しかも実はlaravel-adminがLaravel 5.5しかサポートしてないのにうまく動作するという快挙。

こんな重要な機能、なんでLaravel公式の説明に書いてないの。(´;ω;`)

追記

上記の方法では、laravel-permission のキャッシュがクリアされず、即時反映されないことがわかりました。
即時反映のためには、permissionやroleのつながりを変更するformの保存後にキャッシュをフラッシュする必要があります。コントローラーに以下のコードを入れれば、解決します。

app/Admin/Controllers/UserController.php
...
    protected function form()
    {
...
        $form->saved(function (Form $form) {
            app()->make(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions();
        });
...
    }
...

キャッシュについては、以下のページに詳細があります。
https://docs.spatie.be/laravel-permission/v3/advanced-usage/cache/

:thumbsup:※随時お仕事募集中。ご相談ください。:thumbsup:

8
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
15