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 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 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 4
Complicated

其实这种插件吧,比较那啥,,权限比较复杂的系统这些插件的灵活性和可扩展性就显的有点力不从心了,出了问题还得自己看人家源码。其实最好的方式的,,自己明白权限角色这种原理(不复杂),自己写,这样出了问题好定位,而且扩展性也好。当然了,要求不高的系统,这些插件还是很方便的,省去了很多事儿!

5年前 评论

@Complicated 嗯,知道了。权限问题我只知道用多对多关联,具体实现也不是很清楚。慢慢学吧,一起加油 :smile:

5年前 评论
黄威

如果需要 扩展 已有的 Role 和 Permission 模型,请注意:

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

这个要如何继承,大神有示例代码吗

5年前 评论

@hihuangwei 知道原理的话,应该不难吧,具体我也没做过。你去看看扩展包的源码,看它怎么实现的。

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!