Encodera

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

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

Оглавление

Сервисы и внедрение зависимостей

Сервис — объект, который выполняет одну работу и доступен из любого места сайта: подключение к базе, текущий пользователь, отправка почты. Свою логику тоже оформляют сервисом — тогда один и тот же код используют контроллер, форма, блок и хук.

Класс сервиса

Сервис — обычный класс в папке src модуля. Напишем подсчёт времени чтения статьи. Создайте файл вручную:

// web/modules/custom/blog/src/ReadingTime.php
namespace Drupal\blog;
 
class ReadingTime
{
    public function minutes(string $text): int
    {
        $words = preg_split('/\s+/u', trim(strip_tags($text)), -1, PREG_SPLIT_NO_EMPTY);
 
        return max(1, (int) ceil(count($words) / 200));
    }
}

Метод считает слова в тексте и делит на 200 — столько слов в минуту читает человек. От Drupal в классе пока ничего нет.

Регистрация

Чтобы класс стал сервисом, его описывают в файле blog.services.yml в корне папки модуля. Создайте его вручную:

# web/modules/custom/blog/blog.services.yml
services:
  blog.reading_time:
    class: Drupal\blog\ReadingTime

blog.reading_time — имя сервиса, по нему его получают. После drush cr Drupal знает о сервисе и создаст объект при первом обращении, один на весь запрос.

Получение в контроллере

Класс не создаёт сервис сам, а получает готовый объект через конструктор. Это и называют внедрением зависимостей. В контроллере для этого служит статический метод create():

// web/modules/custom/blog/src/Controller/BlogController.php
namespace Drupal\blog\Controller;
 
use Drupal\blog\ReadingTime;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
 
class BlogController extends ControllerBase
{
    public function __construct(private ReadingTime $readingTime) {}
 
    public static function create(ContainerInterface $container): static
    {
        return new static($container->get('blog.reading_time'));
    }
 
    public function about(): array
    {
        $minutes = $this->readingTime->minutes('Блог о разработке на Drupal.');
 
        return ['#markup' => $this->t('Время чтения: @min мин.', ['@min' => $minutes])];
    }
}

Drupal вызывает create() и передаёт в него контейнер — хранилище всех сервисов. Метод достаёт нужные по имени и отдаёт их конструктору. Страница /blog/about покажет «Время чтения: 1 мин.». Так же устроены формы и блоки.

Получение в хуке

В функциях файла blog.module конструктора нет. Там сервис берут через класс \Drupal:

// web/modules/custom/blog/blog.module
$minutes = \Drupal::service('blog.reading_time')->minutes($text);

Сервис с зависимостями

Сервису тоже могут понадобиться другие сервисы. Их перечисляют в arguments; знак @ означает «сервис с таким именем»:

# web/modules/custom/blog/blog.services.yml
services:
  blog.reading_time:
    class: Drupal\blog\ReadingTime
    arguments: ['@config.factory']
// web/modules/custom/blog/src/ReadingTime.php
namespace Drupal\blog;
 
use Drupal\Core\Config\ConfigFactoryInterface;
 
class ReadingTime
{
    public function __construct(private ConfigFactoryInterface $configFactory) {}
 
    public function minutes(string $text): int
    {
        $speed = $this->configFactory->get('blog.settings')->get('words_per_minute') ?? 200;
        $words = preg_split('/\s+/u', trim(strip_tags($text)), -1, PREG_SPLIT_NO_EMPTY);
 
        return max(1, (int) ceil(count($words) / $speed));
    }
}

Теперь скорость чтения берётся из настроек модуля, а пока их нет — равна 200. Настройки разобраны в уроке о конфигурации. Порядок аргументов в arguments совпадает с порядком в конструкторе.

Частые сервисы ядра

ИмяЧто даёт
entity_type.managerЗагрузка и сохранение сущностей
current_userТекущий пользователь
config.factoryНастройки
databaseПодключение к базе данных
messengerСообщения посетителю
logger.factoryЖурнал
http_clientЗапросы к другим сайтам

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

Вызовы \Drupal::service() внутри классов — контроллеров, форм, своих сервисов — скрывают зависимости: по конструктору не видно, что нужно классу, и в тесте его не создать. Статический доступ оставляйте для функций-хуков, а в классах передавайте сервисы через конструктор. И помните: blog.services.yml читается только при drush cr.

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

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