Zend Basic Tutorial
Zend Forms
Zend Database
Zend Advanced
The Zend (Laminas) Service Manager is a Dependency Injection (DI) container.
It is responsible for:
In short:
It's a centralized object factory that manages class dependencies and lifecycle.
Using the Service Manager provides several important benefits:
Benefit |
Description |
---|---|
Centralized control |
Manages object creation and configuration in one place |
Loose coupling |
Avoids hardcoded dependencies like |
Testable code |
Easily mock or replace services during testing |
Reusability |
Reuse shared services (e.g., DB connection, Logger) |
Better scalability |
Easy to manage complex apps with many dependencies |
You have two classes: Logger
and UserService
.
UserService
needs a Logger
to log user actions.
module\Application\src\Service\Logger.php
class Logger { public function log($message) { echo "Log: $message"; } }
module\Application\src\Service\UserService.php
class UserService { protected $logger; public function __construct(Logger $logger) { $this->logger = $logger; } public function register($user) { $this->logger->log("Registering user: $user"); } }
Module.php
(module\Application\src\Module.php)use Application\Service\Logger; use Application\Service\UserService; class Module { public function getConfig(): array { /** @var array $config */ $config = include __DIR__ . '/../config/module.config.php'; return $config; } public function getServiceConfig() { return [ 'factories' => [ Logger::class => function($container) { return new Logger(); }, UserService::class => function($container) { $logger = $container->get(Logger::class); return new UserService($logger); }, ], ]; } }
use Application\Service\UserService; public function homeAction() { $userService = $this->getEvent() ->getApplication() ->getServiceManager() ->get(UserService::class); $userService->register('John Doe'); return new ViewModel(); }
Explanation:
UserService
depends on Logger
, but we don't create it manually.A Factory in Zend/Laminas is a special class or function used by the Service Manager to create and return an instance of a service.
It is especially useful when:
A factory gives you full control over how a service (or controller) is constructed.
namespace Application\Factory; use Psr\Container\ContainerInterface; use Application\Service\UserService; use Application\Service\Logger; class UserServiceFactory { public function __invoke(ContainerInterface $container, $requestedName, array $options = null) { $logger = $container->get(Logger::class); return new UserService($logger); } }
Module.php
(module\Application\src\Module.php)use Application\Service\Logger; use Application\Service\UserService; use Application\Factory\UserServiceFactory; class Module { public function getConfig(): array { /** @var array $config */ $config = include __DIR__ . '/../config/module.config.php'; return $config; } public function getServiceConfig() { return [ 'factories' => [ Logger::class => function($container) { return new Logger(); }, UserService::class => UserServiceFactory::class, ], ]; } }
use Application\Service\UserService; public function indexAction() { $userService = $this->getEvent() ->getApplication() ->getServiceManager() ->get(Application\Service\UserService::class); $userService->register('Alpha User'); return new ViewModel(); }
Explanation:
UserServiceFactory
creates UserService
and injects Logger
UserService
is requestedIn Zend/Laminas Service Manager, "shared" means the same instance of a service is reused every time it’s requested.
"non-shared" means a new instance is created each time the service is fetched.
Use Case |
Shared (Default) |
Non-Shared |
---|---|---|
Fast access |
Same object reused |
Slow if heavy to build |
Global state |
Keeps data in memory |
Not suitable for state |
Stateless |
Avoid if using per-user data |
Good for temporary or disposable objects |
module\Application\src\Service\CounterService.php
class Counter { private $count = 0; public function increment() { return ++$this->count; } }
Module.php
(module\Application\src\Module.php)use Application\Service\CounterService; class Module { public function getConfig(): array { /** @var array $config */ $config = include __DIR__ . '/../config/module.config.php'; return $config; } public function getServiceConfig() { return [ 'factories' => [ CounterService::class => function($container) { return new CounterService(); } ], 'shared' => [ CounterService::class => false, // This makes it non-shared ], ]; } }
use Application\Service\CounterService; public function homeAction() { $counter1 = $this->getEvent() ->getApplication() ->getServiceManager()->get(CounterService::class); echo $counter1->increment(); // 1 $counter2 = $this->getEvent() ->getApplication() ->getServiceManager()->get(CounterService::class); echo $counter2->increment(); // 1 again (non-shared) return new ViewModel(); }
Explanation:
Counter
instance every time.An Alias is an alternative name for a service. It allows you to refer to services with custom, simpler names.
Reason |
Benefit |
---|---|
Simplicity |
Use short names instead of long class names |
Cleaner Code |
Easier to refactor or rename later |
Flexible |
Change underlying class without changing usage everywhere |
Module.php
(module\Application\src\Module.php)use Application\Service\CounterService; class Module { public function getConfig(): array { /** @var array $config */ $config = include __DIR__ . '/../config/module.config.php'; return $config; } public function getServiceConfig() { return [ 'factories' => [ CounterService::class => function($container) { return new CounterService(); }, ], 'aliases' => [ 'Counter' => CounterService::class, ], ]; } }
public function homeAction() { $counter1 = $this->getEvent() ->getApplication() ->getServiceManager()->get('Counter'); echo $counter1->increment(); // 1 $counter2 = $this->getEvent() ->getApplication() ->getServiceManager()->get('Counter'); echo $counter2->increment(); // 1 again (non-shared) return new ViewModel(); }
Explanation:
Counter
is just an alias.CounterService
class factory.Zend/Laminas uses factories for controllers, just like services.
This allows injecting services into controllers — instead of creating them manually inside the controller.
Benefit |
Description |
---|---|
Testable Code |
Easy to mock services in unit tests |
Single Responsibility |
Controller focuses only on request handling |
Loose Coupling |
Swappable services, easier maintenance |
class MainControllerFactory { public function __invoke(ContainerInterface $container, $requestedName, array $options = null) { $counterService = $container->get(CounterService::class); return new MainController($counterService); } }
Module.php
(module\Application\src\Module.php)use Application\Service\CounterService; class Module { public function getConfig(): array { /** @var array $config */ $config = include __DIR__ . '/../config/module.config.php'; return $config; } public function getServiceConfig() { return [ 'factories' => [ CounterService::class => function($container) { return new CounterService(); } ] ]; } }
use Application\Controller\MainController; use Application\Factory\MainControllerFactory; 'controllers' => [ 'factories' => [ MainController::class => MainControllerFactory::class, ], ],
use Application\Service\CounterService; class MainController extends AbstractActionController { private $counterService; public function __construct(CounterService $counterService) { $this->counterService = $counterService; } public function homeAction() { echo $this->counterService->increment(); echo $this->counterService->increment(); return new ViewModel(); } }
The Service Manager is ideal for managing:
Use Case |
Example Class |
---|---|
Database |
Laminas\Db\Adapter\Adapter |
|
|
Auth |
Laminas\Authentication\AuthenticationService |
APIs |
|
Custom Logic |
|