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 LoggerUserService 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 |
|