Laminas ServiceManager and MVC ControllerManager: Proxy Lifecycle Coordination
0 reputation · 29 Aug 2026, 19:02 UTC
0 reputation · 29 Aug 2026, 19:02 UTC
Laminas MVC utilizes the ControllerManager as a plugin manager to resolve controllers, which in turn relies on the primary ServiceManager for dependency injection. A common architectural requirement is the use of LazyServiceFactory to defer the instantiation of heavy dependencies until they are accessed during the request lifecycle.
When a controller is defined as a lazy service, the ControllerManager provides a proxy object. However, the interaction between the proxy's lifecycle and the MVC event loop introduces uncertainty regarding when the actual service instantiation occurs relative to the dispatch process.
If a lazy-loaded controller depends on other lazy services, the sequence of proxy resolution can impact memory overhead and initialization timing during the MvcEvent::EVENT_DISPATCH phase.
ControllerManager trigger the LazyServiceFactory proxy resolution before or after the controller's onDispatch method is called?ServiceManager and the ControllerManager?35275 reputation · 30 Aug 2026, 05:51 UTC
The ControllerManager::get() call returns a LazyServiceFactory proxy without initializing the controller. The real controller is instantiated when the proxy is first invoked, which occurs during the MVC dispatch lifecycle after the controller has been looked up but before the controller's onDispatch() method (or action) runs. Therefore, proxy resolution happens after the lookup and before your controller's onDispatch body.
ControllerManager is a plugin manager that delegates to the parent ServiceManager for services it does not define locally.ServiceManager returns the same instance on every get() call.LazyServiceFactory creates a value‑holder proxy; initialization occurs on the first method call on that proxy.When a lazy controller depends on other lazy services, each dependency gets its own proxy. Building the controller proxy does not trigger its dependencies; each proxy initializes independently on first use. Consequently, memory usage and initialization order follow actual access patterns, not the EVENT_DISPATCH trigger itself.
If you request the same service name from the primary ServiceManager and from ControllerManager, you receive two separate proxies unless the plugin manager is wired to delegate to the primary container. To keep a single shared instance:
ControllerManager is constructed with the application ServiceManager as its parent (or creation context).ControllerManager resolve it via parent delegation; avoid registering a duplicate definition in the plugin manager.If the plugin manager lacks this parent link, each container will produce its own proxy and thus two distinct real objects.
ControllerManager::get() returns a proxy and that the controller’s constructor has not run yet (e.g., static counter is 0).onDispatch) executes.ServiceManager as shared and rely on parent delegation from the ControllerManager.If the ControllerManager was instantiated without the application ServiceManager as its parent, the delegation step above will not work. Verify the parent container wiring before adjusting service definitions.
// After fetching from ControllerManager, before dispatch:
$proxy = $controllerManager->get(MyController::class);
// assert real constructor has not run yet
// During dispatch, after first method call:
// assert real constructor has run exactly once
// Shared identity check:
$fromPrimary = $primarySm->get(SharedDep::class);
$fromCM = $controllerManager->get(SharedDep::class);
// compare spl_object_id($fromPrimary) === spl_object_id($fromCM)
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.