iampersistent/mongodb-acl-bundle
Symfony bundle that stores ACL (access control list) data in MongoDB. Provides MongoDB-backed persistence for access control entries, letting permission checks and role-based rules live alongside your document data. Integrates with Symfony's security ACL system, suited to apps already using MongoDB...
Before anything else: confirm you are looking at the right package. The repository description is truncated to This Bundle is now being maintained at — a forwarding stub. Check the Packagist page for an abandoned flag and the named replacement package, and read the successor's repo instead. Everything below describes day-to-day usage of this bundle as it exists, in the environment it was built for; treat it as operating a frozen artifact, not adopting a live dependency.
This is a Symfony bundle, not a Laravel package. If you arrived here from a Laravel shortlist, stop — there is no service provider, no facade, no config/acl.php. There is no supported path from a Laravel app to this code short of building a permanent bridge into Symfony's Security container and event dispatcher. The realistic first use case is therefore a legacy Symfony application that already uses it, where your job is to operate and eventually migrate off.
Minimum wiring (Symfony-era apps only):
// composer.json — pin to a commit, there will be no patch releases
"iampersistent/mongodb-acl-bundle": "dev-master"
// app/AppKernel.php
new IamPersistent\MongoDBAclBundle\MongoDBAclBundle(),
# config.yml — swap Symfony's default DBAL ACL provider for the Mongo one
security:
acl:
provider: mongodb_acl.provider
Then run the one-time initialization (the Mongo equivalent of acl:init) and verify the canonical ACL collections exist — object identities, security identities, entries, and object identity ancestors. Nothing works until those are present.
First use case to validate with: grant READ on a specific document to a role, then assert isGranted('READ', $document) for a user holding that role. If that round-trips against Mongo correctly, the bundle is doing its core job; if it throws on the first check, you have a driver/ODM version mismatch (see Gotchas).
Where to look first when reading the code: the AclProvider implementation. Everything the bundle does flows through Symfony's AclProviderInterface, so that class is the real API surface — the rest is DI glue.
Wrap the provider, don't scatter it. Symfony's AclProviderInterface/MutableAclProviderInterface are broad. In a real codebase, isolate them behind an opinionated service that exposes exactly the operations your domain needs:
class DocumentPermissions
{
public function __construct(
private MutableAclProviderInterface $provider,
private ObjectIdentityFactory $identityFactory, // Symfony's
) {}
public function grant(object $document, SecurityIdentityInterface $who, string $mask): void
{
$acl = $this->provider->findAcl($this->identityFactory->create($document));
$acl->insertObjectAce($who, $mask);
$this->provider->updateAcl($acl);
}
}
This is the single highest-value pattern here: it gives you a seam to swap implementations when you migrate, and it keeps ACL calls out of controllers.
Create-on-demand, then reuse. findAcl throws when the ACL doesn't exist yet; the standard flow is findAcl → catch AclNotFoundException → createAcl with an ObjectIdentity built from class name + document identifier. Centralize that try/catch once. Do not let each call site invent its own creation logic.
Grant at the right granularity. The Symfony ACL model this bundle implements has two distinct layers: object-level ACEs (this document, this role, this mask) and class-level ACEs (every document of this type). Day-to-day you want class-level grants for defaults and object-level grants for exceptions. Mixing them ad hoc is how permission models become unexplainable six months in.
Store hierarchy alongside the content. The bundle's genuine strength over relational tables is that roles, groups, and their parents live as documents in the same Mongo database as the protected content. Model your role hierarchy and any per-tenant scoping as documents, resolve a user's effective security identities (user + groups + roles) in one query, and pass that set into ACL lookups. This mirrors how Symfony's RoleHierarchyVoter composes identities, and it is where document storage pays off.
Cache resolved decisions, not raw ACLs. A per-document, per-request ACL resolution against Mongo is a textbook N+1. Cache the outcome of isGranted(...) keyed by (securityIdentity, objectIdentity, mask), with a short TTL and explicit invalidation on grant/revoke. Caching the ACL documents themselves invites stale-mask bugs that are very hard to debug.
Multi-tenant filtering belongs in the query. For listing pages ("show me the documents I can read"), do not resolve ACLs per row. Precompute a set of readable security-identity IDs for the current user and filter the content query against ACL entries you've denormalized onto the documents. Document storage makes this cheap; object-level lookups in a loop do not.
Bulk operations: batch, and be aware grants are write-heavy. The underlying Symfony ACL data model rewrites more state per grant than you'd expect. Seeding thousands of permissions at import time is where you'll first notice — do it in batches and monitor write load.
Abandonment is a runtime concern, not just a procurement one. Nothing here will get a security patch. If a fail-open bug exists in the ACL resolution path, it will not raise an error — it will silently allow access. Budget for reading the provider code yourself and owning the fork.
Truncated description breaks provenance. You cannot trust the metadata to tell you where the maintained successor lives. Verify the successor independently via Packagist's replacement field and the GitHub README. Do not assume the redirect target is active or compatible.
The foundation is likely EOL. An ACL bundle of this vintage almost certainly builds on symfony/security-acl, deprecated and removed from the core line years ago. Compatibility with current PHP branches and current ext-mongodb is undocumented — plan on a timeboxed spike before assuming it runs.
Driver/ODM drift shows up late. Version mismatches tend to surface as exceptions on the first ACL check rather than at install. That is the more annoying failure mode but also the safer one — the dangerous case is a mismatch that degrades to "no entries found" rather than throwing.
Debugging: go straight to the collections. When a permission check disagrees with your mental model, inspect the ACL collections directly with mongosh before touching application code. The bundle's indirection makes the data model the fastest source of truth. Confirm entries are attached to the object identity you think they are — a mismatched class name in the ObjectIdentity silently produces a separate ACL.
ObjectIdentity identity is class-name + identifier. Renaming a model class or changing an identifier format orphans every grant made under the old identity. Treat both as schema changes with a migration.
Indexing is on you. There is no documented index strategy. Expect to add indexes on security identity and object identity fields yourself, and to profile ACL resolution under realistic grant volumes.
Extension points, if you must. The provider is a service — you can decorate it with decorates: in Symfony to add caching or logging without forking. That is the cleanest place to insert observability: log every isGranted call and its resolved decision during rollout so a silent bypass is visible.
The migration tip that matters most: use the wrapping service from Implementation Patterns from day one. When the successor (or an in-house MongoDB-backed policy layer) is ready, you change one class, not every controller.
How can I help you explore Laravel packages today?