modularize-rbac / laravel
Laravel bridge for modularize-rbac/core: Eloquent repositories, HTTP controllers, migrations, and optional Spatie permissions adapter.
Requires
- php: ^8.2
- laravel/framework: ^11.0|^12.0
- modularize-rbac/core: ^1.1
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.8
- spatie/laravel-permission: ^6.24
Suggests
- spatie/laravel-permission: ^6.0 — opt-in adapter that mirrors permissions into Spatie's `role_has_permissions` pivot so hosts using the HasRoles trait keep working with `$user->can()`. Enable via `config('access.spatie.enabled')`.
- dev-main
- v2.0.0
- v1.0.0
- dev-pr/v2.7-release
- dev-pr/v2.6-commands-policy
- dev-pr/v2.5-audit-log
- dev-pr/v2.4-has-access-permissions
- dev-pr/v2.3-decouple-spatie
- dev-chore/tighten-core-constraint
- dev-chore/rename-vendor
- dev-pr/7-tighten-core-constraint
- dev-pr/6-release-v1
- dev-pr/5-spatie-adapter
- dev-pr/4-http-adapter
- dev-pr/3-eloquent-adapter
- dev-pr/0-rename-and-packaging
This package is auto-updated.
Last update: 2026-05-23 15:45:51 UTC
README
Laravel bridge for modularize-rbac/core. Ships Eloquent repositories, HTTP controllers, FormRequests, migrations, an audit log pipeline, console commands, and an optional Spatie permission adapter.
What v2.0 ships
A drop-in admin RBAC layer with:
- Modules — feature catalog with hierarchy, soft-delete, sort order, i18n.
- Roles — guard-scoped, tenant-aware, level-ordered, system-flag protected.
- Permissions —
{slug}.{action}names, package-owned (Spatie is optional). - Role × Module matrix — flag-based UI translated to action names by a domain service.
- Languages + Translations — polymorphic translations with locale fallback.
- REST API —
/api/admin/modules,/roles,/languages,/audit. - Audit log — every domain event is auto-persisted to
access_audit_log. HasAccessPermissionstrait — drop on your User to make$user->can('events.view')work without Spatie.AccessAdminPolicy— turn-key Gate::before for the package'sadmin.*abilities.- Console commands —
access:diagnose,access:sync-spatie,access:audit. - Spatie integration is opt-in — the package works whether or not
spatie/laravel-permissionis installed.
Architecture
┌──────────────────────────────────────────────────────────────┐
│ Infrastructure (this package) │
│ Eloquent · Mappers · Controllers · Resources · Audit │
│ Console commands · Policies · Spatie adapter (opt-in) │
└──────────────────────────┬───────────────────────────────────┘
│ implements ports
┌──────────────────────────▼───────────────────────────────────┐
│ modularize-rbac/core (framework-agnostic, PHP 8.2+) │
│ Use-cases · Domain entities · Ports · Events · Read models │
└──────────────────────────────────────────────────────────────┘
Install
composer require modularize-rbac/laravel php artisan vendor:publish --tag=access-config php artisan migrate
Edit config/access.php and point tenant_model at your tenant class or leave null for single-tenant setups.
Host wiring
config/auth.php
Define the admin guard the package defaults to:
'guards' => [ 'admin' => [ 'driver' => 'sanctum', 'provider' => 'admin_users', ], ],
HasAccessPermissions on your User
use ModularizeRbac\Laravel\Concerns\HasAccessPermissions; class User extends Authenticatable { use HasAccessPermissions; }
Provides:
$user->rbacRoles()BelongsToMany via therole_userpivot$user->canAccess('events.view')— direct lookup against the package schema
The AccessServiceProvider registers Gate::before so $user->can('events.view') works through Laravel's normal authorization flow.
Tenant context (optional)
Multi-tenant hosts bind the current tenant id in the container from their tenant-resolution middleware:
$app->instance('access.current_tenant_id', (string) $request->user()->organization_id);
TenantContext::currentTenantId() reads this value. Single-tenant hosts never bind the key.
Spatie integration (optional)
spatie/laravel-permission is in suggest since v2.0. Install it alongside if you want role_has_permissions kept in sync (so Spatie's HasRoles trait keeps working on a different User model):
composer require spatie/laravel-permission
// config/access.php 'spatie' => [ 'enabled' => null, // null = auto, true = force on, false = force off ],
REST API
All routes under config('access.route_prefix') (default api/admin):
| Method | URL | Action |
|---|---|---|
| GET | /modules | List modules |
| POST | /modules | Create |
| GET | /modules/{id} | Show |
| PUT | /modules/{id} | Update |
| DELETE | /modules/{id} | Soft delete |
| GET | /roles | List roles |
| GET | /roles/{id} | Show + matrix |
| PUT | /roles/{id} | Update display_name + translations |
| PUT | /roles/{id}/modules | Sync the role's permission matrix |
| GET | /languages | List |
| POST | /languages | Create |
| GET | /languages/{id} | Show |
| PUT | /languages/{id} | Update |
| DELETE | /languages/{id} | Delete (rejects default) |
| PUT | /languages/{id}/default | Mark as default |
| GET | /audit | List audit entries (?event=&actor_id=&tenant_id=&since=&until=&limit=&offset=) |
Console commands
php artisan access:diagnose— pre-deploy health check.php artisan access:sync-spatie [--dry-run]— force resync of every role-module binding into Spatie's pivot.php artisan access:audit [--event= --actor= --tenant= --since= --until= --limit= --format=table|json]— query the audit log.
Authorization model
Two layers:
-
User layer —
Gate::before(registered by the ServiceProvider) calls$user->canAccess($ability)when the User has theHasAccessPermissionstrait. Resolvesevents.view-style abilities directly fromrole_user+role_module_permission+module_permissions. -
Admin layer —
AccessAdminPolicy(the defaultconfig('access.policies.admin')) wraps the samecanAccess()check but scoped toadmin.*abilities the package's use-cases consult (admin.modules.view,admin.audit.view, ...). Hosts override via config.
To grant admin.modules.view, create a module with slug admin.modules, bind it to a role with is_reading_allowed = true, and assign the role to the user via role_user.
Calling use-cases directly
Every use-case is container-resolvable:
use ModularizeRbac\Core\Application\Module\CreateModule\CreateModule; use ModularizeRbac\Core\Application\Module\CreateModule\CreateModuleInput; $module = app(CreateModule::class)->execute(new CreateModuleInput( slug: 'billing', name: 'Billing', redirect: '/billing', icon: 'receipt', rootModuleId: null, sortOrder: 10, ));
Upgrading from v1.x
See CHANGELOG.md for breaking changes and step-by-step upgrade notes.
Layout
.
├── composer.json
├── config/access.php
├── database/migrations/ # v2.0 schema (idempotent)
├── routes/api.php
├── src/
│ ├── AccessServiceProvider.php
│ ├── Audit/ # AuditingListener
│ ├── Authorization/ # GateAuthorizer, AccessAdminPolicy
│ ├── Concerns/ # HasAccessPermissions trait
│ ├── Console/ # diagnose / sync-spatie / audit
│ ├── Eloquent/
│ │ ├── Mappers/ # Entity <-> Eloquent
│ │ └── Repositories/ # Implement core ports
│ ├── Events/ # LaravelEventDispatcher
│ ├── Http/ # Controllers / FormRequests / Resources
│ ├── Localization/ # LaravelLocaleResolver
│ ├── Models/ # Persistence DTOs
│ ├── Persistence/ # Clock / IdGenerator / UnitOfWork
│ ├── Spatie/ # Optional permission gateway
│ ├── Tenant/ # LaravelTenantContext
│ └── Translations/ # TranslationApplier
└── tests/ # Pest + Testbench (matrix: with/without Spatie)