Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Zend Permissions Rbac Laravel Package

zendframework/zend-permissions-rbac

Role-based access control component for Zend Framework and PHP applications. Define hierarchical roles and permissions, attach assertions for dynamic rules, and check user access through a simple, flexible API.

Deep Wiki
Context7

Getting Started

This package is archived (last release 2019-06-25). For any new work, use laminas/laminas-permissions-rbac instead — it is the maintained fork with the same API, just a different namespace (Laminas\Permissions\Rbac instead of Zend\Permissions\Rbac). For a Laravel app, Laravel's native Gate/Policy system is usually the better default; use this package only if you need a framework-agnostic RBAC graph or are maintaining a legacy Zend/Laminas application.

If you must use it:

composer require zendframework/zend-permissions-rbac
# or, preferably:
composer require laminas/laminas-permissions-rbac

Look first at these classes:

  • Rbac — the registry and query entry point (addRole, getRole, hasRole, isGranted)
  • Role / RoleInterface — role name, permissions, parent/children
  • AssertionInterface — optional dynamic check during isGranted

Minimal first use case — a config-driven role hierarchy:

use Zend\Permissions\Rbac\Rbac;
use Zend\Permissions\Rbac\Role;

$rbac = new Rbac();

$rbac->addRole(new Role('guest'));
$rbac->addRole(new Role('member'), 'guest'); // parent must already exist
$rbac->addRole(new Role('admin'), 'member');

$rbac->getRole('member')->addPermission('posts.view');
$rbac->getRole('admin')->addPermission('posts.delete');

$rbac->isGranted('admin', 'posts.view');   // true — inherited from member/guest graph
$rbac->isGranted('member', 'posts.delete'); // false

The package only models roles and permissions. It does not manage users, persist role assignments, cache anything, or integrate with Laravel. You own those pieces.

Implementation Patterns

Bind one Rbac instance in a service provider. Build it from config or a repository, then resolve it through the container.

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Zend\Permissions\Rbac\Rbac;
use Zend\Permissions\Rbac\Role;

class RbacServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(Rbac::class, function () {
            $rbac = new Rbac();

            // Register parents before children, or topologically sort the config.
            foreach (config('rbac.roles', []) as $name => $definition) {
                $role = new Role($name);

                foreach ($definition['permissions'] ?? [] as $permission) {
                    $role->addPermission($permission);
                }

                $rbac->addRole($role, $definition['parent'] ?? null);
            }

            return $rbac;
        });
    }
}

Bridge to Laravel Gate once, then use normal Laravel authorization everywhere. A Gate::before callback lets @can, can: middleware, $this->authorize(), and policies continue to work.

use Illuminate\Support\Facades\Gate;
use Zend\Permissions\Rbac\Rbac;

public function boot(): void
{
    Gate::before(function ($user, string $ability) {
        $role = $user->rbac_role ?? null; // your user-to-role mapping

        if ($role === null) {
            return null; // fall through, do not deny globally
        }

        return app(Rbac::class)->isGranted($role, $ability)
            ? true
            : null; // returning false here would deny every ability
    });
}

Then application code stays Laravel-native:

// Controller
$this->authorize('posts.delete');

// Route
Route::delete('/posts/{post}', [PostController::class, 'destroy'])
    ->middleware('can:posts.delete');

// Blade
@can('posts.delete')
    <button>Delete</button>
@endcan

Use a permission naming convention. Treat permissions as stable ability strings such as posts.view, posts.create, posts.delete, users.impersonate. Define them as constants or an enum so typos fail fast and IDE completion works.

Support multi-role users by OR-ing checks. The package checks one role at a time. If users can have several roles, loop over them:

public function userIsGranted($user, string $permission): bool
{
    $rbac = app(Rbac::class);

    foreach ($user->roles as $role) {
        if ($rbac->isGranted($role, $permission)) {
            return true;
        }
    }

    return false;
}

Cache the role/permission definition, not necessarily the object. The role graph is often static per deployment. Cache the config array or a serialized structure keyed by an application version, then rebuild the Rbac object. This avoids stale object graphs after deploys and keeps invalidation explicit.

Use assertions only for coarse dynamic checks. AssertionInterface receives the Rbac instance, role, and permission string — not the Eloquent model. It is a poor fit for record-level ownership checks. Use Laravel policies for “can this user edit this specific post?” and keep RBAC for “does this role have the posts.edit ability at all?”

Test the permission matrix directly. A small table-driven test catches accidental inheritance changes:

public function testRoleMatrix(): void
{
    $rbac = app(Rbac::class);

    $this->assertTrue($rbac->isGranted('admin', 'posts.delete'));
    $this->assertTrue($rbac->isGranted('admin', 'posts.view'));  // inherited
    $this->assertFalse($rbac->isGranted('member', 'posts.delete'));
}

Gotchas and Tips

  • Archived package. Composer will warn that the package is abandoned. Do not build new features on it. Migrate to laminas/laminas-permissions-rbac; the migration is mostly a namespace swap from Zend\Permissions\Rbac to Laminas\Permissions\Rbac. Do not install both packages at once.
  • PHP version risk. The last Zend release predates modern PHP versions. On PHP 8.2/8.3+, verify installability and run your test suite. The Laminas fork is more likely to work on current PHP.
  • No deny rules. RBAC here is additive: child roles inherit parent permissions. There is no built-in “deny this permission for this child role.” If you need explicit denies, you need an additional layer, custom logic, or a different authorization model.
  • No wildcard permissions. Permissions are exact strings. posts.* will not match posts.delete unless you expand wildcards yourself or implement a custom wrapper/matcher. Prefer enumerating concrete permissions.
  • Parent must exist before child. addRole($role, 'parent-name') resolves the parent immediately and will fail if the parent has not been registered. Register parents first or topologically sort your config.
  • Duplicate role names may throw. Seeders and repeated bootstrapping should guard with $rbac->hasRole($name) or build from a clean instance.
  • Role names are case-sensitive. Normalize names (strtolower, constants) to avoid Admin vs admin bugs.
  • Unknown role behavior varies by version. Do not assume isGranted() throws for an unknown role; verify in your installed version. Fail closed in your wrapper.
  • Assertions do not grant missing permissions. An assertion is generally evaluated after the role is found to have the permission. Do not use an assertion to add a permission the role does not have; grant the base permission and use the assertion to narrow it.
  • Do not check roles in application code. Check permissions (can:posts.delete), not role names (if ($user->role === 'admin')). Role checks make the hierarchy rigid and spread policy across the codebase.
  • Beware mutable singletons under Octane. Role objects are mutable. A container singleton persists across requests in long-running workers. Do not mutate roles per request; rebuild or treat the graph as immutable.
  • Cache invalidation is your job. If roles and permissions come from the database, version your cache key and clear it when roles, permissions, or assignments change. A stale RBAC cache is an authorization bug.
  • Debug with the graph. Inspect getRole($name), its parent/children, and its permissions when a check unexpectedly fails. Most issues are a missing addPermission, a parent registered after a child, or a typo in the ability string.
  • Extension point: wrap Rbac in your own AuthorizationManager if you need wildcard matching, multi-role OR-ing, audit logging, or a cleaner Laravel-facing API. Keep the Zend/Laminas object as an internal implementation detail.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager