codemastersolucoes/laravel-permission

Laravel 5.4及以上版本的权限处理

2.29.2 2018-12-26 20:25 UTC

README

####此包已从spatie/laravel-permission分支 fork,并由codemastersolucoes/laravel-permission修改

赞助商

Latest Version on Packagist Build Status StyleCI Total Downloads

此包允许您在数据库中管理用户权限和角色。

安装后,您可以执行以下操作

// Adding permissions to a user
$user->givePermissionTo('edit articles');

// Adding permissions via a role
$user->assignRole('writer');

$role->givePermissionTo('edit articles');

如果您使用多个守卫,我们也为您提供了支持。每个守卫都将有自己的权限和角色集,可以分配给守卫的用户。请参阅README中的使用多个守卫部分。

因为所有权限都将注册在Laravel的gate上,所以您可以使用Laravel的默认can函数检查用户是否有权限。

$user->can('edit articles');

Spatie是一家位于比利时安特卫普的网络设计公司。您可以在我们的网站上找到所有开源项目的概述这里

安装

Laravel

此包可用于Laravel 5.4或更高版本。如果您正在使用Laravel的较旧版本,请参阅此包的v1分支

您可以通过Composer安装此包

composer require codemastersolucoes/laravel-permission

在Laravel 5.5中,服务提供程序将自动注册。在框架的旧版本中,只需在config/app.php文件中添加服务提供程序即可

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

您可以使用以下命令发布迁移

php artisan vendor:publish --provider="CodeMaster\Permission\PermissionServiceProvider" --tag="migrations"

如果您使用UUID或GUID作为您的User模型,您可以更新create_permission_tables.php迁移,将$table->unsignedBigInteger($columnNames['model_morph_key'])替换为$table->uuid($columnNames['model_morph_key'])。为了保持一致性,您还可以更新包配置文件以使用model_uuid列名而不是默认的model_id列名。

迁移发布后,您可以通过运行迁移来创建角色和权限表

php artisan migrate

您可以使用以下命令发布配置文件

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

发布后,config/permission.php配置文件包含

return [

    'models' => [

        /*
         * When using the "HasPermissions" trait from this package, we need to know which
         * Eloquent model should be used to retrieve your permissions. Of course, it
         * is often just the "Permission" model but you may use whatever you like.
         *
         * The model you want to use as a Permission model needs to implement the
         * `CodeMaster\Permission\Contracts\Permission` contract.
         */

        'permission' => CodeMaster\Permission\Models\Permission::class,

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * Eloquent model should be used to retrieve your roles. Of course, it
         * is often just the "Role" model but you may use whatever you like.
         *
         * The model you want to use as a Role model needs to implement the
         * `CodeMaster\Permission\Contracts\Role` contract.
         */

        'role' => CodeMaster\Permission\Models\Role::class,

    ],

    'table_names' => [

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * table should be used to retrieve your roles. We have chosen a basic
         * default value but you may easily change it to any table you like.
         */

        'roles' => 'roles',

        /*
         * When using the "HasPermissions" trait from this package, we need to know which
         * table should be used to retrieve your permissions. We have chosen a basic
         * default value but you may easily change it to any table you like.
         */

        'permissions' => 'permissions',

        /*
         * When using the "HasPermissions" trait from this package, we need to know which
         * table should be used to retrieve your models permissions. We have chosen a
         * basic default value but you may easily change it to any table you like.
         */

        'model_has_permissions' => 'model_has_permissions',

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * table should be used to retrieve your models roles. We have chosen a
         * basic default value but you may easily change it to any table you like.
         */

        'model_has_roles' => 'model_has_roles',

        /*
         * When using the "HasRoles" trait from this package, we need to know which
         * table should be used to retrieve your roles permissions. We have chosen a
         * basic default value but you may easily change it to any table you like.
         */

        'role_has_permissions' => 'role_has_permissions',
    ],

    'column_names' => [

        /*
         * Change this if you want to name the related model primary key other than
         * `model_id`.
         *
         * For example, this would be nice if your primary keys are all UUIDs. In
         * that case, name this `model_uuid`.
         */
        'model_morph_key' => 'model_id',
    ],

    /*
     * When set to true, the required permission/role names are added to the exception
     * message. This could be considered an information leak in some contexts, so
     * the default setting is false here for optimum safety.
     */

    'display_permission_in_exception' => false,

    'cache' => [

        /*
         * By default all permissions will be cached for 24 hours unless a permission or
         * role is updated. Then the cache will be flushed immediately.
         */

        'expiration_time' => 60 * 24,

        /*
         * The key to use when tagging and prefixing entries in the cache.
         */

        'key' => 'cms.permission.cache',

        /*
         * When checking for a permission against a model by passing a Permission
         * instance to the check, this key determines what attribute on the
         * Permissions model is used to cache against.
         *
         * Ideally, this should match your preferred way of checking permissions, eg:
         * `$user->can('view-posts')` would be 'name'.
         */

        'model_key' => 'name',

        /*
         * You may optionally indicate a specific cache driver to use for permission and
         * role caching using any of the `store` drivers listed in the cache.php config
         * file. Using 'default' here means to use the `default` set in cache.php.
         */
        'store' => 'default',
    ],
];

Lumen

您可以通过Composer安装此包

composer require codemastersolucoes/laravel-permission

复制所需文件

mkdir -p config
cp vendor/codemastersolucoes/laravel-permission/config/permission.php config/permission.php
cp vendor/codemastersolucoes/laravel-permission/database/migrations/create_permission_tables.php.stub database/migrations/2018_01_01_000000_create_permission_tables.php

您还需要在config/auth.php中创建另一个配置文件。您可以在Laravel存储库中获取它,或者只需运行以下命令

curl -Ls https://raw.githubusercontent.com/laravel/lumen-framework/5.7/config/auth.php -o config/auth.php

然后,在bootstrap/app.php中注册中间件

$app->routeMiddleware([
    'auth'       => App\Http\Middleware\Authenticate::class,
    'permission' => CodeMaster\Permission\Middlewares\PermissionMiddleware::class,
    'role'       => CodeMaster\Permission\Middlewares\RoleMiddleware::class,
]);

以及配置文件、服务提供程序和缓存别名

$app->configure('permission');
$app->alias('cache', \Illuminate\Cache\CacheManager::class);  // if you don't have this already
$app->register(CodeMaster\Permission\PermissionServiceProvider::class);

现在,运行您的迁移

php artisan migrate

用法

首先,将CodeMaster\Permission\Traits\HasRoles特性添加到您的User模型中

use Illuminate\Foundation\Auth\User as Authenticatable;
use CodeMaster\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;

    // ...
}
  • 请注意,如果您需要将 HasRoles 特性与其他模型(例如 Page)一起使用,您还需要将 protected $guard_name = 'web'; 添加到该模型中,否则您会收到错误。
use Illuminate\Database\Eloquent\Model;
use CodeMaster\Permission\Traits\HasRoles;

class Page extends Model
{
   use HasRoles;

   protected $guard_name = 'web'; // or whatever guard you want to use

   // ...
}

此包允许将用户与权限和角色关联起来。每个角色都与多个权限相关联。一个 Role 和一个 Permission 都是常规的 Eloquent 模型。它们需要一个 name,可以像这样创建:

use CodeMaster\Permission\Models\Role;
use CodeMaster\Permission\Models\Permission;

$role = Role::create(['name' => 'writer']);
$permission = Permission::create(['name' => 'edit articles']);

可以使用以下方法之一将权限分配给角色:

$role->givePermissionTo($permission);
$permission->assignRole($role);

可以使用以下方法之一将多个权限同步到角色:

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

可以使用以下方法之一从角色中删除权限:

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

如果您使用多个守卫,则需要设置 guard_name 属性。在 README 的 使用多个守卫 部分中了解相关信息。

HasRoles 特性为您的模型添加了 Eloquent 关联,可以直接访问或用作基础查询。

// get a list of all permissions directly assigned to the user
$permissions = $user->permissions;

// get all permissions for the user, either directly, or from roles, or from both
$permissions = $user->getDirectPermissions();
$permissions = $user->getPermissionsViaRoles();
$permissions = $user->getAllPermissions();

// get the names of the user's roles
$roles = $user->getRoleNames(); // Returns a collection

HasRoles 特性还为您模型的模型添加了一个 role 范围,以将查询范围到某些角色或权限。

$users = User::role('writer')->get(); // Returns only users with the role 'writer'

role 范围可以接受一个字符串,一个 \CodeMaster\Permission\Models\Role 对象或一个 \Illuminate\Support\Collection 对象。

此特性还添加了一个范围,仅获取具有特定权限的用户。

$users = User::permission('edit articles')->get(); // Returns only users with the permission 'edit articles' (inherited or directly)

此范围可以接受一个字符串,一个 \CodeMaster\Permission\Models\Permission 对象或一个 \Illuminate\Support\Collection 对象。

使用“直接”权限(以下将使用角色和权限)

可以将权限授予任何用户。

$user->givePermissionTo('edit articles');

// You can also give multiple permission at once
$user->givePermissionTo('edit articles', 'delete articles');

// You may also pass an array
$user->givePermissionTo(['edit articles', 'delete articles']);

可以从用户中撤销权限。

$user->revokePermissionTo('edit articles');

或者一次性撤销并添加新的权限。

$user->syncPermissions(['edit articles', 'delete articles']);

您可以检查用户是否具有权限。

$user->hasPermissionTo('edit articles');

或者您可以通过一个表示权限 id 的整数来传递。

$user->hasPermissionTo('1');
$user->hasPermissionTo(Permission::find(1)->id);
$user->hasPermissionTo($somePermission->id);

您可以检查用户是否具有数组中的任何权限。

$user->hasAnyPermission(['edit articles', 'publish articles', 'unpublish articles']);

...或者如果用户具有数组中的所有权限

$user->hasAllPermissions(['edit articles', 'publish articles', 'unpublish articles']);

您也可以通过权限 id 来查找整数。

$user->hasAnyPermission(['edit articles', 1, 5]);

保存的权限将与默认守卫的 Illuminate\Auth\Access\Gate 类注册。因此,您可以使用 Laravel 的默认 can 函数检查用户是否具有权限。

$user->can('edit articles');

通过角色使用权限

可以将角色分配给任何用户。

$user->assignRole('writer');

// You can also assign multiple roles at once
$user->assignRole('writer', 'admin');
// or as an array
$user->assignRole(['writer', 'admin']);

可以从用户中删除角色。

$user->removeRole('writer');

角色也可以同步。

// All current roles will be removed from the user and replaced by the array given
$user->syncRoles(['writer', 'admin']);

您可以确定用户是否具有特定的角色。

$user->hasRole('writer');

您还可以确定用户是否具有给定列表中的任何角色。

$user->hasAnyRole(Role::all());

您还可以确定用户是否具有给定列表中的所有角色。

$user->hasAllRoles(Role::all());

函数 assignRolehasRolehasAnyRolehasAllRolesremoveRole 可以接受一个字符串,一个 \CodeMaster\Permission\Models\Role 对象或一个 \Illuminate\Support\Collection 对象。

可以将权限授予角色。

$role->givePermissionTo('edit articles');

您可以确定角色是否具有特定的权限。

$role->hasPermissionTo('edit articles');

可以从角色中撤销权限。

$role->revokePermissionTo('edit articles');

函数 givePermissionTorevokePermissionTo 可以接受一个字符串或一个 CodeMaster\Permission\Models\Permission 对象。

权限会自动从角色继承。此外,还可以为用户分配单个权限。例如

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

$user->assignRole('writer');

$user->givePermissionTo('delete articles');

在上面的例子中,一个角色被赋予了编辑文章的权限,并将此角色分配给了用户。现在用户可以编辑文章,并且还可以删除文章。'删除文章'的权限是用户的直接权限,因为它是直接分配给他们的。当我们调用 $user->hasDirectPermission('delete articles') 时,它返回 true,但对于 $user->hasDirectPermission('edit articles') 返回 false

此方法很有用,如果构建一个用于在应用程序中设置角色和用户权限的表单,并想要限制或更改用户角色的继承权限,即仅允许更改用户的直接权限。

您可以列出所有这些权限。

// Direct permissions
$user->getDirectPermissions() // Or $user->permissions;

// Permissions inherited from the user's roles
$user->getPermissionsViaRoles();

// All permissions which apply on the user (inherited and direct)
$user->getAllPermissions();

所有这些响应都是 CodeMaster\Permission\Models\Permission 对象的集合。

如果我们遵循之前的示例,第一个响应将包含具有删除文章权限的集合,第二个将包含具有编辑文章权限的集合,第三个将包含两者。

使用Blade指令

此包还添加了Blade指令以验证当前登录用户是否具有给定角色列表中的所有或任何角色。

可选地,您可以作为第二个参数传递将执行检查的guard

Blade和角色

检查特定角色

@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和权限

此包不添加任何特定权限的Blade指令。相反,使用Laravel的本地@can指令来检查用户是否具有特定权限。

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

或者

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

定义超级管理员

我们强烈建议通过设置全局Gate::before规则来处理超级管理员,该规则检查所需的角色。

然后,您可以在整个应用程序中主要使用基于权限的控制,而无需总是检查“这是一个超级管理员”。

请参阅此维基文章,了解如何在您的应用程序中定义超级管理员Gate规则

最佳实践 -- 角色与权限

通常最好在您的应用程序中围绕permissions进行编码。这样,您就可以在应用程序的任何地方始终使用本地的Laravel@cancan()指令。

角色仍然可以用来分组权限,以便于分配,并且如果确实需要,您仍然可以使用基于角色的辅助方法。但大多数与应用程序相关的逻辑通常最好使用can方法来控制,这允许Laravel的Gate层做所有繁重的工作。

使用多个守卫

当使用默认的Laravel认证配置时,上述所有方法都将默认工作,无需额外配置。

然而,当使用多个guard时,它们将像命名空间一样作用于您的权限和角色。这意味着每个guard都有自己的权限和角色集合,可以分配给其用户模型。

使用多个guard使用权限和角色

当创建新权限和角色时,如果没有指定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');

注意:在确定给定模型上的角色/权限是否有效时,它将按以下顺序选择guard:首先模型上的$guard_name属性;然后配置中的guard(通过提供者);然后auth.guards配置数组中定义的第一个guard;然后auth.defaults.guard配置。

注意:当使用除默认web guard之外的其他guard时,您需要通过在模型中设置$guard_name属性来声明每个模型希望使用的guard_name。每个模型一个最为简单。

注意:如果您的应用程序仅使用单个guard,但不是web,则更改您的config/app.php中列出的guards的顺序,将您的primary guard作为默认值并在定义的guards列表中列为第一个。

将权限和角色分配给guard用户

您可以使用与上述在通过角色使用权限中描述相同的方法来将权限和角色分配给用户。只需确保权限或角色上的guard_name与用户的guard匹配,否则将抛出GuardDoesNotMatch异常。

使用多个guard的blade指令

您可以通过将您想要使用的守卫作为指令的第二个参数传递,来使用列表中列出的所有 blade 指令。

@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' => \CodeMaster\Permission\Middlewares\RoleMiddleware::class,
    'permission' => \CodeMaster\Permission\Middlewares\PermissionMiddleware::class,
    'role_or_permission' => \CodeMaster\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 \CodeMaster\Permission\Exceptions\UnauthorizedException) {
        // Code here ...
    }

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

使用 artisan 命令

您可以使用 artisan 命令从控制台创建角色或权限。

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

当为特定的守卫创建权限/角色时,您可以在第二个参数中指定守卫名称。

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"

单元测试

在您的应用测试中,如果您没有在测试的 setUp() 部分作为部分对角色和权限进行播种,那么您可能会遇到鸡生蛋的困境,因为角色和权限没有注册在门(因为您的测试在门注册之后创建它们)。解决这个问题很简单:在您的测试中仅添加一个 setUp() 指令,就像这样

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

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

数据库填充

您可能会发现,在播种之前清除此包的缓存是最佳做法,以避免缓存冲突错误。这可以直接在播种器类中完成。以下是一个示例播种器,它首先清除缓存,然后创建权限,最后将权限分配给角色(这些步骤的顺序是有意为之)

use Illuminate\Database\Seeder;
use CodeMaster\Permission\Models\Role;
use CodeMaster\Permission\Models\Permission;

class RolesAndPermissionsSeeder extends Seeder
{
    public function run()
    {
        // Reset cached roles and permissions
        app()[\CodeMaster\Permission\PermissionRegistrar::class]->forgetCachedPermissions();

        // 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

        // this can be done as separate statements
        $role = Role::create(['name' => 'writer']);
        $role->givePermissionTo('edit articles');

        // or may be done by chaining
        $role = Role::create(['name' => 'moderator'])
            ->givePermissionTo(['publish articles', 'unpublish articles']);

        $role = Role::create(['name' => 'super-admin']);
        $role->givePermissionTo(Permission::all());
    }
}

扩展

如果您需要扩展现有的 RolePermission 模型,请注意以下内容

  • 您的 Role 模型需要扩展 CodeMaster\Permission\Models\Role 模型
  • 您的 Permission 模型需要扩展 CodeMaster\Permission\Models\Permission 模型

如果您需要替换现有的 RolePermission 模型,您需要注意以下事项

  • 您的 Role 模型需要实现 CodeMaster\Permission\Contracts\Role 协议
  • 您的 Permission 模型需要实现 CodeMaster\Permission\Contracts\Permission 协议

在两种情况下,无论是扩展还是替换,您都需要在配置中指定您的新模型。为此,您必须在发布配置后更新配置文件中的 models.rolemodels.permission

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

缓存

角色和权限数据被缓存以提高性能。

虽然我们建议不要更改缓存“键”名称,但如果您想更改过期时间,您可以在 config/permission.php 文件中的 cache 数组中这样做。请注意,自 v2.26.0 版本起,这里的 cache 条目现在是一个数组,而 expiration_time 是一个子数组条目。

当您使用内置的用于操作角色和权限的函数时,缓存会自动为您重置,并且关系会自动重新加载当前模型记录。

$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);

然而,如果您在数据库中直接操作权限/角色数据而不是调用提供的方法,那么除非您手动重置缓存,否则您将看不到应用程序中的更改。

手动缓存重置

要手动重置此包的缓存,您可以在您的应用代码中运行以下命令

$this->app->make(\CodeMaster\Permission\PermissionRegistrar::class)->forgetCachedPermissions();

或者您可以使用 artisan 命令

php artisan permission:cache-reset

缓存标识符

提示:如果您正在使用像 redismemcached 这样的缓存服务,并且您的服务器上还有其他网站在运行,那么您可能会遇到应用程序之间的缓存冲突。明智的做法是在 Laravel 的 /config/cache.php 中为每个应用程序设置唯一的缓存 prefix,这将防止其他应用程序意外地使用/更改您的缓存数据。

需要用户界面吗?

该包默认不包含任何屏幕,您需要自己构建。以下是一些启动选项:

测试

composer test

升级

如果您要从 v1 升级到 v2,@fabricecw 准备了一个存档,这可能使您的数据迁移更容易。您还需要删除旧的 laravel-permission.php 配置文件,发布新的 permission.php,并进行相应编辑。

变更日志

请参阅 CHANGELOG 了解最近更改的详细信息。

贡献

请参阅 CONTRIBUTING 了解详细信息。

安全性

如果您发现任何安全相关的问题,请通过电子邮件 freek@spatie.be 联系我们,而不是使用问题跟踪器。

明信片软件

您可以使用此软件包,但如果它进入您的生产环境,我们非常感谢您从家乡寄给我们一张明信片,注明您正在使用我们的哪个软件包。

我们的地址是:Spatie,Samberstraat 69D,2060 安特卫普,比利时。

我们将所有收到的明信片发布在我们的公司网站上

致谢

此软件包主要基于 Jeffrey Way 的优秀 Laracasts 课程,关于权限和角色。他的原始代码可以在 GitHub 的这个存储库中找到

特别感谢 Alex Vanderbist,他为 v2 做了大量工作,并感谢 Chris Brown 长期支持我们维护此软件包。

替代方案

支持我们

Spatie 是一家位于比利时安特卫普的网页设计公司。您可以在我们的网站上找到所有开源项目的概述 (在网站上)

如果您的业务依赖于我们的贡献,请与我们联系并在 Patreon 上支持我们。所有承诺都将专门用于分配人力以维护和开发新功能。

许可

MIT 许可证(MIT)。有关更多信息,请参阅 许可文件