Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

SectionWhat it describes
factoriesA service and the factory that creates it
aliasesA second name for an existing service
invokablesClasses without dependencies (a short form)
abstract_factoriesFactories that create services by a general rule
sharedWhether 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.

Thanks for reading!

Donate to the site — we work hard for you.