「laravel-adminで、追加パラメータのあるピボットテーブルをコーディングなしで保存する。」
「assign()の第2パラメータなしでピボットテーブルの追加パラメータを扱う」
「モデルにbelongsToManyピボットテーブルの追加パラメータを指定して保存まで自動化する」
といったタイトルでもいいですね。
※ここで出てくる「ユーザー」は、管理画面ではなくフロント側のユーザーです。
Laravel 5.7を導入して、laravel-permission
+ laravel-admin
で快適開発!と思ってたら、ユーザーのRoleとPermission編集画面作成でハマった話。日本語でも英語でもあまり情報がなかったのでコツだけ紹介。
普通に作るとエラーが出る。
この記事などを参考にしながら進めてみる。
laravel-permission
+ laravel-admin
を導入して、ユーザーモデルのUser.php
にlaravel-permission
を割り当て。さらにrole()
とpermission()
を実装して、
...
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');
}
...
※もしこちらの記事でマルチテナント化してる場合は以下のようにモデルを変更してください。
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
...
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(' ', $role);
});
$grid->permission()->display(function ($permission) {
$permission = array_map(function ($permission) {
return "<span class='label label-success'>{$permission['name']}</span>";
}, $permission);
return join(' ', $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通して、メニューに登録して。
$router->resource('users', UserController::class);
表示できるかなー?
ここまでは楽勝。で、試しにuser
Roleを追加して、保存。(デフォルトの状態だとパスワード入力も必須。)
model_type
にデフォルト値がありません。
エラーの理由の解析
ですよねー。Laravelのピボットテーブルの設計でいうと
- user_roleテーブル(名前も重要)
- id (プライマリキー)
- user_id
- role_id
という設計であるべきところが、laravel-permissionのピボットテーブルの設計って、
- model_has_roleテーブル
- role_id (プライマリキー)
- model_type
- model_id (プライマリキー)
みたいになってて、model_type
にApp\User
などとモデル名を入れるという仕様なわけで、このmodel_typeはもちろんデフォルト値がないし、Laravelもそんな設計知りません。
本来であれば、そもそもlaravel-permissionをコールしてRole設定すべきなんでしょうが、めんどくさい!(←重要)
app/Admin/Controllers/UserController.php
にupdate()
を新設していろいろ書くのすらめんどくさい!(←重要)
ということで、問題は2つあります。
- 一覧表示できてたRoleってそもそも
model_type
考慮してないよね? - 保存時に自動で
model_type
取り扱ってよ!
解決方法
問題を順番に解決していきます。
一覧表示でmodel_type
考慮
ユーザーモデルでは、ピボットテーブルのmodel_type
がApp\User
だけになるように絞り込む。
...
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
!仕様はこちら(本家の説明が手抜き過ぎて用途がわからないけど・・・)
...
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');
}
...
実行。
user
Roleも保存できた!
しかも実はlaravel-adminがLaravel 5.5しかサポートしてないのにうまく動作するという快挙。
こんな重要な機能、なんでLaravel公式の説明に書いてないの。(´;ω;`)
追記
上記の方法では、laravel-permission
のキャッシュがクリアされず、即時反映されないことがわかりました。
即時反映のためには、permissionやroleのつながりを変更するformの保存後にキャッシュをフラッシュする必要があります。コントローラーに以下のコードを入れれば、解決します。
...
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/
※随時お仕事募集中。ご相談ください。