Laravel-permission

2025-04-17

パクリ38発目!!
サクっとやれる分だけやる、

パクリ元

内容

認証の次は認可。今まではカスタムで作ってきたけど汎用的なので問題なければそれがいい。パクリ元1つ目にあった Laravel-permission を使ってみる。

準備

インストール&ファイル作成

~/example-app$ sail composer require spatie/laravel-permission
・・・
Using version ^6.17 for spatie/laravel-permission
~/example-app$ sail artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
・・・

 マイグレーション

~/example-app$ sail artisan migrate
・・・

専用のテーブルが5テーブル追加された。

ロール、パーミッションの定義

書いてあるとおりでやってみる。

~/example-app$ mkdir app/Enums
~/example-app$ touch app/Enums/{Role.php,Permission.php}
app/Enums/Role.php
<?php

declare(strict_types=1);

namespace App\Enums;

enum Role: string
{
    case Staff = 'staff';
    case Manager = 'manager';
    case Developer = 'developer';
}
app/Enums/Permission.php
<?php

declare(strict_types=1);

namespace App\Enums;

enum Permission: string
{
    case EditArticles = 'edit articles';
    case DeleteArticles = 'delete articles';
    case PublishArticles = 'publish articles';
    case UnpublishArticles = 'unpublish articles';
}

declare(strict_types=1); て知らんかった、、

app/Models/User.php
・・・
use Spatie\Permission\Traits\HasRoles;
・・・
    use HasFactory, Notifiable, TwoFactorAuthenticatable, HasRoles;
・・・
app/Providers/AuthServiceProvider.php
<?php

declare(strict_types=1);

namespace App\Providers;

use App\Enums\Role;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

final class AuthServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Gate::before(fn ($user) => $user->hasRole(Role::Developer->value) ? true : null);
    }
}
app/bootstrap/providers.php
<?php

return [
    App\Providers\AppServiceProvider::class,
    App\Providers\FortifyServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
];

シーダーの作成

~/example-app$ sail artisan make:seeder RolesAndPermissionsSeeder
database/seeders/RolesAndPermissionsSeeder.php
<?php

declare(strict_types=1);

namespace Database\Seeders;

use App\Enums\Permission;
use App\Enums\Role;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission as EloquentPermission;
use Spatie\Permission\Models\Role as EloquentRole;
use Spatie\Permission\PermissionRegistrar;

final class RolesAndPermissionsSeeder extends Seeder
{
    /**
     * @param PermissionRegistrar $permissionRegistrar
     */
    public function __construct(private PermissionRegistrar $permissionRegistrar)
    {
    }

    public function run(): void
    {
        $this->permissionRegistrar->forgetCachedPermissions();

        foreach (Permission::cases() as $permission) {
            EloquentPermission::create(['name' => $permission->value]);
        }

        foreach (Role::cases() as $role) {
            EloquentRole::create(['name' => $role->value]);
        }

        foreach ($this->makeAuthorizationRuleList() as $roleName => $permissionNameList) {
            $role = EloquentRole::findByName($roleName);

            foreach ($permissionNameList as $permissionName) {
                $role->givePermissionTo($permissionName);
            }
        }
    }

    /**
     * @return array
     */
    private function makeAuthorizationRuleList(): array
    {
        return [
            Role::Staff->value => [
                Permission::EditArticles->value,
                Permission::DeleteArticles->value,
            ],
            Role::Manager->value => [
                Permission::PublishArticles->value,
                Permission::UnpublishArticles->value,
            ],
        ];
    }
}
~/example-app$ sail artisan db:seed --class=RolesAndPermissionsSeeder
   INFO  Seeding database. 

記事にはユーザーシーダーが載っていないので適当に作ってみる。

~/example-app$ sail artisan make:seeder UsersSeeder
database/seeders/UsersSeeder.php
<?php

declare(strict_types=1);

namespace Database\Seeders;

use App\Enums\Role;
use App\Models\User;
use Illuminate\Database\Seeder;

final class UsersSeeder extends Seeder
{
    public function run(): void
    {
        User::truncate();
        
        $user1 = User::factory()->create([
            'name' => 'user1',
            'email' => 'user1@example.com',
        ]);
        $user1->assignRole(Role::Staff);
        
        $user2 = User::factory()->create([
            'name' => 'user2',
            'email' => 'user2@example.com',
        ]);
        $user2->assignRole(Role::Manager);
        
        $user3 = User::factory()->create([
            'name' => 'user3',
            'email' => 'user3@example.com',
        ]);
        $user3->assignRole(Role::Developer);
    }
}
~/example-app$ sail artisan db:seed --class=UsersSeeder
   INFO  Seeding database. 

確認

tinker で確認する。

~/example-app$ sail artisan tinker
Psy Shell v0.12.8 (PHP 8.4.5 — cli) by Justin Hileman
> use App\Enums\Role;
> use App\Enums\Permission;
> use App\Models\User;
> $staff = User::where('name', 'user1')->first();
・・・
> $staff->hasRole(Role::Staff->value);
= true

> $staff->can(Permission::EditArticles->value);
= true

> $staff->can(Permission::PublishArticles->value);
= false

>  $staff->getAllPermissions()->map(fn ($p) => $p->name);
= Illuminate\Support\Collection {#6260
    all: [
      "edit articles",
      "delete articles",
    ],
  }

> $manager = User::where('name', 'user2')->first();
・・・
> $manager->hasRole(Role::Manager->value);
= true

> $manager->can(Permission::EditArticles->value);
= false

> $manager->can(Permission::PublishArticles->value);
= true

> $developer = User::where('name', 'user3')->first();
・・・
> $developer->hasRole(Role::Developer->value);
= true

> $developer->can(Permission::EditArticles->value);
= true

> $developer->can(Permission::PublishArticles->value);
= true

とりあえず理解した。getAllPermissions() でそのユーザーの持つ権限を取得できることも確認。ロールに権限を与えたり、モデルにロールをアサインするだけで DB にも保存されるので便利な仕組みだね。Enum を使うのは考えモノだと思う、->value とか ->name とか忘れるよね、気づかないよね、

チーム権限

パクリ元2つ目を参考にチーム権限を試してみる。

config/permission.php
・・・
    'teams' => true,
・・・
~/example-app$ sail artisan permission:setup-teams
The teams feature setup is going to add a migration and a model
 Proceed with the migration creation? (yes/no) [yes]:
 > yes
Creating migration
Migration created successfully.
~/example-app$ sail artisan migrate
   INFO  Running migrations.  

テーブルを確認すると、、model_has_roles テーブルと roles テーブル に team_id カラムができており(model_has_permissionsもだけど無視)、考えていた、所属会社のデータだけが見れる、というのをやろうとすると全会社分登録しないといけない気がするのでソース、DB ともに戻すことにした。同じロールなのに所属によってやれることが違う、というのは大会社の部長と零細の部長では業務範囲が違う、というようなことかもしれないがロールを少しずつ変える方が分かりやすいとおもう。データ範囲の絞りは取得時の条件追加だな。

なんとなく適用

現時点までのソースに追加してみる。ダッシュボードにユーザー一覧を表示しているのを権限によって変えてみる。権限名とか全く関係ないけど

app/Http/Controllers/UsersController.php
・・・
use Illuminate\Support\Facades\Auth;
use Spatie\Permission\Exceptions\UnauthorizedException;
・・・
public function index()
    {
        $users = [];

        $user = Auth::user();
        if ($user->can(Permission::PublishArticles->value) && $user->can(Permission::EditArticles->value)) {
            $users = User::all();
        } else if ($user->can(Permission::PublishArticles->value)) {
            $users = [$user];
        } else {
            throw UnauthorizedException::forPermissions([Permission::PublishArticles->value]);
        }
        
        return $this->sendResponse('successfully fetched.', $users);
    }・・・

user3 では登録済みユーザー3件全部、user2 では自分の1件、user1では何も返されず Console でエラーが出ているのを確認できた。

雑感

ロールと権限、ユーザーとロール、それぞれマトリックスにしてチェックで管理する画面まで作りたかったところだが大仕事になりそうなので今回はやめとく。

これからアイキャッチ画像はペイント Copilt にまかせる。意味わからん絵だけど、、

laravel,PHP

Posted by ak