wnikk/laravel-access-rules
wnikk/laravel-access-rules adds flexible rule-based access control to Laravel. Define permissions for routes, models, and UI elements, then enforce them with middleware, gates, and Blade directives. Lightweight configuration keeps authorization clear and maintainable.
This package models rules as a tree: each rule can declare a parent, and the package resolves that hierarchy through the usual Laravel Eloquent + Artisan surface. Start by reading the package's migration and model before touching application code — the parent relationship is the thing everything else hangs off.
php artisan migrate.php artisan acr:doctor immediately on a fresh install. A clean bill of health gives you a baseline; anything it reports is usually bad seed data rather than a package bug.Your first real use case is almost always the same: a settings/taxonomy screen where rules are grouped under categories and reordered. Get a single-level tree working end to end (create, nest, reorder, display) before introducing deeper hierarchies.
What v3.3.15 changes for you: this is an integrity-focused patch, not a breaking one. Two things landed:
acr:doctor is the new diagnostic command. It reports rules whose parent no longer exists (orphans, typically left behind by hard deletes or bad imports) and rules sitting in a parent chain that loops back on itself.Both are safe to adopt on an existing install, and running acr:doctor against your current data is the right first action after upgrading.
Guard the parent assignment in Form Requests, not controllers. The package now rejects self-parenting, but the failure surfaces at persistence time. Validate in the request layer so the error renders against the parent_id field instead of bubbling up as a model exception:
'parent_id' => ['nullable', 'exists:rules,id', new NotDescendantOf($rule)],
The exists check catches dangling parents early; the custom rule walks the candidate parent's ancestors to stop cycles before they're written.
Reparenting is the risky operation. Creating a valid tree is easy; moving a subtree is where cycles creep in. When a user drags a node in a tree UI, the new parent must not be the node itself or any of its descendants. Encapsulate that check in one place — a value object or a model method — and call it from both the drag handler and any API endpoint, so the two paths can't drift.
Treat the tree as read-heavy. Parent chains are read on nearly every page that renders rules and written rarely. Eager-load the relationship, and if you render the full tree repeatedly, resolve it once into an in-memory structure (a keyed array of id => children) rather than issuing a query per node. Recursive relationship loading is fine for shallow trees and quietly expensive for deep ones.
Wire acr:doctor into your scheduler. The two failure modes it detects — orphans and loops — are almost never caused by user action. They come from bulk imports, hard deletes that skipped the package's cleanup, or manual SQL. A weekly run that emails on non-zero exit catches drift long before a user reports a blank settings page:
$schedule->command('acr:doctor')->weekly()->emailOutputOnFailure('[email protected]');
Prefer soft deletes if the package supports them. Hard-deleting a parent is the single most common source of orphaned children. If your schema allows it, soft-delete parent rules and let the children keep a technically-valid pointer, then filter on display.
acr:doctor reports a symptom, not a cause. An orphan means some write path bypassed the package's model events — a raw DB::table()->delete(), a truncate, an import script. Fix the data, then find the writer. If you only clean up rows, the orphans come back next month.
Loops survive the self-parent guard. The new validation stops a rule being placed under itself. It does not stop a three-node cycle (A → B → C → A) created by three individually-valid reparenting operations. That's exactly why the doctor command has the chain-walk check — treat the two features as complementary, not redundant.
Cached trees go stale silently. If you cache the resolved hierarchy, invalidate on any write to any node in the subtree, not just the node being saved. Reparenting a node invalidates the cache entries for its old ancestors and its new ones. This is the most common source of "it looks right in the database but wrong on the page" reports.
Debug with the raw chain first. When traversal misbehaves, dump the id => parent_id map for the affected subtree before stepping through model code. A loop or orphan is visible in that map in seconds and is invisible in a rendered view.
Watch the recursion depth. Deep or looping chains can blow a recursive relationship or policy check into a stack overflow. If your data can legitimately go deep, resolve the ancestry iteratively with a bounded loop and a seen-set rather than relying on recursive eager loading.
Extension points worth knowing: the parent relationship method on the model (override to add constraints such as tenant scoping), the validation rule behind the self-parent guard (extend it to also reject descendant-parenting), and the doctor command's output (parse it in CI if you want a hard gate). None of these require forking the package.
Version pinning: v3.3.15 is a patch, so ^3.3 is safe. If you have existing data, run acr:doctor before deploying application code that assumes clean trees — the deploy will otherwise surface pre-existing corruption as a new bug.
How can I help you explore Laravel packages today?