Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Диспетчер сервисов

Сервис — обычный класс, который делает одну работу: считает, отправляет письма, читает таблицу. Диспетчер сервисов (service manager) — это контейнер: объект, который знает, как создать каждый сервис, и создаёт его при первом обращении. Благодаря ему контроллер не пишет new сам, а получает готовые объекты через конструктор.

Сервис

Сервисы модуля кладут в module/<Модуль>/src/Service. Создайте файл вручную:

// 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));
    }
}

Класс считает, сколько минут займёт чтение текста. От Laminas он никак не зависит.

Регистрация сервиса

Сервисы описывают в разделе service_manager файла module/Blog/config/module.config.php:

// module/Blog/config/module.config.php
'service_manager' => [
    'factories' => [
        Service\ReadingTime::class => InvokableFactory::class,
    ],
],

Слева — имя сервиса (обычно имя класса), справа — фабрика. InvokableFactory подходит классам, у конструктора которых нет аргументов.

Фабрика контроллера

Чтобы контроллер получил сервис, ему нужна своя фабрика — класс с методом __invoke(), который достаёт зависимости из контейнера. Создайте файл рядом с контроллером:

// 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),
        );
    }
}

Замените фабрику контроллера в настройках:

'controllers' => [
    'factories' => [
        Controller\PostController::class => Controller\PostControllerFactory::class,
    ],
],

Использование в контроллере

// module/Blog/src/Controller/PostController.php
use Blog\Service\ReadingTime;
 
class PostController extends AbstractActionController
{
    public function __construct(private ReadingTime $readingTime) {}
 
    public function viewAction()
    {
        $body = 'Текст статьи из нескольких слов.';
 
        return new ViewModel([
            'body' => $body,
            'minutes' => $this->readingTime->minutes($body),
        ]);
    }
}

В шаблоне view.phtml значение доступно как $this->minutes. Откройте /blog/1 — рядом с текстом можно вывести «Время чтения: 1 мин.».

Разделы service_manager

РазделЧто описывает
factoriesСервис и фабрику, которая его создаёт
aliasesВторое имя существующего сервиса
invokablesКлассы без зависимостей (короткая запись)
abstract_factoriesФабрики, создающие сервисы по общему правилу
sharedСоздавать ли сервис один раз (по умолчанию да)

Контроллеры описывают в отдельном разделе controllers, но устроен он так же.

Настройки в фабрике

В контейнере под именем config лежит весь массив настроек. Фабрика может взять оттуда значение и передать сервису: $container->get('config')['blog']['posts_per_page'].

Частая ошибка

Передать в контроллер сам контейнер и вызывать $container->get() внутри действий — значит спрятать зависимости: по конструктору уже не видно, что нужно классу, и в тесте его трудно создать. Контейнер должен встречаться только в фабриках, а классы получают готовые объекты.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.