0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

権限を「リソース×操作」で動的生成し、ロールの差分だけ書く(spatie/laravel-permission)

0
Posted at

社内システムの管理者権限をspatie/laravel-permissionで設計したときのメモです。権限を1つずつ手で書き並べるのではなく、リソースと操作の掛け算で生成しています。

権限を手で書き並べると、増えたときに辛い

管理対象のリソース(顧客、契約、料金プラン、オプションなど)が9種類、操作(閲覧・作成・更新・削除)が4種類あるので、単純に組み合わせると36個の権限があります。これを1つずつ

Permission::create(['name' => 'customers.view']);
Permission::create(['name' => 'customers.create']);
// ... 34行続く

と書くと、リソースを1つ増やすたびに4行足す作業が発生し、書き漏れにも気づきにくくなります。

対処: 二重ループで生成する

// database/seeders/RolePermissionSeeder.php
public function run(): void
{
    $resources = [
        'customers', 'contracts', 'features', 'customer-features',
        'site-settings', 'plans', 'options', 'support-hour-packages', 'payments',
    ];

    $actions = ['view', 'create', 'update', 'delete'];

    foreach ($resources as $resource) {
        foreach ($actions as $action) {
            Permission::firstOrCreate(['name' => "{$resource}.{$action}"]);
        }
    }

    $superAdmin = Role::firstOrCreate(['name' => 'SuperAdmin']);
    $superAdmin->syncPermissions(Permission::all());

    // Staffは削除操作のみ権限を持たない(誤操作による顧客データ消失を防ぐ)
    $staff = Role::firstOrCreate(['name' => 'Staff']);
    $staffPermissions = Permission::where('name', 'not like', '%.delete')->get();
    $staff->syncPermissions($staffPermissions);
}

リソースを1つ足したいときは、$resources配列に文字列を1行追加するだけで、そのリソースの4つの権限(view/create/update/delete)が自動的に生成されます。権限の命名規則(リソース名.操作名)を統一しておくことで、ループでの生成が可能になっています。

ロールは「全部から引く」形で定義する

SuperAdminにはPermission::all()で全権限を渡し、Staffには**「削除以外」という条件**で絞り込んだものを渡しています。

$staffPermissions = Permission::where('name', 'not like', '%.delete')->get();

ここがこの設計の要点です。「Staffに何を許可するか」を列挙するのではなく、**「全権限から、許可しないものを引く」**という発想にしています。理由は、1人体制に近い小規模な組織では、権限の差は「削除操作ができるかどうか」の1点に集約されることが多いからです。

もし将来、リソースが増えても、not like '%.delete'という条件はそのまま機能します。新しいリソースの権限が生成されても、.deleteで終わる部分だけが自動的にStaffの権限から除外されるので、ロール定義側のコードは一切変更不要です。

逆に「リソースごとに列挙する」設計を選んでいたら、リソースを1つ増やすたびに、権限の生成コードとロールへの割り当てコードの両方を直す必要が出てきます。

firstOrCreateとsyncPermissionsで冪等にする

このシーダーは、何度実行しても同じ結果になるように書かれています。

  • Permission::firstOrCreate(...): 既に存在する権限は再作成しない
  • Role::firstOrCreate(...): 既に存在するロールは再作成しない
  • syncPermissions(...): ロールの権限を「渡した集合に完全に一致させる」(既存の余分な権限があれば外し、足りない権限があれば足す)

syncPermissionsが冪等な同期処理になっているおかげで、権限の定義($resourcesや$actions)を後から変更してシーダーを再実行するだけで、DBの状態を最新の定義に追従させられます。手動での付け外しが不要です。

この設計が向いているケース

  • ロールの種類が少なく(今回は2つ)、権限の差が単純なルール(「削除だけ禁止」等)で表現できる
  • リソースの追加・削除が今後も発生する見込みがある

逆に、ロールごとに全く異なる権限体系が必要な場合(例えば「経理担当は支払い関連だけ、営業担当は顧客関連だけ」のような縦割り)は、この「全部から引く」方式は合わず、リソースごとに許可リストを持たせる方が素直です。業務の実態に合わせてどちらの形にするかを選ぶ必要があります。

まとめ

  • 権限が「リソース×操作」の掛け算になるなら、手で列挙せず二重ループで生成する
  • ロールの権限差が単純なルールで表現できるなら、「列挙」ではなく「全部から条件で引く」形にすると、リソース追加時の変更箇所が減る
  • シーダーはfirstOrCreate+syncPermissionsで冪等にし、定義変更のたびに再実行するだけで済むようにする

この記事はZennにも投稿しています。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?