初次接觸到 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
traits 到User
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 還提供了本地作用域,role
和permission
scope,去查詢特定的角色或許可權$users = User::role('writer')->get(); // 返回擁有 'writer' 角色的使用者 $users = User::permission('edit')->get(); // 返回擁有特定許可權的使用者(包括直接分配的和透過角色繼承的)
role
和permission
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 自帶的 @can
和 can()
方法。
角色 仍然可被用來劃分許可權對於簡單的分配,在有必要時,你仍然能使用基於角色的輔助方法。
但是,大部分與應用相關的邏輯,通常使用 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
使用中介軟體
擴充套件包中還包含了幾個中介軟體:RoleMiddleware
、PermissionMiddleware
、RoleOrPermissionMiddleware
,你可以將它們加入到 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 點說明:
- 在填充前最好先清除
spatie.permission.cache
快取,避免快取衝突出現錯誤。清除快取可以透過一條 Artisan 命令來完成,或者直接在填充類中完成(看下面示例)。 -
下面示例中演示了清除快取、建立許可權並分配給角色:
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()); } }
擴充套件
如果需要 擴充套件 已有的 Role
和 Permission
模型,請注意:
- 你的
Role
model 需要繼承自Spatie\Permission\Models\Role
model - 你的
Permission
model 需要繼承自Spatie\Permission\Models\Permission
model
如果需要 替換 已有的 Role
和 Permission
模型,請注意:
- 你的
Role
model 需要實現(implement)Spatie\Permission\Contracts\Role
contract - 你的
Permission
model 需要實現(implement)Spatie\Permission\Contracts\Permission
contract
在這兩種情況下,不論是擴充套件還是替換,都需要在配置中指定你的新模型。在用下面的命令釋出(publish)了配置檔案後,你必須更新配置檔案中的 models.role
和 models.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: 如果用了某些快取服務比如 redis
、memcached
,或有其他站點執行在你的伺服器上,這可能會導致快取衝突。
一個簡單有效的方式是在 /config/cache.php
中設定你自己的 「快取字首」 來區分每個應用,這將會避免其他應用意外地使用或更改了你的快取資料。
本作品採用《CC 協議》,轉載必須註明作者和本文連結