ServiceManager Shared Instances vs Non-Shared Factories for State Isolation
0 reputation · 26 Aug 2021, 00:39 UTC
0 reputation · 26 Aug 2021, 00:39 UTC
In Zend (Laminas) applications, the ServiceManager defaults to caching service instances to minimize instantiation overhead during the request lifecycle. While this reduces latency and memory allocation, it introduces risks when services maintain internal state that must remain isolated between different components within a single execution thread.
Configuring services as non-shared forces the manager to invoke the factory for every retrieval, ensuring a fresh instance. However, this creates a trade-off between strict state isolation and the CPU cycles required for repeated object construction, particularly for heavy dependencies.
shared configuration key in the ServiceManager array.Under what specific conditions does the overhead of non-shared instantiation outweigh the risks of state leakage in a high-concurrency environment? Which approach is more sustainable for services that require dynamic configuration based on the current request context?
35275 reputation · 26 Aug 2021, 03:53 UTC
In a Laminas ServiceManager, the shared flag determines whether a service instance is cached for the lifetime of the container. The trade‑off is between the cost of constructing a fresh instance on every get() call (non‑shared) and the risk that a mutable instance may expose request‑specific data to unrelated components (shared).
Request object to the constructor).For services that must adapt to the current request (e.g., a translator that loads locale data or a logger that tags messages with a request ID), the recommended pattern is:
shared (default).shared" => false and ensure the factory uses the current request to build the instance.get() multiple times in a test request. Compare the time taken for shared vs non‑shared.shared => false unless the container is per‑request.To refine the recommendation, please confirm: Does your ServiceManager instance live only for the duration of a single HTTP request, or does it persist across multiple requests in a long‑running environment (e.g., Swoole, RoadRunner, or a single ReactPHP loop)?
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 26 Aug 2021, 10:38 UTC
Setting shared => false only guarantees a new root instance per get(); it does not isolate state if the factory resolves shared dependencies that hold mutable state.
In Laminas ServiceManager 3.x the default is shared => true. A non-shared service forces the factory to run on every retrieval, raising CPU and allocation cost proportional to the dependency graph depth.
For request-scoped dynamic configuration a sustainable pattern is often a shared factory with a delegator or lazy proxy that creates a fresh object per request, rather than making the whole service non-shared. Verify behavior by comparing object identity with === on two successive get() calls.