directorytree/ldaprecord
LdapRecord is a full-featured LDAP ORM for PHP and Laravel. It offers ActiveRecord-style models, a fluent query builder, authentication, and multi-domain connection management, making OpenLDAP and Active Directory integration simple and expressive.
Architecture fit. LdapRecord is an ActiveRecord-style ORM that maps LDAP directory entries onto Eloquent-shaped models, with a fluent query builder, attribute casts, relationships (group membership ↔ member), global scopes, pagination and chunking. For a Laravel product where the directory is the identity source of truth, this is a strong architectural fit: the directory layer speaks the same idioms as the rest of the app, so authorization logic ("is this user under OU=Contractors?", "is this user a member of CN=Admins?") can be expressed as scoped queries rather than a parallel integration language. Crucially, the package is split into a framework-agnostic core plus a Laravel adapter (auth guard, user provider, config publishing, DirectoryFake), which means the LDAP logic is not welded to the delivery framework.
Integration feasibility. Integration is a Composer install plus a published ldap.php config (connections, base DNs, TLS/SASL, failover). Models are hand-written classes per object class (User, Group, OU) with casts for AD-specific encodings. Authentication plugs into Laravel's guard system. Testing is the notable strength for a TPM: DirectoryFake plus auth events and the Ldap::DEBUG_* constant set (v3.7.2) let us assert login and provisioning behavior in CI without a domain controller, removing the usual "QA needs VPN and a service account" bottleneck.
Technical risk. The risks are domain risks, not library-shape risks:
{base} substitution in whereMemberOf. A stale or mis-scoped cache key here is a security defect, not a performance defect. Any caching of directory-derived permissions must be reviewed as authz code.Key questions to resolve before commit.
whereMemberOf, timestamp formats, objectGUID/SID binary handling, password-change protocol).{base} substitution helps, but OU structure is a dependency we don't control.allow_insecure_password_changes? That flag exists, which means the unsafe path is one config line away and needs an explicit decision recorded.Stack fit. The package sits inside our existing Laravel service as a library, not a service: no new runtime, no new datastore, no new deployment unit. It requires PHP 8.x with the ldap extension enabled on every app node — an infrastructure checklist item that is easy to miss in containerized environments. Laravel 11, 12 and 13 compatibility exists on both the v3 and v4 branches (v3.8.6 / v4.0.3), and PHP 8.5 deprecation fixes landed in v4.0.8, so framework currency is not a blocker.
Migration path. Three plausible entry points, in ascending cost:
>=4.1.1.ldap_* wrappers — the ORM absorbs attribute mapping, paging, error handling and DN parsing that is currently hand-rolled. insertAndGetDn / renameAndGetDn with base-DN substitution (v3.3.0) make porting legacy create/rename scripts tractable; refresh() returning false for missing entries instead of throwing changes control flow in ways our call sites must be audited for.Two version decisions should be made explicitly and early:
orFilter/andFilter) that were still being corrected through v4.0.2. Do not bundle the v4 upgrade with a framework upgrade.Compatibility notes to encode as constraints. AD objectGUID/SID serialization and UTF-8 handling were fixed in v3.5.1; 32-bit PHP timestamp handling in v4.0.4; the TLS upgrade state bug in v3.6.0. These suggest we should standardize on 64-bit PHP, UTF-8 end-to-end, and a single supported connection configuration rather than a matrix.
Sequencing (each step shippable and reversible).
DirectoryFake in CI and a live DC in staging. Output: a written AD/OpenLDAP assumptions doc, base-DN inventory, and a working auth guard.User::changePassword() (v4.1.0, OpenLDAP Argon2 supported) behind TLS enforcement, with the allow_insecure_password_changes decision documented as an explicit "no."morphIntoOrFail and model-morphing (v3.4.0) cover heterogeneous object classes.Maintenance. Low code-level maintenance, moderate version-tracking effort. The library is actively patched (roughly monthly; v4.0.5 → v4.1.1 within recent history) with a parallel v3 line, so we should budget a recurring review of release notes rather than assume quiet. Ownership is a single vendor maintainer; the realistic mitigation is internal: pin a known-good version, keep our LDAP model layer thin and centralized, and avoid subclassing deep internals so an upstream break has a small blast radius. Everything LDAP-schema-specific — object classes, attribute names, base DNs, group naming conventions — is our ongoing maintenance burden, not the package's.
Support. MIT, community-supported, no SLA. Documentation at ldaprecord.com is unusually complete (per-version docs, upgrade guides, base-DN substitution, caching). Escalation for a production incident lands on our platform team. If the business requires contractual support for identity infrastructure, this is the wrong dependency — plan to own it. Conversely, "self-hosted, no per-seat license, no vendor schema lock-in" is a real cost avoidance versus a commercial IAM offering.
Scaling. The directory is the bottleneck, not PHP. Scaling concerns are:
paginate, so pin at that floor and test early-exit paths.secure state must be verified on reconnect (v3.8.3 fixed resetting that flag on close()).Failure modes to instrument.
| Failure | Symptom | Mitigation |
|---|---|---|
| DC unreachable / failover | Login and authz timeouts | Failover config, short timeouts, circuit breaker, cached-but-correct authz fallback with explicit staleness policy |
| Bind failure | Login rejected for valid users | BindException surfaced in the Failed auth event (v3.6.1); distinguish credential vs. connectivity in logs |
| TLS upgrade failure | start_tls local errors |
Track TLS-upgraded state (v3.6.0); alert on handshake errors; never silently fall back to plaintext |
| Partial write | User updated, group membership not | Dry-run, idempotent retries, compensating runbook, per-operation audit trail |
| Over-broad detach | Populated group deleted | Pin >=4.1.1; restrict who can call relationship detach; test against a seeded group |
| Secret leakage | Passwords in exception traces | Pin >=4.1.1 (PR #778); add a log-scrubbing assertion to CI |
| Stale authz cache | Wrong group / wrong OU scope | Cache keys must include search base and controls; treat as security-reviewed code; add a regression test |
Ramp-up. For an Eloquent-fluent team the API ramp is days; the LDAP ramp is weeks and is the real cost. Engineers must absorb distinguished names, search filters, OIDs and controls, paged results, AD's binary objectGUID/SID encodings, AD's 1000-object paging limit, AD timestamp epoch quirks, and password-change protocol differences between AD and OpenLDAP. Plan for at least one engineer to become the named directory owner, and front-load a written internal runbook (connection config, base DNs, object classes, read-only vs. write policies, escalation path) during the spike rather than after the first incident. DirectoryFake plus the DEBUG_* constants materially shorten this curve — use them from day one so the team learns from tests instead of from the production directory.
How can I help you explore Laravel packages today?