project-a/acl-bundle
ACL bundle for Project A that adds role- and permission-based access control. Define roles, assign permissions, and protect routes and resources through configuration, with simple checks for authorization throughout your application.
Read this first: despite the project-a/acl-bundle name, the repository is chili-labs/AclBundle — a Symfony bundle wrapping symfony/security-acl, not a Laravel package. There is no AclServiceProvider, no config file in config/, and nothing that hooks into Laravel's Gate/Policy system. As a Laravel developer you will not be able to composer require this into an app and use it; treat it as a reference implementation of ACL concepts and a source of patterns to port.
What has changed: v1.1.0 has been tagged. The release is small and behavioural rather than structural — fix class permissions, accept ObjectIdentity instances directly, accept class names directly. It does not add a Laravel service provider, does not add Symfony 4+ support, and the ACL component it wraps is still absent from maintained Symfony. So the "not for Laravel" verdict stands; the "hard-frozen at 2015" verdict needs softening to "sporadically maintained, support matrix unstated."
Practical first steps if you are evaluating it on a legacy Symfony 2/3 codebase where it actually runs:
composer.json — the release notes say nothing about supported Symfony/PHP versions, so the constraints file is the only authority. Do not assume v1.1.0 means Symfony 4+ compatibility; the underlying symfony/security-acl was removed in 4.0.AppKernel::registerBundles() and check the bundle's DI extension for the config tree (Resources/config/*.xml|yml) — that is where the service IDs and any acl: config keys live.MutableAclProviderInterface / AclProviderInterface decorator) and a manager/service that wraps create-ACL + insert-entries + grant in one call. v1.1.0 widens the second one's accepted argument types — read its signature before writing call sites.VIEW/EDIT to the current user, and assert with $this->get('security.authorization_checker')->isGranted('EDIT', $entity). If that round trip works, the plumbing is sound.If you are on Laravel, skip straight to Implementation Patterns and read it as a blueprint for Gate::define + policies, not as an install guide.
Because the bundle is thin, the day-to-day work is really standard Symfony ACL usage with the bundle's manager trimming boilerplate. The patterns that matter:
MutableAclProvider directly, route writes through the bundle's manager so createAcl → insertObjectIdentity → insertClassAce → updateAcl happens in one transaction. Hand-rolling those four calls is where most bugs come from.ObjectIdentity or a class name string where it previously demanded an entity/its parts. Prefer supplying the ObjectIdentity yourself when you already have (type, id) in hand — it removes a lookup and makes the identity explicit at the call site. Use the class-name shorthand only for class-level ACEs, where there is no instance to speak of.@Acl-style listener that resolves the object from a route attribute and asserts a mask before the action runs. Check Resources/config/ for a kernel.event_listener on kernel.controller before assuming the annotation exists — many forks dropped it, and the v1.1.0 notes do not mention it.ObjectIdentity for per-record permissions and a class-level ACE for the "everyone with ROLE_ADMIN can edit anything" baseline. Resolve class ACEs via AclAncestor(Entity::class)-style checks so you get inheritance for free. This is precisely the path v1.1.0's fix class permissions touches — see the gotchas before trusting an upgrade.grant(object, identity, mask) and revoke(...) from a small application service, inject the provider once, and keep controllers ACL-free. This is also the seam that makes a Laravel port mechanical: swap the provider for Gate/Gate::after and the call sites survive. Widening the manager's accepted types is an argument for keeping your wrapper's signature narrow so the churn stops at one file.MaskBuilder constants (MASK_VIEW, MASK_EDIT, MASK_OWNER) and keep a single permission map. Scattering raw integers across the codebase is the fastest way to lose track of what 4 meant.insertObjectAce per identity in a loop is an N+1 against acl_entries. Collect identities and call the batch variants; for seeding, a single updateAcl with a pre-populated ACL object is dramatically faster than per-row grants.acl:init) or Doctrine fixture so class-level ACEs are reproducible across environments — they are data, not code. After upgrading to v1.1.0, re-run the seeder rather than trusting previously written class ACEs.v1.1.0 exists, but Symfony removed the ACL component in 4.0, so this still cannot be installed on any maintained Symfony, and the notes make no claim about PHP 8 or Symfony 5/6. Treat it as a live-but-frozen branch, not a revived dependency. Check the tag date and the composer.json constraints before planning anything around it.project-a/acl-bundle vs chili-labs/AclBundle) is the kind of thing that wastes an afternoon.PermissionMapInterface, that workaround may now double-apply or shadow the fixed path. Re-run your authorization tests against both an object ACE and an ancestor class ACE before deleting the workaround.ObjectIdentity yourself, the manager no longer builds it from your entity, so "wrong identity type" and "partially populated identity" no longer get caught for you — a mismatch just silently fails to match at check time. Assert type and id are both set, and remember the id must exist (flush before creating the ACL, or defer to postPersist).ObjectIdentity ad hoc makes it impossible to reason about which layer owns identity construction. Pick one convention per layer — explicit identities in services, class-name strings only in seeders — and enforce it.acl_cache / the configured cache pool) or stale results will persist for the request lifetime and beyond. This is the single most common "my grant didn't take effect" report — and it will masquerade as a failed upgrade if you change ACL code without clearing.isGranted disagrees with your intent, inspect acl_object_identities, acl_object_identity_entries, and acl_entries directly — reading the ACE hierarchy is faster than stepping through AclProvider internals, and it distinguishes "wrong mask" from "wrong identity type" immediately. With v1.1.0's direct-identity input, this is also the quickest way to spot an identity that was constructed but never inserted.UserSecurityIdentity and RoleSecurityIdentity do not compare equal; a role grant will not satisfy a check scoped to a user identity. Be explicit at the call site — more so now that you can hand the manager an identity object directly.PermissionMapInterface) and the ACL voter are both replaceable service definitions — that is the sane way to add custom masks without forking. Beyond that, the bundle adds little value over the base component, so if you find yourself overriding three services, use symfony/security-acl directly and drop the bundle.AclProvider → Gate, MutableAclProvider → policies, MaskBuilder → abilities, class ACEs → Gate::before role checks, AclAncestor → Gate::after. The one thing ACL gives you that Gate does not out of the box is per-object persistence of grants in SQL — if you need that, model it as a permissions table and query it from a single policy rather than resurrecting this stack.How can I help you explore Laravel packages today?