定制 sirix/mezzio-rbac 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

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

sirix/mezzio-rbac

Composer 安装命令:

composer require sirix/mezzio-rbac

包简介

RBAC authorization package for Mezzio framework with optional attribute-based support

README 文档

README

Latest Stable Version Total Downloads Latest Unstable Version License PHP Version Require

RBAC authorization package for Mezzio with PSR-15 middleware and optional PHP attribute integration.

Installation

composer require sirix/mezzio-rbac

Package is auto-registered via extra.laminas.config-provider.

Core Concepts

Actor

Current subject is represented by ActorInterface.

use Sirix\Mezzio\Rbac\Actor\Actor;

$actor = new Actor(['editor', 'moderator']);

Guest fallback is provided by Sirix\Mezzio\Rbac\Actor\GuestActor.

Authorization paths

The package has two authorization paths:

Use case Service
HTTP request authorization RequestGuardInterface / AuthorizeMiddleware
Non-HTTP service or CLI authorization GuardInterface

GuardInterface remains request-independent. It uses ActorProviderInterface and is useful from services, CLI commands, or application-managed contexts.

AuthorizeMiddleware uses RequestGuardInterface so it can authorize against the actor stored on the current PSR-7 request.

Guard

Main non-HTTP authorization entrypoint:

use Sirix\Mezzio\Rbac\Contract\GuardInterface;

$guard->allows('posts.update');
$guard->denies('admin.panel');
$guard->authorize('posts.delete');

authorize() throws Sirix\Mezzio\Rbac\Exception\AuthorizationException with HTTP status 403.

Request Guard

HTTP-aware authorization entrypoint:

use Psr\Http\Message\ServerRequestInterface;
use Sirix\Mezzio\Rbac\Contract\RequestGuardInterface;

final readonly class PostHandler
{
    public function __construct(private RequestGuardInterface $guard) {}

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $this->guard->authorize($request, 'posts.update', [
            'postId' => $request->getAttribute('id'),
        ]);

        // ...
    }
}

For route-level protection, prefer AuthorizeMiddleware or #[Can].

Permissions

Permissions use dot-notation and wildcard matching:

  • posts.read
  • posts.update
  • admin.users.delete
  • posts.* (greedy match)
  • admin.*.delete (exact segment count)

Example:

use Sirix\Mezzio\Rbac\Contract\PermissionsInterface;
use Sirix\Mezzio\Rbac\Rule\ForbidRule;

$permissions->addRole('editor');
$permissions->associate('editor', 'posts.*');
$permissions->associate('editor', 'posts.delete', ForbidRule::class);

Resolution rules:

  • exact match beats wildcard;
  • more specific wildcard beats broader wildcard;
  • latest association wins when specificity is equal;
  • another actor role may still grant access if one role forbids it.

Conflict Resolution: Allow wins over Deny

The package follows an "Allow wins over Deny" policy. If an actor has multiple roles, access is granted if at least one role allows the permission.

Example: if a user has both user allowed posts.read and banned forbidden posts.read, the user still has access because the user role grants it.

Wildcard Matching

Permissions use dot-notation and support greedy terminal wildcard matching:

  • posts.* matches posts.read, posts.update, and nested resources like posts.read.history.
  • admin.* grants access to all sub-resources of any depth.
  • Non-terminal wildcards, for example admin.*.delete, still require exact segment positioning.

Rules

Built-in rules:

  • Sirix\Mezzio\Rbac\Rule\AllowRule
  • Sirix\Mezzio\Rbac\Rule\ForbidRule

Custom rules implement Sirix\Mezzio\Rbac\Contract\RuleInterface:

use Sirix\Mezzio\Rbac\Contract\ActorInterface;
use Sirix\Mezzio\Rbac\Contract\RuleInterface;

final class OwnPostRule implements RuleInterface
{
    public function allows(ActorInterface $actor, string $permission, array $context): bool
    {
        return ($context['ownerId'] ?? null) === ($context['userId'] ?? null);
    }
}

Then associate it with a permission:

$permissions->associate('user', 'posts.update', OwnPostRule::class);

HTTP Integration

Actor resolution for HTTP requests

AuthorizeMiddleware resolves the actor from the current request through RequestActorProviderInterface.

The default provider reads this request attribute:

'rbac' => [
    'request_actor_attribute' => 'sirix.authentication.actor',
]

This default matches sirix/mezzio-authentication, which stores the authenticated actor in sirix.authentication.actor.

If the request attribute contains an RBAC ActorInterface, it is used directly. If it contains an authentication-like object with getRoles(), it is adapted to an RBAC actor. Missing or invalid actor values fall back to GuestActor.

ContainerActorProvider is still available for non-request usage through GuardInterface, but it should not be used to resolve the current HTTP user.

Metadata resolution

AuthorizeMiddleware resolves permission metadata in this order:

  1. request attribute sirix.rbac.permission;
  2. matched route option sirix.rbac.permission;
  3. if missing or empty, pass through without authorization.

Context follows the same order:

  1. request attribute sirix.rbac.context;
  2. matched route option sirix.rbac.context;
  3. empty array.

Context values map request attributes into rule context:

[
    'postId' => 'id', // context['postId'] = $request->getAttribute('id')
]

With standard Mezzio routing

Register AuthorizeMiddleware in your route pipeline and set permission/context as route options:

use Sirix\Mezzio\Rbac\Middleware\AuthorizeMiddleware;
use Sirix\Mezzio\Rbac\RbacAttribute;

$app->post('/posts/:id', [
    AuthorizeMiddleware::class,
    PostHandler::class,
], 'post.update')->setOptions([
    RbacAttribute::Permission->value => 'posts.update',
    RbacAttribute::Context->value => ['postId' => 'id'],
]);

You can also set request attributes before AuthorizeMiddleware runs. Request attributes take precedence over route options.

With sirix/mezzio-routing-attributes

When used with sirix/mezzio-routing-attributes:^1.0, #[Can] implements RouteAttributeModifierInterface. It injects AuthorizeMiddleware into the route pipeline and stores permission/context in route options.

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Sirix\Mezzio\Rbac\Attribute\Can;
use Sirix\Mezzio\Routing\Attributes\Attribute\Post;

#[Post('/posts/:id', name: 'post.update')]
#[Can('posts.update', ['postId' => 'id'])]
final class PostHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        // Authorization has already run before the handler.
    }
}

No manual middleware registration is needed for routes discovered by sirix/mezzio-routing-attributes.

Integration with sirix/mezzio-authentication

sirix/mezzio-authentication writes the current actor to request attribute sirix.authentication.actor. RBAC uses that attribute by default, so the usual route pipeline is:

AuthenticateMiddleware
  -> request attribute sirix.authentication.actor
  -> AuthorizeMiddleware
  -> RequestGuard
  -> permission lookup/rules

With attributes:

use Sirix\Mezzio\Authentication\Attribute\Authenticated;
use Sirix\Mezzio\Rbac\Attribute\Can;
use Sirix\Mezzio\Routing\Attributes\Attribute\Get;

#[Get('/admin', name: 'admin')]
#[Authenticated]
#[Can('admin.access')]
final class AdminHandler implements RequestHandlerInterface
{
    // ...
}

Expected behavior:

  • anonymous user is stopped by authentication;
  • authenticated non-admin user receives 403;
  • authenticated admin user receives 200.

Manual authorization from services

For services without a request, use GuardInterface:

use Sirix\Mezzio\Rbac\Contract\GuardInterface;

final readonly class PostService
{
    public function __construct(private GuardInterface $guard) {}

    public function deletePost(string $postId): void
    {
        $this->guard->authorize('posts.delete', [
            'postId' => $postId,
        ]);
    }
}

Storage Boundary

The package depends on contracts, not on concrete persistence.

Public storage contract:

  • Sirix\Mezzio\Rbac\Contract\PermissionStoreInterface

Read-only lookup contract used by authorization internals:

  • Sirix\Mezzio\Rbac\Contract\PermissionLookupInterface

Default implementation:

  • Sirix\Mezzio\Rbac\InMemoryPermissionStore

Later adapters can replace storage without changing the guard API.

Extensibility

Custom non-request actor provider

Use ActorProviderInterface for non-request authorization through GuardInterface:

use Sirix\Mezzio\Rbac\Actor\Actor;
use Sirix\Mezzio\Rbac\Contract\ActorInterface;
use Sirix\Mezzio\Rbac\Contract\ActorProviderInterface;

final readonly class MyActorProvider implements ActorProviderInterface
{
    public function __construct(private MyAuthService $auth) {}

    public function getActor(): ActorInterface
    {
        $user = $this->auth->getIdentity();

        return new Actor($user?->getRoles() ?? ['guest']);
    }
}

Custom request actor provider

Use RequestActorProviderInterface for HTTP authorization through RequestGuardInterface / AuthorizeMiddleware:

use Psr\Http\Message\ServerRequestInterface;
use Sirix\Mezzio\Rbac\Actor\Actor;
use Sirix\Mezzio\Rbac\Contract\ActorInterface;
use Sirix\Mezzio\Rbac\Contract\RequestActorProviderInterface;

final readonly class MyRequestActorProvider implements RequestActorProviderInterface
{
    public function getActor(ServerRequestInterface $request): ActorInterface
    {
        $user = $request->getAttribute('user');

        return new Actor($user?->roles() ?? ['guest']);
    }
}

Register it in your dependencies:

'dependencies' => [
    'factories' => [
        RequestActorProviderInterface::class => MyRequestActorProviderFactory::class,
    ],
],

Custom Permission Store

Implement PermissionStoreInterface to load permissions from a database, cache, or another source:

use Sirix\Mezzio\Rbac\Contract\PermissionAssociationInterface;
use Sirix\Mezzio\Rbac\Contract\PermissionStoreInterface;

final readonly class DatabasePermissionStore implements PermissionStoreInterface
{
    public function associationsForRole(string $role): array
    {
        // Fetch from DB and map to PermissionAssociation objects.
    }

    // ... implement other methods
}

Custom Rules

As shown in the Rules section, implement RuleInterface to add dynamic logic to permissions. Rules are resolved through RuleResolver, which can use the PSR-11 container or instantiate rule classes directly.

Main Components

  • GuardInterface / Guard
  • RequestGuardInterface / RequestGuard
  • ActorProviderInterface
  • RequestActorProviderInterface
  • RequestAttributeActorProvider
  • Permissions
  • PermissionLookupInterface
  • PermissionMatcher
  • RuleResolver
  • InMemoryPermissionStore
  • AuthorizeMiddleware
  • RbacAttribute
  • #[Can(...)]

sirix/mezzio-rbac 适用场景与选型建议

sirix/mezzio-rbac 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 434 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 05 月 08 日, 在 PHP 生态内属于活跃度较高的组件。

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

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

围绕 sirix/mezzio-rbac 我们能提供哪些服务?
定制开发 / 二次开发

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

BUG 修复 & 性能优化

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

项目外包 & 长期维护

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

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

统计信息

  • 总下载量: 434
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 44
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

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

其他信息

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