The service manager
A service is an ordinary class that does one job: calculates, sends mail, reads a table. The service manager is a container: an object that knows how to create every service and creates it on first use. Thanks to it a controller does not write new itself — it receives ready objects through its constructor.
A service
A module's services go into module/<Module>/src/Service. Create the file by hand:
// module/Blog/src/Service/ReadingTime.php
namespace Blog\Service;
class ReadingTime
{
public function minutes(string $text): int
{
$words = count(preg_split('/\s+/u', trim($text)));
return max(1, (int) ceil($words / 200));
}
}The class calculates how many minutes it takes to read a text. It does not depend on Laminas in any way.
Registering the service
Services are described in the service_manager section of module/Blog/config/module.config.php:
// module/Blog/config/module.config.php
'service_manager' => [
'factories' => [
Service\ReadingTime::class => InvokableFactory::class,
],
],On the left is the service name (usually the class name), on the right the factory. InvokableFactory suits classes whose constructor takes no arguments.
A controller factory
For the controller to receive the service it needs a factory of its own — a class with an __invoke() method that pulls dependencies out of the container. Create the file next to the controller:
// module/Blog/src/Controller/PostControllerFactory.php
namespace Blog\Controller;
use Blog\Service\ReadingTime;
use Psr\Container\ContainerInterface;
class PostControllerFactory
{
public function __invoke(ContainerInterface $container): PostController
{
return new PostController(
$container->get(ReadingTime::class),
);
}
}Replace the controller's factory in the configuration:
'controllers' => [
'factories' => [
Controller\PostController::class => Controller\PostControllerFactory::class,
],
],Using it in the controller
// module/Blog/src/Controller/PostController.php
use Blog\Service\ReadingTime;
class PostController extends AbstractActionController
{
public function __construct(private ReadingTime $readingTime) {}
public function viewAction()
{
$body = 'A post body made of a few words.';
return new ViewModel([
'body' => $body,
'minutes' => $this->readingTime->minutes($body),
]);
}
}In the view.phtml view script the value is available as $this->minutes. Open /blog/1 — next to the text you can print "Reading time: 1 min".
Sections of service_manager
| Section | What it describes |
|---|---|
factories | A service and the factory that creates it |
aliases | A second name for an existing service |
invokables | Classes without dependencies (a short form) |
abstract_factories | Factories that create services by a general rule |
shared | Whether the service is created once (yes by default) |
Controllers are described in a separate controllers section, but it works the same way.
Configuration in a factory
The container holds the whole configuration array under the name config. A factory can take a value from it and pass it to the service: $container->get('config')['blog']['posts_per_page'].
Common mistake
Passing the container itself into a controller and calling $container->get() inside actions hides the dependencies: the constructor no longer shows what the class needs, and the class is hard to create in a test. The container should appear only in factories, while classes receive ready objects.