tjgazel/laravel-docblock-acl
Laravel DocBlock ACL lets you define access control rules directly in PHP docblocks, keeping authorization next to your controller actions. Annotate methods with roles or permissions and the package enforces them automatically—less boilerplate, clearer, self-documenting code.
This release targets Laravel 6 and 7, so before anything else confirm your app is on that generation — composer require will fail hard on Laravel 8+ because of the framework constraints, illuminate/support and the laravel/ui view assumptions.
Minimal path:
php artisan migrate). Expect at least a permissions table, a roles/groups table, and a polymorphic pivot linking users to permission groups.vendor:publish) — the shipped views are written for laravel/ui + Bootstrap 4, so publish them if you intend to customise rather than writing your own UI.can-style check inside tinker before wiring anything into controllers.First real use case: gate a route or Blade block behind a permission, not a role. @can('posts.edit') / $user->can('posts.edit') keeps your authorisation checks stable when group composition changes — that is the whole point of supporting multiple groups per user, so lean on permission-level checks from day one rather than role-name checks.
Look at the package's trait (usually applied to the User model) first — that is where the relationship names and the permission-checking API live, and it dictates everything downstream.
Multiple groups per user. The headline of this release. A user can belong to several groups and inherits the union of their permissions. Model this explicitly in your tests: assert a user in editors + moderators can do both, and be explicit about whether a deny in one group overrides an allow in another (most of these packages are additive-only — no deny precedence — so verify before designing around it).
Authorisation at three layers.
->middleware('permission:posts.edit') or a policy that delegates to $user->can()).@can for UI visibility only. Never let a hidden button be the security boundary.Seeding as the source of truth. Keep permissions in a dedicated seeder or a config array the seeder iterates. On deploy, run the seeder idempotently (firstOrCreate) so renaming a permission in code produces a new row rather than a silent gap.
Cache the permission set per user. Permission checks appear in loops, policies, and Blade partials. Resolve the user's effective permission list once per request (or per user with a short TTL cache) and answer checks from memory. Invalidate on group attach/detach/permission-edit — that invalidation is where bugs live, so centralise it in model events or an observer rather than sprinkling Cache::forget in controllers. Be aware that most permission packages ship their own cache layer; check whether it is already on before adding a second.
Livewire Full-Page Components. With Route::get('/admin', AdminDashboard::class), route middleware still runs, but anything rendered client-side after the initial mount is only as safe as the server-side checks inside the component. Put the can() check in mount() and re-check in every action method — a full-page component's actions are separate requests and do not re-run mount(). Component authors often forget this.
Views with laravel/ui + Bootstrap 4. Treat the shipped views as scaffolding you will replace. If your app is on Tailwind, don't fight the markup — publish the views, strip the Bootstrap classes, and keep only the form/route structure. The controllers and validation behind them are the reusable part.
Group management UI. A simple pivot-sync form ($user->groups()->sync($request->groups)) with a multi-select is usually enough; resist building a matrix editor until someone actually asks.
laravel/ui is legacy scaffolding. Laravel dropped it from the default install in 8.x in favour of Breeze/Jetstream. If you're on Laravel 7 the package's auth views will drop in cleanly; on anything newer they won't, and laravel/ui will fight your existing frontend.laravel/ui 4.x. If you later upgrade the frontend, the v4 markup here (.form-group, .custom-select, .input-group-append) will break. Budget a markup pass, not just a CSS swap.with('groups.permissions')) on the user object used for the page, or rely on caching.sync(). The classic symptom: a user gains a permission in the admin UI but the change only appears after the next request, or worse, after a queue worker restart. Confirm the package's cache key includes the user id and that attach/detach events flush it; add your own observer if not.sync() runs in an action — and a stale property will happily re-save an outdated permission set. Re-read the user inside the action before syncing.resource.action convention (posts.edit, users.delete) and a constant list or enum in PHP that both the seeder and your checks reference. Free-typed strings are the most common source of "it says I can't, but I'm in the group" bugs.users table. Check the migration order and publish/timestamp them before running migrate:fresh in CI.How can I help you explore Laravel packages today?