ordermind/logical-authorization-bundle
A Laravel package for building logical, composable authorization rules. Combine gates, policies, and custom conditions into readable checks, keep permission logic centralized and testable, and integrate cleanly with the framework's existing auth system.
The first thing to know is that this is a Symfony 4 bundle, not a Laravel package. You won't composer require it into a Laravel app and have it "just work" — there is no service provider, no facade, no Gate integration. So "getting started" is really about deciding which of two paths you're on:
composer require ordermind/logical-authorization-bundle, register Ordermind\LogicalAuthorizationBundle\LogicalAuthorizationBundle in config/bundles.php, then look first at config/packages/logical_authorization.yaml and the AuthorizationManager service. First use case: protect a controller action with @RoutePermission(permissions={"and", "logged_in"}) or call $authManager->checkPermission($permission) from a controller.src/Logic/ (LogicEvaluator, LogicExpression) and src/Permissions/ (PermissionProvider, AuthorizationManager). That's ~500 LOC of genuinely portable code — the logical expression DSL is the entire value proposition, and it drops cleanly into a Laravel Gate::define() callback or a custom Authorizer service.The mental model to hold: permissions are data, defined in YAML or attributes, and each one is a nested logical expression (["and", ["or", "editor", "owner"], ["not", "banned"]]). The AuthorizationManager is the single entry point that evaluates them in context.
Logical expressions are the core. Everything revolves around operators: and, or, not, plus leaf terms that resolve to booleans, role checks, or context comparisons. Expressions nest arbitrarily, so you compose coarse policies (route-level) from fine-grained ones (field-level) without duplicating rules.
Permission providers organize policy. You register named providers in YAML, each mapping permission names to expressions. A controller asks checkPermission("post.edit", $post), and the provider resolves the chain. This is the equivalent of a Laravel Policy class but defined declaratively rather than as PHP methods — which means non-developers can edit policy, but you lose IDE support and static analysis.
Three enforcement layers, use all three:
@RoutePermission attribute on controller actions — the closest analog to Laravel's ->middleware('can:...').@PermissionProvider on a Doctrine entity — like a policy attached to a model.@PermissionConfigForField + @Permission on properties, with canSeeField() / canEditField() at runtime — useful for serializing responses (hide fields the caller can't read) which Laravel doesn't have a first-class story for.Context passing. Expressions reference @user.id, @user.roles, and the current entity via @this.*. The AuthorizationManager accepts arbitrary context arguments, so the same permission can be evaluated against different users/entities in a single request — useful for admin-preview features ("what would this user see?").
Chaining with existing security. The bundle is designed to sit alongside Symfony's Voters rather than replace them; in a Laravel port you'd have Gate::before() delegate to the expression evaluator, and fall through to native policies for anything not expression-driven.
true and false in YAML parse as PHP booleans, not the strings the evaluator expects. Quote them: "true" / "false". Same for numeric-looking permission names.["a", "b"] does not mean "a and b" — the evaluator expects an operator first. Unrecognized shapes typically fail closed (deny), which means a typo silently locks users out rather than erroring loudly.@ reference resolution is context-sensitive. @this.field only works when a subject entity is passed; @user.id requires the user in context. Missing context keys fail — surface a clear exception in your port rather than returning false, or you'll spend hours debugging authorization that looks correct but silently denies.null is not false for field checks. Partial expressions can return null; canSeeField() treats that inconsistently across versions. Normalize null → false at the boundary in any port.bin/console cache:clear (sometimes cache:warmup) — config is compiled into the container. Easy to think a rule change didn't apply when really you're hitting stale cache.bin/console debug:config logical_authorization dumps the parsed provider graph, and $authManager->getPermissionProvider($name) gives you the raw provider if you need to trace an evaluation. If you port it, build a single explain(string $permission, array $context): array method that returns the evaluation tree — you'll use it constantly.LogicEvaluatorInterface is tagged (in Laravel terms, register an implementation and bind the interface) — you can add domain operators like "owned_by" without forking. Do this before you start hand-writing compound expressions everywhere.AuthorizationManager, load provider YAML in a service provider (use config/logical_authorization.php), and wire Gate::before(fn($user, $ability, $args) => $manager->check($ability, $user, ...$args)). Ship a @canSee('post.body', $post) Blade directive for field-level checks. That's the whole integration surface.How can I help you explore Laravel packages today?