formal/access-layer
Formal Access Layer is a Laravel package for building a clean, centralized access-control layer. It helps organize authorization rules, permissions, and access checks so application code stays consistent, testable, and easy to maintain.
composer require formal/access-layer. With a single star and zero dependents, pin the exact version ("formal/access-layer": "x.y.z") rather than floating, and commit the lockfile.src/*ServiceProvider.php), the config file, and the base class you are expected to extend are the real documentation. The tests/ directory is usually the most reliable usage reference — mirror one of its tests to confirm the package works in your app.php artisan vendor:publish --tag=access-layer-config (verify the tag with php artisan vendor:publish and picking the provider interactively). Check whether the provider is auto-discovered; if not, add it to bootstrap/providers.php (Laravel 11+) or config/app.php.app(YourLayer::class)) and that a request returns the same payload as before the refactor.App\Access\Orders\ListOrders, App\Access\Orders\ShowOrder read better than a god-object OrderAccessLayer, and they map cleanly onto controller actions and invokable classes.App\Contracts\OrderAccess and bind the package-backed implementation in a service provider. This is the single most valuable pattern for a 1-star dependency: it gives you a seam to replace or fake the package without touching call sites.$this->authorize(...) / gate checks at the top of the layer method. Internal helpers then trust the caller — this avoids permission checks scattered across controllers, jobs, and commands.whereBelongsTo($user), soft-delete filters — put them in the layer so HTTP, queued jobs, and artisan commands all get the same visibility rules. This is where an access layer earns its keep.handle() methods. Consistency across entry points is the point of the abstraction.can:viewAny,Order on the route, fine-grained "can this user see this row" inside the layer. Don't duplicate the same check in both.ShowOrder layer can depend on a LoadOrderForUser layer that performs the scoped fetch; each stays testable in isolation.$this->app->instance(OrderAccess::class, $fake) to bypass the package entirely; unit-test the real implementation with RefreshDatabase and factories. Assert both the allowed and denied branches — negative tests are what catch scoping regressions.composer show formal/access-layer --all); you may need dev-main with a commit hash pinned in composer.json for reproducibility.php artisan config:clear and php artisan package:discover. Re-publish the config after upgrades — a stale published config silently masks new keys.[] or throws AuthorizationException, and encode it in the layer. Mixing the two produces endpoints that leak existence of records.DB::listen() or Laravel Debugbar / Telescope and assert query counts in tests for list endpoints.request(), no auth() globals inside the layer — take the actor as a parameter. This keeps it usable from queued jobs where there is no HTTP context, and makes tests trivial.app()->make(OrderAccess::class)::class in tinker tells you which implementation is bound; php artisan about plus php artisan route:list catches provider-not-registered issues quickly.vendor/ — MIT license means forking or vendoring a small, unmaintained package is a legitimate long-term option if the abstraction proves valuable.How can I help you explore Laravel packages today?