Lazy Loading Services in Zend Framework 3 with the Service Manager
Learn how to defer heavyweight service creation in Zend Framework 3 using lazy proxies, reducing startup latency and memory use while keeping frequently used services eager.
09 Mar 2026, 14:57 UTC

Problem: Unnecessary Service Instantiation Slows Startup
In a typical Zend Framework 3 application, the Service Manager creates every registered service as soon as the application boots. If a service is heavyweight—for example, a database adapter that establishes a network connection or an external API client—its constructor runs on every request, even when the current request never uses that service. This eager instantiation adds latency and consumes memory unnecessarily.
Thesis: Load Services Only When First Accessed
By configuring the Service Manager to return a lazy placeholder instead of the real object, construction is deferred until the first method call on the service. The placeholder behaves like the real service but triggers the actual instantiation transparently. This approach lets you keep frequently used services eager while making infrequently used ones lazy, balancing startup speed with runtime overhead.
How Zend Framework 3 Implements Lazy Services
Zend Framework 3’s Service Manager supports lazy loading through two complementary mechanisms:
- LazyServiceInterface: A service class can implement
Zend\ServiceManager\LazyService\LazyServiceInterface. The Service Manager then injects a ProxyManager-generated proxy that forwards method calls to a factory which creates the real instance on demand. - Abstract factories returning closures or proxies: If a class cannot implement the interface (e.g., it is final or lacks a public constructor), you can register an abstract factory that returns a closure or a ProxyManager proxy. The closure is invoked only when the service is accessed.
Both approaches rely on ProxyManager (or a compatible library) to generate the proxy class. The Service Manager treats the proxy as a regular service; the only difference is that the real object is not instantiated until a method is invoked.
Worked Example: Lazy Database Adapter
The following steps illustrate how to make a custom database adapter lazy. The example does not show actual output; it describes what you should observe when you run the code.
Create a simple adapter that logs when it is constructed:
namespace App\Service; class DbAdapter implements \Zend\ServiceManager\LazyService\LazyServiceInterface { private $pdo; public function __construct() { // Simulate a heavyweight operation error_log('DbAdapter constructor called'); $this->pdo = new PDO('sqlite::memory:'); } public function query($sql) { return $this->pdo->query($sql); } // Required by LazyServiceInterface public function __invoke() { return new self(); } }Register an abstract factory that returns a lazy proxy. In a module’s
getServiceConfig():return [ 'service_manager' => [ 'abstract_factories' => [ function ($serviceManager) { $factory = function () use ($serviceManager) { return new \App\Service\DbAdapter(); }; // ProxyManager creates a lazy proxy; the third argument is the initializer return \ProxyManager\Factory\LazyLoadingValueHolderFactory::createProxy( '\App\Service\DbAdapter', $factory, [] ); }, ], ], ];Retrieve the service and use it:
// Somewhere in a controller or service $adapter = $serviceManager->get('App\Service\DbAdapter'); // At this point, no constructor log should appear yet $adapter->query('SELECT 1'); // Now the constructor log appearsTo verify the difference, replace the abstract factory with an eager one that returns
new DbAdapter()directly. The constructor log will appear immediately whenget()is called.
Trade‑off: Debugging Complexity
Introducing a proxy adds an extra layer to the call stack. When an exception occurs, the stack trace may show methods from the generated proxy class rather than your actual service implementation, which can make debugging slightly harder. Additionally, if a service class is final, lacks a public constructor, or uses private properties that the proxy cannot access, you may need to configure ProxyManager with custom serialization or interception rules, or reconsider lazy loading for that service.
Actionable Checklist
- Identify services that are instantiated on every request but used conditionally.
- Ensure the service class either implements
LazyServiceInterfaceor has a public constructor compatible with ProxyManager. - Register an abstract factory that returns a lazy proxy or a closure.
- Retrieve the service in your code and invoke a method to trigger initialization.
- Check logs or use a debugger to confirm that the constructor runs only after the first method call.
- If debugging becomes cumbersome, consider switching back to eager loading for that service while retaining lazy loading for others.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.