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

H Rbac Laravel Package

dlnsk/h-rbac

Hierarchical role-based access control for Laravel. Define roles and permissions, assign roles to users, and inherit permissions through role hierarchies. Includes middleware, Blade directives, caching, and a simple API for checking access throughout your application.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require dlnsk/h-rbac
php artisan vendor:publish --provider="Dlnsk\HierarchicalRBAC\RBACServiceProvider"
php artisan migrate

Publishing gives you config/h-rbac.php and the role/permission/pivot migrations. Add the trait to your authenticatable model:

use Dlnsk\HierarchicalRBAC\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
}

If you need custom behavior on roles or permissions (extra columns, relationships, scopes), extend the package models and point the config at your classes:

// config/h-rbac.php
'models' => [
    'role'       => \App\Models\Role::class,
    'permission' => \App\Models\Permission::class,
],

Where to look first, in order:

  1. config/h-rbac.php — model mapping, table names, cache settings, callback registry.
  2. The published migration — confirm the schema, especially parent_id on permissions and the pivot tables.
  3. Dlnsk\HierarchicalRBAC\Traits\HasRoles — this is the real public API (assignRole, removeRole, hasRole, can integration).
  4. The service provider — shows how the package hooks into Laravel's gate pipeline, which explains most of the surprising behavior.

First use case — seed a role with a permission, assign it, check it through the native gate:

$role = Role::create(['name' => 'editor']);
$permission = Permission::create(['name' => 'edit-post']);

$role->permissions()->attach($permission);
$user->assignRole('editor');

$user->can('edit-post'); // true

Because it sits on top of native gates, @can, Gate::allows, middleware (can:edit-post), and policies continue to work — you are adding a permission source, not replacing the gate system.

Implementation Patterns

Model the permission tree explicitly. Hierarchy is the whole point: assign the parent, inherit the children.

$manage = Permission::create(['name' => 'manage-posts']);
$edit   = Permission::create(['name' => 'edit-post',   'parent_id' => $manage->id]);
$delete = Permission::create(['name' => 'delete-post', 'parent_id' => $manage->id]);

$role->permissions()->sync([$manage->id]); // editor gets manage-posts only
$user->assignRole('editor');

$user->can('delete-post'); // true — inherited

Keep the tree shallow (2–3 levels). Deep trees make “why does this user have this ability?” hard to answer and make cache invalidation harder to reason about.

Seed idempotently. Use firstOrCreate/updateOrCreate keyed by permission name, then resolve parents by name so seeders can be re-run safely in CI and local resets:

$manage = Permission::firstOrCreate(['name' => 'manage-posts']);
Permission::firstOrCreate(['name' => 'edit-post'], ['parent_id' => $manage->id]);

Treat roles as bundles, not users. Assign roles at lifecycle points (registration, invite acceptance, team membership). Avoid ad-hoc assignRole sprinkled in controllers; centralize it in a service or observer so you can invalidate caches in one place.

Use callbacks for ownership and context-dependent checks. The callback mechanism is what distinguishes this package from plain RBAC — it lets a permission consult the actual model being acted on:

// config/h-rbac.php
'callbacks' => [
    'update-post' => \App\Hrbac\Callbacks\UpdatePost::class,
],
namespace App\Hrbac\Callbacks;

class UpdatePost
{
    public function check($user, $post)
    {
        return $post->author_id === $user->id;
    }
}
$user->can('update-post', $post); // callback receives the extra argument

Prefer class-strings over closures in config — closures break php artisan config:cache.

Compose with policies instead of fighting them. Let the package answer “does this role grant this ability?” and let policies answer “is this particular model in a valid state?”. If both a policy method and a permission share a name, decide deliberately which should win and test it — the gate short-circuits can surprise you.

Super-admin via Gate::before. The package resolves through the gate; a global before-hook is the cleanest escape hatch:

Gate::before(fn ($user) => $user->isSuperAdmin() ? true : null);

Return null (not false) so normal resolution continues for everyone else.

Invalidate caches after bulk changes. If caching is enabled, attaching/detaching permissions or editing the tree leaves users with stale abilities. Flush the affected user's cache (or disable caching in config/h-rbac.php during development) after seeders and admin CRUD operations.

Test through the gate, not the trait. Assertions against $user->can(...) and HTTP responses catch integration mistakes (middleware, policies, cache) that trait-level assertions miss:

$this->actingAs($editor)->get("/posts/{$post->id}/edit")->assertOk();

Gotchas and Tips

  • Hierarchy direction is easy to invert. A parent permission grants its descendants, not ancestors. Write one test for “parent grants child” and one for “child does not grant parent” before building on it.
  • Cached abilities outlive role edits. After any attach, detach, sync, or tree reshuffle, clear the relevant cache. This is the most common “it works in tinker but not in the browser” bug with this package.
  • Table name collisions. Default roles/permissions tables will collide with spatie/laravel-permission if both are installed. Rename via config/h-rbac.php or run only one.
  • config:cache and callbacks. Closures in the callback map are silently dangerous in production. Always register class-strings, and re-run config:cache after changing mappings.
  • Callback return semantics. Confirm whether null falls through to the normal role check or denies — this determines whether a callback can be a partial override. Encode the answer in a test.
  • Extra can() arguments are ignored without a callback. Calling $user->can('update-post', $post) when no callback or policy exists will not magically scope the check; the argument is dropped.
  • N+1 on permission checks. Eager-load roles.permissions when listing users or rendering menus, and consider a per-request memoized “can” map for views that check dozens of abilities.
  • Orphaned children on delete. Deleting a parent permission without handling descendants leaves dangling parent_id values. Use a FK with cascade, or reparent in an observer.
  • Gate ordering conflicts. If another package or a legacy Gate::before returns a non-null value, the package never runs. Audit Gate::before/Gate::after registrations when adding this to an existing app.
  • No admin UI. You are building the role/permission CRUD yourself. Budget for it; the package only gives you models, relations, and checking.
  • Small ecosystem. 37 stars means fewer eyes on edge cases. Pin the version, read the trait and provider source (they are small), wrap the integration in your own tests, and keep the permission names in one enum/constants class so a future migration to another package is mechanical.
  • Debugging tip. In tinker, inspect $user->roles and each role's permissions, then compare to the tree; most “wrong answer” reports are stale cache or a mistyped permission name (the check is name-based and fails closed).
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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
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