sylius/rbac-bundle
Sylius RBAC Bundle adds role-based access control to Sylius applications, with roles, permissions, and resource-level authorization for admin and storefront access. It integrates with Symfony Security to simplify managing user privileges securely.
This is a Symfony 2-era bundle, not a Laravel package. There is no service provider, facade, config file, migration set, or Artisan command to publish. You cannot composer require it into a modern Laravel app without hitting dependency conflicts against Symfony/Doctrine constraints. The realistic day-to-day use is as a reference implementation for hierarchical RBAC that you port into your own Laravel codebase.
Where to look first, in order:
composer.json — confirm the version pins and the fact that it is archived. This tells you immediately that you are reading, not installing.Role, Permission, RoleInterface, PermissionInterface. This is the schema you actually want: a self-referencing roles table plus a role↔permission pivot and a user↔role pivot.Gate.First use case to validate the design end to end: create ROLE_ADMIN > ROLE_MANAGER > ROLE_USER, attach permissions such as user.create and user.delete to the appropriate roles, assign a role to a user, and assert that an inherited permission resolves as granted. If you can prove that loop in ~50 lines of Eloquent, you have extracted everything the package is worth to you.
Model the hierarchy as data, not config. The package's core idea is a self-referencing role table (parent_id) plus a pivot. Port it directly:
// roles: id, name, parent_id
// permissions: id, name
// permission_role: role_id, permission_id
// role_user: role_id, user_id
Resolve permissions by walking ancestors. Do not flatten this into Symfony-style security.role_hierarchy arrays if roles are user-editable; the database is the source of truth.
public function permissionsFor(User $user): array
{
return $user->roles()
->with('ancestors.permissions') // eager-load the chain
->get()
->flatMap(fn ($role) => $role->selfAndAncestors())
->flatMap(fn ($role) => $role->permissions->pluck('name'))
->unique()
->values()
->all();
}
Expose it through a single authorization surface. Wrap the resolver in one service and register Gates against it — never sprinkle in_array('user.delete', ...) or role-string checks through controllers.
foreach (['user.create', 'user.delete', 'order.refund'] as $perm) {
Gate::define($perm, fn (User $u) => $this->rbac->can($u, $perm));
}
// or generically, via a single before() closure or Policy base class
Seed the hierarchy idempotently. Roles and permissions are configuration-like data; use updateOrCreate in a seeder or a rbac:sync command so redeploys don't duplicate or clobber. Treat the seeder as code-reviewed and versioned.
Cache the resolved map and bust it deliberately. Role graphs change rarely; permission expansion on every request does not scale in spirit. Cache user:{id}:permissions (or a role→permissions map keyed by role) and invalidate on role/permission/assignment writes through model events.
For non-role checks, map the package's voter concept to Policies. The bundle's extension point is Symfony voters; the Laravel equivalent is a Policy or a closure added to Gate. Anything that isn't pure role/permission (ownership, tenant scoping) belongs there, not in the RBAC resolver.
sylius/rbac component, you own it and its CVE response — budget for that or don't do it.$role->parent->parent->... access will fan out queries. Prefer an eager-loaded ancestor path, a recursive CTE, or a materialized closure table if depth grows.role.permissions pivot writes, role parent changes, and user role assignment requires model event coverage on the pivot models too, not just Role/Permission. Missing one produces stale grants — the worst kind of bug in authorization.How can I help you explore Laravel packages today?