Laravel-permission 中文翻译
初次接触到 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 协议》,转载必须注明作者和本文链接
其实这种插件吧,比较那啥,,权限比较复杂的系统这些插件的灵活性和可扩展性就显的有点力不从心了,出了问题还得自己看人家源码。其实最好的方式的,,自己明白权限角色这种原理(不复杂),自己写,这样出了问题好定位,而且扩展性也好。当然了,要求不高的系统,这些插件还是很方便的,省去了很多事儿!
@Complicated 嗯,知道了。权限问题我只知道用多对多关联,具体实现也不是很清楚。慢慢学吧,一起加油 :smile:
如果需要 扩展 已有的 Role 和 Permission 模型,请注意:
你的 Role model 需要继承自 Spatie\Permission\Models\Role model
你的 Permission model 需要继承自 Spatie\Permission\Models\Permission model
这个要如何继承,大神有示例代码吗
@hihuangwei 知道原理的话,应该不难吧,具体我也没做过。你去看看扩展包的源码,看它怎么实现的。