Диспетчер сервисов
Сервис — обычный класс, который делает одну работу: считает, отправляет письма, читает таблицу. Диспетчер сервисов (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() внутри действий — значит спрятать зависимости: по конструктору уже не видно, что нужно классу, и в тесте его трудно создать. Контейнер должен встречаться только в фабриках, а классы получают готовые объекты.