laminas/laminas-modulemanager
Laminas ModuleManager provides the infrastructure for building modular Laminas applications: it discovers and loads modules, resolves dependencies, merges configuration, and triggers lifecycle events for extensible bootstrap and application setup.
What the package is: laminas/laminas-modulemanager is the module bootstrap layer for Laminas MVC and Mezzio. It discovers enabled modules via Composer or custom autoloaders, instantiates each Module.php, aggregates configuration from module config and *.global.php / *.local.php files into a single array, orders modules by declared dependency, and fires load/bootstrap/merge events through laminas-eventmanager. It is mature, actively released (latest 2025-10-17), BSD-3-Clause, and maintained under the Laminas project umbrella.
Architecture fit — low for a Laravel-native codebase. Laravel already implements every concern this package owns, through a different and incompatible mechanism:
| Concern | Laravel native | laminas-modulemanager |
|---|---|---|
| Module discovery | Composer extra.laravel.providers, package manifest |
Composer autoload + explicit modules list |
| Registration | ServiceProvider::register() |
Module::getConfig(), onBootstrap() |
| Config aggregation | config/*.php + mergeConfigFrom() |
config/autoload/*.global.php / *.local.php |
| Boot ordering | $defer, provider dependency on bootstrap list |
Module::$dependencies + event priority |
| Lifecycle | boot(), booting/booted callbacks |
ModuleManager events, EventManager |
| Container | Illuminate\Container |
laminas-servicemanager |
Introducing it into a Laravel application means running two bootstrappers, two DI containers, two event dispatchers, and two config trees in one process. That is the central architectural risk, and it is not offset by any capability Laravel lacks except declared module dependency ordering — which is better solved with nwidart/laravel-modules or a plain ordered provider list.
Where it can legitimately fit (the only cases I would fund):
Technical risk register:
| Risk | Severity | Note |
|---|---|---|
| Dual container ambiguity | High | Same interface bound in both containers; resolution depends on which bootstrapper won. PSR-11 makes a bridge possible, not safe. |
| Config divergence | High | Two config trees with different merge semantics and precedence; no single source of truth. |
config:cache blindness |
High | Laravel's config cache will not capture ModuleManager output; production behaviour can differ from local. |
| Long-running workers (Octane, Horizon) | Medium-High | Module merge/event side effects are per-process, not per-request; state leakage across requests. |
| Autoloader overlap | Medium | Two autoload registration paths over the same namespace roots; silent shadowing. |
| Performance | Medium | Module discovery and merge are per-boot; without an explicit merged-config cache this is a fixed startup tax. |
| Debuggability | Medium | Merge precedence is opaque; exceptions surface in Laminas' error path, not Laravel's handler. |
Technical questions for engineering before any commitment:
config/autoload/*.global.php and Laravel's config/?nwidart/laravel-modules against the actual requirement? What is it missing?unknown/no stars — confirm Packagist ownership, GitHub provenance, and signed tags before adoption.Recommendation: do not adopt for a greenfield Laravel service. Fund a spike only if a concrete legacy-Laminas bridging requirement exists. Opportunity score of 6.78 reads as "technically interesting, strategically narrow" — accurate.
Stack assumptions: PHP 8.1+, Laravel 10/11/12, Composer 2. The package ships no Laravel service provider; there is no package discovery, no mergeConfigFrom, no facade. All wiring is manual, which means all wiring is ours to own and test.
Candidate integration patterns, cheapest first:
config/ files, and consume the output as ordinary Laravel config. Keeps Laminas out of the request lifecycle entirely. Recommended default.laminas-servicemanager behind a Laravel binding of Psr\Container\ContainerInterface, with delegator factories routing service resolution back into Illuminate\Container. Feasible because both are PSR-11; costly because resolution order becomes implicit. Reserve for modules with non-trivial factories.ModuleManager::bootstrap() inside a Laravel service provider's register()/boot(). Only viable inside a strangler migration where Laminas still serves traffic. Highest blast radius.laminas-* leaf packages via Composer and skip ModuleManager. Frequently the correct answer.Compatibility work items:
laminas/laminas-servicemanager and laminas/laminas-eventmanager majors; they move independently of ModuleManager and of Laravel.laminas-* transitive dependency conflicts with Illuminate 10/11/12 constraints (mostly clean, but check psr/container and psr/event-dispatcher version pins).php artisan config:cache and route:cache and under Octane if applicable.Sequencing and rough sizing:
| Phase | Work | Effort |
|---|---|---|
| 0. Diligence | Packagist/GitHub provenance, license review, security advisory history, dependency tree | 1–2 days |
| 1. Spike | Boot ModuleManager standalone in a Laravel app; dump merged config; document precedence | 3–5 eng days |
| 2. Decision gate | Compare against nwidart/laravel-modules and against provider-only refactor on the real requirement |
1 day + review |
| 3. Adapter build | Config-import command, schema mapping, golden-file tests | 5–10 eng days |
| 4. Pilot | One non-critical module through the adapter; parity tests on config output | 10–15 eng days |
| 5. Rollout | Module-by-module, or bridge-container for modules that require live factories | 6–10 weeks, scope-dependent |
Migration direction matters: if the end state is Laravel, every integrated Laminas module is technical debt with a sunset date. Write the exit criteria into the proposal — "no Laminas modules bootable after Q3" — before writing code. If the end state is permanent coexistence, this is an architecture decision requiring sign-off beyond the team, not a package selection.
Support and lifecycle. Community-maintained under Laminas, with a healthy release cadence (latest release 2025-10-17) and security advisories published through the Laminas security process. BSD-3-Clause is permissive and commercially safe. What is not provided: a vendor SLA, an LTS commitment tied to Laravel versions, or anyone contractually accountable for a cross-framework edge case. Internal ownership must be named — a specific engineer who understands Laminas bootstrap semantics, not "the platform team."
Failure modes to instrument for:
*.global.php/*.local.php is not obvious; a duplicate key produces a working-looking application with the wrong value. Mitigate with golden-file snapshot tests on the merged output.Module.php. A module in the enabled list without a resolvable class fails late and cryptically.config:cache ignoring Laminas config, or a cached merged config surviving a deploy that changed a module.Ramp-up cost. A Laravel-standard team has no priors on EventManager, ServiceManager delegators, or Laminas module semantics. Expect 2–3 weeks before a mid-level engineer can debug a bootstrap ordering problem unaided, and a standing tax on code review: reviewers must know which container resolves a given dependency. The hiring pool for this intersection is materially smaller than for Laravel alone; treat that as a recurring cost, not a one-time one.
Operational surface to add: a command that dumps the merged config for diffing, precedence documentation in the repo, composer audit in CI, and an explicit alert if module count or bootstrap time changes unexpectedly between deploys.
Verdict for a TPM decision record: low ongoing operational burden if consumed via the config-import adapter (a build-time artifact, no request-path coupling); high and diffuse burden if ModuleManager is bootstrapped inside the Laravel request lifecycle. The cost is not the package — it is the second mental model. Adopt only against a named legacy-integration requirement with a written sunset, and default to Laravel's native provider mechanism plus Composer packages for everything else.
How can I help you explore Laravel packages today?