Laravel-permission 中文翻譯

hehorange發表於2018-11-09

初次接觸到 spatie/laravel-permission 這個包,沒找到中文文件,自己抽空翻譯了一下,做了很小部分的刪減,基本沒影響。第一次翻譯,如有不準確的地方還請指正,感謝。

安裝

此擴充套件包適用於 Laravel 5.4 或更高版本。
可以用 composer 安裝:
composer require spatie/laravel-permission
在 Laravel 5.5 中,服務提供器會被自動註冊,而在早一點的版本中,需要手動新增服務提供器到
config/app.php 中:

'providers' => [
    // ...
    Spatie\Permission\PermissionServiceProvider::class,
];

透過 vendor:publish 釋出相關遷移檔案:
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
然後執行 php artisan migrate 生成相關資料表。
釋出配置檔案:
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"

使用

使用概覽

  • 首先,需要新增 Spatie\Permission\Traits\HasRoles traitsUser model(s):

    use Spatie\Permission\Traits\HasRoles;
    class User extends Authenticatable
    {
    use HasRoles;
    // ...
    }

    Note: 如果要使用 HasRoles trait 到其它 model,需要新增屬性 protected $guard_name = 'web';,否則會出現錯誤!

  • 此擴充套件包允許使用者與許可權和角色相關聯,每一個角色關聯多個許可權,任意一個 Role 和 Permission 都是 Eloquent models。
    可以透過 create 建立:

    use Spatie\Permission\Models\Role;
    use Spatie\Permission\Models\Permission;
    $role = Role::create(['name' => 'writer']);
    $permission = Permission::create(['name' => 'edit']);

    給角色分配一個或多個許可權:

    $role->givePermissionTo('edit');
    $role->givePermissionTo('edit', 'delete');
    $permission->assignRole($role);

    許可權與角色 同步

    $role->syncPermissions($permissions);
    $permission->syncRoles($roles);

    從角色移除一個許可權:

    $role->revokePermissionTo($permission);
    $permission->removeRole($role);

如果你使用多個 guards,需要設定 guard_name

  • HasRoles trait 為你的 model 新增了 Eloquent relationships,因此可直接使用這些關聯方法:
    // 獲取直接分配給使用者的所有許可權
    $permissions = $user->permissions;
    // 獲取使用者的所有許可權,包括直接分配的、透過角色繼承的,或者兩者全部
    $permissions = $user->getDirectPermissions();
    $permissions = $user->getPermissionsViaRoles();
    $permissions = $user->getAllPermissions();
    // 獲取使用者所有角色名稱
    $roles = $user->getRoleNames(); // 返回一個集合(collection)
  • HasRoles trait 還提供了本地作用域, rolepermission scope,去查詢特定的角色或許可權
    $users = User::role('writer')->get(); // 返回擁有 'writer' 角色的使用者
    $users = User::permission('edit')->get(); // 返回擁有特定許可權的使用者(包括直接分配的和透過角色繼承的)

    rolepermission scope 可接收字串,角色(\Spatie\Permission\Models\Role)/許可權(Permission)實體或集合(\Illuminate\Support\Collection)實體

「直接」 許可權

  • 許可權可以分配給任何一個使用者:
    $user->givePermissionTo('edit');
    // 一次賦予多個許可權
    $user->givePermissionTo('edit', 'delete');
    // 也可傳入一個陣列
    $user->givePermissionTo(['edit', 'delete']);
  • 從使用者移除許可權:
    $user->revokePermissionTo('edit');
    // 同步許可權,沒有的許可權會新增,不一致的會移除
    $user->syncPermissions(['edit', 'delete']);
  • 檢測使用者是否有某個許可權:
    $user->hasPermissionTo('edit');
    // 或者傳入許可權的 id
    $user->hasPermissionTo('1');
    $user->hasPermissionTo(Permission::find(1)->id);
    $user->hasPermissionTo($somePermission->id);
  • 判斷使用者是否具有一組許可權中的任意一個或全部:
    $user->hasAnyPermission(['edit', 'publish', 'unpublish']);
    $user->hasAllPermissions(['edit', 'publish', 'unpublish']);
    // 同樣可以僅傳入許可權的 id
    $user->hasAnyPermission(['edit', 1, 5]);
  • 儲存的許可權會被 Illuminate\Auth\Access\Gate 類註冊為預設的 guard,所以可以用 Laravel 的 can 函式來檢測許可權:
    $user->can('edit');

透過角色使用許可權

  • 一個角色可以賦予給任何使用者:

    $user->assignRole('writer');
    // 一次賦予多個角色
    $user->assignRole('writer', 'admin');
    // 或者傳入一個陣列
    $user->assignRole(['writer', 'admin']);
  • 移除角色:
    $user->removeRole('writer');

  • 同步角色:
    // 不一致的角色會被移除,替換為陣列中提供的角色
    $user->syncRoles(['writer', 'admin']);

  • 檢測使用者是否具有特定角色,一個、任意或全部:

    $user->hasRole('writer');
    $user->hasAnyRole(Role::all());
    $user->hasAllRoles(Role::all());

    assignRole, hasRole, hasAnyRole, hasAllRoles 和 removeRole,這些函式可接收字串、角色(\Spatie\Permission\Models\Role)例項、集合(\Illuminate\Support\Collection)例項

  • 給角色分配許可權:
    $role->givePermissionTo('edit articles');

  • 檢測:
    $role->hasPermissionTo('edit articles');

  • 從角色中移除許可權:
    $role->revokePermissionTo('edit articles');

    givePermissionTo 和 revokePermissionTo 函式可接收字串或許可權(Spatie\Permission\Models\Permission)實體

  • 許可權會自動依附於角色,另外,許可權也可直接分配給使用者。比如下面的示例:

    $role = Role::findByName('writer');
    $role->givePermissionTo('edit articles');
    $user->assignRole('writer');
    $user->givePermissionTo('delete articles');

    例子中,使用者擁有了 'edit articles' 和 'delete articles' 許可權,edit 是透過角色,而 delete 是使用者的直接許可權,因為它是被直接分配的。
    當我們呼叫 $user->hasDirectPermission('delete articles') 時,會返回 true
    而呼叫 $user->hasDirectPermission('edit articles') 會返回 false

    在需要對使用者的直接許可權和角色許可權分別操作時,這個方法是有用的。

  • 獲取使用者的許可權:

    // 「直接」 許可權
    $user->getDirectPermissions() // 或者 $user->permissions;
    // 繼承自角色的許可權
    $user->getPermissionsViaRoles();
    // 所有許可權(直接的、繼承的)
    $user->getAllPermissions();

    結合上面的用例,第一個返回的結果會是 'delete articles' 許可權,第二個是 'edit articles',第三個則是兩個許可權都有。

    注意:返回的結果都是許可權(Spatie\Permission\Models\Permission)例項集合

使用 Blade 指令

擴充套件包還新增了 Blade 指令來判斷已登入使用者是否具有某些角色。

可選的,你可以傳入 guard 作為第二個引數來進行檢測。

  • Blade and Roles

檢測是否有特定角色:

@role('writer')
    I am a writer!
@else
    I am not a writer...
@endrole

// 等同於
@hasrole('writer')
    I am a writer!
@else
    I am not a writer...
@endhasrole

檢測是否有角色列表中的任意一個:

@hasanyrole($collectionOfRoles)
    I have one or more of these roles!
@else
    I have none of these roles...
@endhasanyrole
// or
@hasanyrole('writer|admin')
    I am either a writer or an admin or both!
@else
    I have none of these roles...
@endhasanyrole

檢測是否擁有所列的所有角色:

@hasallroles($collectionOfRoles)
    I have all of these roles!
@else
    I do not have all of these roles...
@endhasallroles
// or
@hasallroles('writer|admin')
    I am both a writer and an admin!
@else
    I do not have all of these roles...
@endhasallroles

作為一種選擇,還可使用 @unlessrole 對特定角色進行反向判斷

@unlessrole('does not have this role')
    I do not have the role
@else
    I do have the role
@endunlessrole
  • Blade and Permissions

擴充套件包沒有新增任何許可權相關的 Blade 指令,可以使用 Laravel 自帶的 @can 指令來檢測使用者是否擁有特定許可權

@can('edit articles')
  // ...
@endcan

// or
@if(auth()->user()->can('edit articles') && $some_other_condition)
  // ...
@endif

定義一個超級管理員(Super-Admin)

強烈建議,「超級管理員」 透過設定全域性 Gate::before 規則來檢測所有期望的角色。

這樣就能實現在整個應用中使用基於許可權操作的最佳實踐,而不需要在所有地方總是要進行是否是 「超級管理員」 的檢測。

看一個在應用中定義一個 Super-Admin Gate 規則的例子:Defining a Super-Admin Gate rule

最佳實踐 -- roles vs permissions

只用 許可權相關 的程式碼來構建你的應用通常是最好的。在這種方式中,你可以在應用的任何地方使用 Laravel 自帶的 @cancan() 方法。

角色 仍然可被用來劃分許可權對於簡單的分配,在有必要時,你仍然能使用基於角色的輔助方法。
但是,大部分與應用相關的邏輯,通常使用 can 方法能被最好的控制,它能讓 Laravel 的 Gate 層去做所有繁重的工作。

多個 guards

當使用 Laravel 預設的 auth 配置,上面所有方法都可以解決常規需求,不需要額外的設定。

然而,當使用多個 guards 時,許可權和角色的呈現就會像名稱空間一樣,那意味著每個 guard 會有它自己獨有的一組許可權和角色分配給使用者。

  • 使用許可權和角色 with multiple guards

在建立新的許可權或角色時,如果沒有指定 guard,那麼將會使用 auth.guards 配置陣列中設定的第一個 guard。
若要為特定 guard 建立許可權或角色,你必須指定 guard_name

// Create a superadmin role for the admin users
$role = Role::create(['guard_name' => 'admin', 'name' => 'superadmin']);

// Define a `publish articles` permission for the admin users belonging to the admin guard
$permission = Permission::create(['guard_name' => 'admin', 'name' => 'publish articles']);

// Define a *different* `publish articles` permission for the regular users belonging to the web guard
$permission = Permission::create(['guard_name' => 'web', 'name' => 'publish articles']);

檢測一個使用者在指定 guard 下是否有某些許可權:
$user->hasPermissionTo('publish articles', 'admin');

Note: 當要決定一個角色/許可權對於給定模型是否有效時,會按下面的順序選擇 guard :
1,此 model 中的 $guard_name 屬性;2(暫定,不知道怎麼翻),the guard in the config (through a provider);3,auth.guards 配置陣列中設定的第一個 guard;4,auth.defaults.guard 中的設定。

Note: 當使用預設 web 之外的 guard 時,需要在你的 model 中宣告 $guard_name 屬性。

Note: 如果你的應用只用一個 guard,但並不是 web,那就改變 config/app.php 中所列 guards 的順序,將你的 guard 放在 guards 列表的第一個,作為唯一預設。

  • 分配許可權或角色給 guard users

你可以使用相同的方法來分配許可權和角色給使用者,只需要確認許可權或角色的 guard_name 與使用者的相匹配,否則會丟擲一個 GuardDoesNotMatch 異常。

  • 使用 Blade 指令 with multiple guards

你可以使用上面提到的所有 Blade 指令,需要傳入你想使用的 guard 作為第二個引數。

@role('super-admin', 'admin')
    I am a super-admin!
@else
    I am not a super-admin...
@endrole

使用中介軟體

擴充套件包中還包含了幾個中介軟體:RoleMiddlewarePermissionMiddlewareRoleOrPermissionMiddleware,你可以將它們加入到 app/Http/Kernel.php 中:

protected $routeMiddleware = [
    // ...
    'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
    'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
    'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class,
];

然後就可以使用它們來保護路由了:

Route::group(['middleware' => ['role:super-admin']], function () {
    //
});

Route::group(['middleware' => ['permission:publish articles']], function () {
    //
});

Route::group(['middleware' => ['role:super-admin','permission:publish articles']], function () {
    //
});

Route::group(['middleware' => ['role_or_permission:super-admin']], function () {
    //
});

Route::group(['middleware' => ['role_or_permission:publish articles']], function () {
    //
});

可選擇地,在使用多個角色或許可權時,可以用管道符(|)來隔開:

Route::group(['middleware' => ['role:super-admin|writer']], function () {
    //
});

Route::group(['middleware' => ['permission:publish articles|edit articles']], function () {
    //
});

Route::group(['middleware' => ['role_or_permission:super-admin|edit articles']], function () {
    //
});

同樣也可在控制器中使用,在其建構函式中設定想要的中介軟體即可:

public function __construct()
{
    $this->middleware(['role:super-admin','permission:publish articles|edit articles']);
}

public function __construct()
{
    $this->middleware(['role_or_permission:super-admin|edit articles']);
}

捕捉角色或許可權的異常

如果要重寫預設的 403 響應,可以用應用的異常處理來捕捉 UnauthorizedException

public function render($request, Exception $exception)
{
    if ($exception instanceof \Spatie\Permission\Exceptions\UnauthorizedException) {
        // Code here ...
    }

    return parent::render($request, $exception);
}

使用 Artisan 命令

還可以在 console 端用 artisan 命令來建立角色或許可權:

php artisan permission:create-role writer
php artisan permission:create-permission "edit articles"

若要為特定 guard 建立許可權或角色,則需要指定 guard 名稱作為第二個引數:

php artisan permission:create-role writer web
php artisan permission:create-permission "edit articles" web

在建立角色時,可以同時建立並分配許可權:

php artisan permission:create-role writer web "create articles|edit articles"

單元測試 (暫不翻,還沒學到單元測試)

In your application's tests, if you are not seeding roles and permissions as part of your test setUp() then you may run into a chicken/egg situation where roles and permissions aren't registered with the gate (because your tests create them after that gate registration is done). Working around this is simple: In your tests simply add a setUp() instruction to re-register the permissions, like this:

public function setUp()
{
    // first include all the normal setUp operations
    parent::setUp();

    // now re-register all the roles and permissions
    $this->app->make(\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();
}

資料庫填充(Seeding)

對於資料庫填充有 2 點說明:

  1. 在填充前最好先清除 spatie.permission.cache 快取,避免快取衝突出現錯誤。清除快取可以透過一條 Artisan 命令來完成,或者直接在填充類中完成(看下面示例)。
  2. 下面示例中演示了清除快取、建立許可權並分配給角色:

    use Illuminate\Database\Seeder;
    use Spatie\Permission\Models\Role;
    use Spatie\Permission\Models\Permission;
    class RolesAndPermissionsSeeder extends Seeder
    {
    public function run()
    {
        // Reset cached roles and permissions
        app()['cache']->forget('spatie.permission.cache');
    
        // create permissions
        Permission::create(['name' => 'edit articles']);
        Permission::create(['name' => 'delete articles']);
        Permission::create(['name' => 'publish articles']);
        Permission::create(['name' => 'unpublish articles']);
    
        // create roles and assign created permissions
    
        $role = Role::create(['name' => 'writer']);
        $role->givePermissionTo('edit articles');
    
        $role = Role::create(['name' => 'moderator']);
        $role->givePermissionTo(['publish articles', 'unpublish articles']);
    
        $role = Role::create(['name' => 'super-admin']);
        $role->givePermissionTo(Permission::all());
    }
    }

擴充套件

如果需要 擴充套件 已有的 RolePermission 模型,請注意:

  • 你的 Role model 需要繼承自 Spatie\Permission\Models\Role model
  • 你的 Permission model 需要繼承自 Spatie\Permission\Models\Permission model

如果需要 替換 已有的 RolePermission 模型,請注意:

  • 你的 Role model 需要實現(implement) Spatie\Permission\Contracts\Role contract
  • 你的 Permission model 需要實現(implement) Spatie\Permission\Contracts\Permission contract

在這兩種情況下,不論是擴充套件還是替換,都需要在配置中指定你的新模型。在用下面的命令釋出(publish)了配置檔案後,你必須更新配置檔案中的 models.rolemodels.permission 值。

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"

快取

角色和許可權資料會被快取起來以提升速度。

當使用提供的方法來操作角色和許可權時,其快取會自動重置:

$user->assignRole('writer');
$user->removeRole('writer');
$user->syncRoles(params);
$role->givePermissionTo('edit articles');
$role->revokePermissionTo('edit articles');
$role->syncPermissions(params);
$permission->assignRole('writer');
$permission->removeRole('writer');
$permission->syncRoles(params);

然而,如果你沒有用擴充套件包提供的方法,而是直接在資料庫裡手動操作許可權/角色資料時,你將看不到應用改變的反饋,除非你手動重置快取。

手動重置快取

執行 Artisan 命令即可:
php artisan cache:forget spatie.permission.cache

快取識別符號

TIP: 如果用了某些快取服務比如 redismemcached,或有其他站點執行在你的伺服器上,這可能會導致快取衝突。
一個簡單有效的方式是在 /config/cache.php 中設定你自己的 「快取字首」 來區分每個應用,這將會避免其他應用意外地使用或更改了你的快取資料。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章