webard/laravel-access-control 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

webard/laravel-access-control

Composer 安装命令:

composer require webard/laravel-access-control

包简介

A modular access control library for Laravel applications.

README 文档

README

Laravel Access Control

Latest Version on Packagist Total Downloads

A modular access control library for Laravel applications that uses enum-based permissions and a voter system. Perfect for modular monolith architectures where different modules can define their own permission logic and extend existing.

Example

Imagine two modules: Product and ProductGallery. The Product module knows nothing about ProductGallery, but ProductGallery should block product deletion until all galleries are removed.

Product module defines the permission:

enum ProductPermission: string implements PermissionDefinition
{
    case Delete = 'product.delete';
}

ProductGallery module registers a voter to add its constraint:

// In ProductGalleryServiceProvider
$registry = resolve(VoterRegistry::class);

$registry->register(
    ProductPermission::Delete,

    function (User $user, Product $product = null): Response {
        if ($product && $product->galleries()->exists()) {
            return Response::deny('Cannot delete product with galleries.');
        }
        return Response::allow();
    }
);

This library allows achieving such behavior without tightly coupling the two modules.

Key Features

  • 🔐 Enum-based permissions - Define permissions as PHP enums for type-safety and IDE autocomplete
  • 🗳️ Voter system - Replace Laravel Policies with flexible voters that can be registered from any module
  • 📦 Modular architecture - Each module can register its own voters without modifying core logic
  • 🏷️ Permission metadata - Add names, descriptions, and groups to permissions via PHP attributes
  • Laravel Gate integration - Works seamlessly with Laravel's authorization system

When to Use This Package

This package is designed primarily for modular monolith architectures where your application is split into independent modules (e.g., using nWidart/laravel-modules or InterNACHI/modular).

The key advantage of this package is that voters can be registered from any module, allowing each module to define its own authorization constraints without modifying the core application or other modules.

When NOT to Use This Package

If you're building a traditional Laravel monolith without modular architecture, you probably don't need this package. In that case, the following solutions are sufficient:

Requirements

  • PHP 8.4+
  • Laravel 12.0+

Installation

You can install the package via composer:

composer require webard/laravel-access-control

Usage

1. Define Permissions as Enums

Create an enum that implements PermissionDefinition:

<?php

namespace App\Permissions;

use Webard\LaravelAccessControl\Contracts\PermissionDefinition;

enum ProductPermission: string implements PermissionDefinition
{
    case View = 'product.view';
    case Create = 'product.create';
    case Update = 'product.update';
    case Delete = 'product.delete';
}

Tip: In modular applications, consider prefixing permission values with your module name (e.g., pim-module.product.view, inventory-module.stock.update) to avoid conflicts between modules and make it clear which module owns each permission.

2. Register Permissions

Register your permission enums in a service provider:

<?php

namespace Modules\Category\Providers;

use Illuminate\Support\ServiceProvider;
use Webard\LaravelAccessControl\PermissionRegistry;
use Modules\Category\Permissions\CategoryPermission;

class CategoryModuleServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $registry = resolve(PermissionRegistry::class);
        
        $registry->register([
            CategoryPermission::class,
        ]);
    }
}

3. Register Voters

Voters allow you to add custom authorization logic to permissions. The main advantage is that voters can be registered from any module, making them perfect for modular monolith architectures.

Using Closures

<?php

namespace Modules\Product\Providers;

use Illuminate\Auth\Access\Response;
use Illuminate\Support\ServiceProvider;
use Webard\LaravelAccessControl\PermissionRegistry;
use Webard\LaravelAccessControl\VoterRegistry;
use App\Models\User;
use Modules\Category\Models\Category;
use Modules\Category\Permissions\CategoryPermission;
use Modules\Product\Permissions\ProductPermission;

class ProductModuleServiceProvider extends ServiceProvider
{
    public function boot(): void {
        $registry = resolve(PermissionRegistry::class);
        
        $registry->register([
            ProductPermission::class,
        ]);

        $registry = resolve(VoterRegistry::class);

        $registry->register(
            CategoryPermission::Delete,

            function (User $user, Category $category = null): Response {
                if ($category->products()->exists()) {
                    return Response::deny(
                        'Cannot delete category with assigned products.'
                    );
                }

                return Response::allow();
            }
        );
    }
}

Using Voter Classes

For more complex logic, create dedicated voter classes with the #[VoterForPermission] attribute:

<?php

namespace App\Voters;

use Illuminate\Auth\Access\Response;
use Webard\LaravelAccessControl\Attributes\VoterForPermission;
use App\Models\User;
use App\Models\Currency;
use App\Models\Channel;
use App\Permissions\CurrencyPermission;

final class CurrencyVoter
{
    #[VoterForPermission(CurrencyPermission::Delete)]
    public function preventDeletingUsedByChannels(User $user, Currency|string|null $currency): Response
    {
        if (!$currency instanceof Currency) {
            return Response::allow();
        }
        
        if (Channel::query()->whereJsonContains('currencies', $currency->code)->exists()) {
            return Response::deny('Some channels are using this currency.');
        }

        return Response::allow();
    }

    #[VoterForPermission(CurrencyPermission::Update)]
    public function preventUpdatingDefaultCurrency(User $user, Currency|string|null $currency): Response
    {
        if ($currency instanceof Currency && $currency->is_default) {
            return Response::deny('Cannot modify the default currency.');
        }

        return Response::allow();
    }
}

Register the voter class:

$registry = resolve(VoterRegistry::class);

$registry->register(CurrencyVoter::class);

// Or register multiple classes at once
$registry->register([
    CurrencyVoter::class,
    ProductVoter::class,
    ChannelVoter::class,
]);

4. Using Authorization

The package integrates with Laravel's Gate, so you can use standard authorization methods:

// Using Gate
Gate::allows(ProductPermission::View, $product);
Gate::authorize(ProductPermission::Delete, $product);

// Using the User model
$user->can(ProductPermission::Update, $product);
$user->cannot(ProductPermission::Delete, $product);

// In controllers
$this->authorize(ProductPermission::Update, $product);

// In Blade templates
@can(ProductPermission::View, $product)
    <a href="{{ route('products.show', $product) }}">View</a>
@endcan

5. Adding Permission Metadata (Optional)

Enhance your permissions with names, descriptions, and groups using PHP attributes:

Permission Groups

First, create a permission group:

<?php

namespace App\Permissions\Groups;

use Webard\LaravelAccessControl\Contracts\PermissionGroupDefinition;

final class ProductGroup implements PermissionGroupDefinition
{
    public function getName(): string
    {
        return 'Products';
    }

    public function getDescription(): ?string
    {
        return 'Permissions related to product management';
    }

    public function getSlug(): string
    {
        return 'products';
    }
}

Enhanced Permission Enum

<?php

namespace Modules\Product\Permissions;

use Webard\LaravelAccessControl\Contracts\PermissionDefinition;
use Webard\LaravelAccessControl\Attributes\PermissionGroup;
use Webard\LaravelAccessControl\Attributes\PermissionName;
use Webard\LaravelAccessControl\Attributes\PermissionDescription;
use Modules\Product\PermissionGroups\ProductGroup;

#[PermissionGroup(ProductGroup::class)]
enum ProductPermission: string implements PermissionDefinition
{
    #[PermissionName('View Products')]
    #[PermissionDescription('Allows viewing product details')]
    case View = 'product.view';

    #[PermissionName('Create Products')]
    #[PermissionDescription('Allows creating new products')]
    case Create = 'product.create';

    #[PermissionName('Update Products')]
    #[PermissionDescription('Allows modifying existing products')]
    case Update = 'product.update';

    #[PermissionName('Delete Products')]
    #[PermissionDescription('Allows removing products from the system')]
    case Delete = 'product.delete';
}

Retrieving Permission Metadata

use Webard\LaravelAccessControl\PermissionCollection;

$collection = resolve(PermissionCollection::class);

// Get all permissions grouped
$grouped = $collection->getGroupedPermissions();

// Get flat list of all permissions
$permissions = $collection->getPermissions();

This is useful for building permission management UIs.

How Voters Work

  1. When a permission check is performed via Laravel's Gate, the package first verifies if the user has the permission (via $user->hasPermissionTo())
  2. If the user has the permission, all registered voters for that permission are executed
  3. If any voter returns Response::deny(), the authorization fails
  4. Only if all voters return Response::allow(), the authorization succeeds

This allows different modules to add constraints to permissions without knowing about each other.

Voters vs Policies

Feature Laravel Policies Voters
Location Single class per model Can be anywhere
Modularity Coupled to model Fully decoupled
Multiple handlers No Yes
Cross-module logic Difficult Easy
Registration Automatic by convention Explicit

Model Setup

The model on which authorization checks are performed (typically User) must implement the AuthControllable interface.

This package provides three traits for managing permissions:

HasRoles (Recommended)

Use this trait when users receive permissions only through roles. This is the recommended approach for most applications.

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Webard\LaravelAccessControl\Contracts\AuthControllable;
use Webard\LaravelAccessControl\Traits\HasRoles;

class User extends Authenticatable implements AuthControllable
{
    use HasRoles;

    /**
     * Get roles assigned to the user.
     */
    public function getRoles(): iterable
    {
        return $this->roles; // Your roles relationship
    }
}

HasPermissions

Use this trait for models that store permissions directly (e.g., a Role model). This trait provides givePermissionTo() and revokePermissionTo() methods.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Webard\LaravelAccessControl\Contracts\AuthControllable;
use Webard\LaravelAccessControl\Traits\HasPermissions;

class Role extends Model implements AuthControllable
{
    use HasPermissions;

    protected $casts = [
        'permissions' => 'array',
    ];

    protected function getPermissions(): Collection
    {
        return new Collection($this->permissions ?? []);
    }

    protected function setPermissions(Collection $permissions): void
    {
        $this->permissions = $permissions->toArray();
        $this->save();
    }
}

You can also use this trait directly on User.

HasRolesAndPermissions

Use this trait when users can receive permissions both through roles AND directly. Permissions are checked in both sources.

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Collection;
use Webard\LaravelAccessControl\Contracts\AuthControllable;
use Webard\LaravelAccessControl\Traits\HasRolesAndPermissions;

class User extends Authenticatable implements AuthControllable
{
    use HasRolesAndPermissions;

    protected $casts = [
        'permissions' => 'array',
    ];

    public function getRoles(): iterable
    {
        return $this->roles;
    }

    protected function getPermissions(): Collection
    {
        return new Collection($this->permissions ?? []);
    }

    protected function setPermissions(Collection $permissions): void
    {
        $this->permissions = $permissions->toArray();
        $this->save();
    }
}

Testing

composer test

License

The MIT License (MIT). Please see License File for more information.

webard/laravel-access-control 适用场景与选型建议

webard/laravel-access-control 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 2.34k 次下载、GitHub Stars 达 7, 最近一次更新时间为 2026 年 01 月 05 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「acl」 「laravel」 「modular」 「roles」 「permissions」 「rbac」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 webard/laravel-access-control 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 webard/laravel-access-control 我们能提供哪些服务?
定制开发 / 二次开发

基于 webard/laravel-access-control 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 2.34k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 7
  • 点击次数: 23
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 7
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-01-05