symfony/ldap
LDAP client for PHP built on the ldap extension, providing connection, querying, and entry management since Symfony 3.1. Stable and maintained within the Symfony ecosystem.
Architecture fit
Ldap::create(), Adapter/Connection, Query, Entry, and Collection — clean primitives for bind/search/read/write. Everything Laravel expects from an auth backend (Guard, UserProvider, password broker, remember-me, session lifecycle) has no counterpart here. The Symfony LdapUserProvider and Security bundle wiring that make this package productive in Symfony are not installed and not usable in a Laravel app.App\Services\Ldap\LdapClient wrapper plus an Illuminate\Contracts\Auth\UserProvider implementation. The package carries roughly 20% of the effort; the Laravel-side bridge carries the remaining 80%.ldap extension plus a very small Symfony dependency footprint), so it drops into a Laravel service container cleanly — you bind the Adapter as a singleton in a dedicated service provider and inject it. There is no package auto-discovery, no config publishing, no facade. That is not a defect for a component, but it means our integration is bespoke code we own and test.Integration feasibility
Adapter is resettable (bug fix landed in 6.4.36 / 7.4.8 / 8.0.8) — this matters for Laravel Octane and long-lived queue workers, where a stale bind or dead socket otherwise poisons the worker until restart.AbstractQuery::setSizeLimit(); 7.x requires PHP ≥ 8.2; 6.4 is the LTS line. Our Symfony transitive dependency major (pulled in by Laravel itself via console/http-foundation) should match the symfony/ldap major we choose to avoid resolver conflicts and duplicate component trees.Technical risk
ext-ldap is a hard, non-negotiable requirement. Many shared hosts disable it, and slim/Alpine containers need openldap-dev at build time and the extension compiled or installed. If any target environment (local, CI, a customer's on-prem deploy) lacks it, the package is unusable regardless of code quality. This is the single largest adoption risk and must be verified per environment before commitment.Stars: 135 and Dependents: 0 describe a read-only split repository; development, issues, and PRs live in the symfony/symfony monorepo, so GitHub-level popularity signals are structurally understated. Conversely, the package is flagged as "looking for a backer," and support is community-only (MIT, no vendor SLA).whoami, SASL/EXTERNAL), referral chasing, and AD-specific behaviors like nested group expansion are only partly covered — SASL bind and whoami were added in 7.2, paging is not a first-class API. Any of these becomes a drop to native ldap_* calls, which undermines the reason for adopting the package. Directorys with >1000 entries will hit AD's default size limit on search and need paging work.StartTLS, CA trust, and certificate validation are governed by ldap.conf/openssl configuration and the LDAP_OPT_* options we set. This is infrastructure configuration, not something the package handles.Key questions
ext-ldap enabled? Who verifies this, and by when?sAMAccountName, memberOf, nested groups) or OpenLDAP/389DS? Group→role mapping rules must be specified by the business, not inferred by engineering.directorytree/ldaprecord-laravel as a Laravel-native alternative with a higher-level, Eloquent-shaped API and AD-friendly conventions? We should be able to articulate why we chose the lower-level component.Stack fit
App\Ldap\LdapClient interface (bind, authenticate, findByUsername, groupsFor, search) and one SymfonyLdapClient implementation backed by the Symfony Adapter. Bind it as a singleton in LdapServiceProvider, configured from config/ldap.php fed by env vars (LDAP_HOST, LDAP_PORT, LDAP_ENCRYPTION, LDAP_BASE_DN, LDAP_SERVICE_DN, LDAP_SERVICE_PASSWORD, LDAP_TIMEOUT). Never let Symfony\Component\Ldap\* types appear in controllers, models, or jobs.Illuminate\Contracts\Auth\UserProvider (and optionally a custom Guard) on top of that interface. First login performs search-then-bind, maps the entry to an Eloquent users row (JIT provisioning), syncs group→role assignments, and thereafter authenticates against the local record or re-binds depending on the policy we pick.AuthenticatesUsers/Fortify flow rather than reinventing login. Add ldap as an additional guard; keep the existing eloquent guard as a break-glass fallback for local admin accounts.Migration path
ldap_* calls exist today, move them behind the LdapClient interface with no behavior change. This decouples the eventual library swap from the auth work and gives us a rollback seam.Adldap2-style) or a bespoke implementation, treat the LdapClient interface as the contract that makes either direction reversible.Compatibility
composer.json and add a CI matrix job per supported PHP version.AbstractQuery::setSizeLimit() removed, eraseCredentials() removed from security interfaces, PHP 8.4 floor. None block adoption on 7.x today.Sequencing
ext-ldap in Docker/CI/staging; smoke-test connect + bind + search against a containerized OpenLDAP and against the real corporate directory with a service account.ldap:health artisan command, structured logging and metrics for connect latency, bind success/failure, search duration.ldap service in docker-compose and CI, fixture LDIFs, integration tests that exercise paging and nested groups rather than only happy-path binds.Maintenance
UserProvider, group-mapping rules, and the secrets rotation for the service-account bind — realistically a few days per quarter plus an upgrade task per Symfony major.symfony/symfony PRs. If we need an upstream fix, our lead time depends on Symfony's community review, not on a vendor. Plan to work around issues locally rather than block on upstream.Support
directorytree/ldaprecord-laravel offers a Laravel-native, Eloquent-shaped API and commercial support options, at the cost of less direct control and a different abstraction. Choosing the Symfony component is a defensible decision for control and Symfony-ecosystem alignment, but it must be a stated trade-off, not a default.Scaling
ldap_* fallback.LDAP_OPT_NETWORK_TIMEOUT and default_socket_timeout, and a hard cap on retries. Under Octane, reset the adapter after bind failures and on a schedule.memberOf (AD) over recursive group expansion, and cache the flattened role set.Failure modes
ldap.conf requirements as deployment prerequisites.Ramp-up
Escaper, so use it), and roughly one sprint end-to-end for a production-grade bridge including tests and observability.ldap:health, where group→role rules live, and who to escalate to when the directory is down.How can I help you explore Laravel packages today?