Статьи

Сборка модуля Drupal 8 — конфигурация и сервисный контейнер

Помните, что из-за процесса разработки Drupal 8 на момент написания этой статьи некоторые части кода могли быть устаревшими. Взгляните на этот репозиторий, в котором я пытаюсь обновить пример кода и заставить его работать с последней версией Drupal 8.

В предыдущей статье о разработке модулей для Drupal 8 мы рассматривали создание типов и форм блоков. Мы видели, что блоки теперь можно использовать повторно и как все, что нам нужно сделать для определения типов блоков, происходит в одном классе. Точно так же функции генерации форм также сгруппированы в один класс с конкретными методами, выполняющими задачи, аналогичные тем, которые мы привыкли в Drupal 7.

В этом уроке я продолжу с того места, где мы остановились. Я проиллюстрирую, как мы можем превратить нашу DemoForm в форму, используемую для хранения значений через систему конфигурации Drupal 8. После этого мы немного поговорим о контейнере службы и внедрении зависимостей в качестве иллюстрации.

Не забывайте, что вы можете проверить этот репозиторий, если хотите получить весь код, который мы пишем в этой серии руководств.

Формы конфигурации

Когда мы впервые определили нашу DemoForm , мы расширили класс FormBase, который является самой простой реализацией FormInterface . Тем не менее, Drupal 8 также поставляется с ConfigFormBase, который предоставляет некоторые дополнительные функции, которые позволяют очень легко взаимодействовать с системой конфигурации.

Теперь мы будем преобразовывать DemoForm в тот, который будет использоваться для хранения адреса электронной почты, который вводит пользователь. Первое, что мы должны сделать, это заменить расширенный класс на ConfigFormBase (и, конечно, использовать его):

 use Drupal\Core\Form\ConfigFormBase; class DemoForm extends ConfigFormBase { 

Прежде чем мы перейдем к изменению других вещей в форме, давайте немного разберемся, как простая конфигурация работает в Drupal 8. Я говорю простой, потому что есть также объекты конфигурации, которые являются более сложными и которые мы не будем рассматривать сегодня. В настоящее время конфигурация, предоставляемая модулями (core или contrib), хранится в файлах YAML. При включении модуля эти данные импортируются в базу данных (для повышения производительности при работе с ним). Через пользовательский интерфейс мы можем изменить эту конфигурацию, которая затем легко экспортируется в файлы YAML для развертывания на разных сайтах.

Модуль может предоставить конфигурацию по умолчанию в файле YAML, расположенном в папке config/install в корневом каталоге модуля. Соглашение о присвоении имени этому файлу заключается в том, чтобы поставить перед ним имя модуля. Итак, давайте создадим один с именем demo.settings.yml . Внутри этого файла давайте вставим следующее:

 demo: email_address: [email protected] 

Это вложенная структура (как ассоциативный массив в PHP). Под demo ключа у нас есть другая пара ключ | значение. И обычно для доступа к этим вложенным значениям мы используем точку ( . ). В нашем случае demo.email_address .

Как только у нас будет этот файл, важно помнить, что этот файл импортируется только после установки модуля. Так что давай и переустанови его. И теперь мы можем вернуться к нашей форме и пройти методы, которые необходимо адаптировать один за другим.

Вот как buildForm() должен выглядеть метод buildForm() :

 public function buildForm(array $form, array &$form_state) { $form = parent::buildForm($form, $form_state); $config = $this->config('demo.settings'); $form['email'] = array( '#type' => 'email', '#title' => $this->t('Your .com email address.'), '#default_value' => $config->get('demo.email_address') ); return $form; } 

Прежде всего, в отличие от FormBase , класс ConfigFormBase реализует этот метод для добавления элементов в массив формы (кнопка отправки). Таким образом, мы можем использовать то, что сделал родитель, перед добавлением наших собственных элементов.

Теперь для части конфигурации. Drupal 8 предоставляет объект Config, который мы можем использовать для взаимодействия с конфигурацией. В некоторых классах он уже доступен через внедрение зависимостей. ConfigFormBase — один из таких классов.

Как вы можете видеть, мы используем метод config () родительского класса для извлечения объекта Config заполненного нашей простой конфигурацией demo.settings . Затем для #default_value элемента формы электронной почты мы используем метод get () объекта Config для получения значения адреса электронной почты.

Далее нам нужно только изменить обработчик отправки, потому что метод validateForm() может пока оставаться прежним:

 public function submitForm(array &$form, array &$form_state) { $config = $this->config('demo.settings'); $config->set('demo.email_address', $form_state['values']['email']); $config->save(); return parent::submitForm($form, $form_state); } 

В этом методе мы сначала получаем объект Config для нашей конфигурации (как мы делали раньше). Затем мы используем его метод set () для изменения значения email_address на значение, предоставленное пользователем. Затем мы используем метод save () для сохранения конфигурации. Наконец, мы расширяем родительский обработчик отправки, поскольку он содержит некоторые функции (в этом случае он выводит сообщение Drupal на экран).

И это в значительной степени так. Вы можете очистить кэш и попробовать его. Отправляя новый адрес электронной почты, вы сохраняете его в конфигурации. Файл модуля demo.settings.yml конечно, не изменится, но вы можете пойти и экспортировать конфигурацию demo.settings и импортировать ее на другой сайт.

Служебный контейнер и внедрение зависимостей

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

Внедрение зависимостей — это способ, которым мы передаем объекты другим объектам, чтобы обеспечить разделение. Каждый сервис должен иметь дело с одной вещью, и если ему нужен другой сервис, последний может быть введен в первый. Но посмотрим как через минуту.

В дальнейшем мы создадим очень простой сервис и зарегистрируем его в контейнере. У него будет только один реальный метод, который возвращает простое значение. Затем мы внедрим этот сервис как зависимость от нашего DemoController и воспользуемся значением, предоставленным сервисом.

Чтобы зарегистрировать сервис, нам нужно создать файл demo.services.yml расположенный в корне нашего модуля, со следующим содержимым:

 services: demo.demo_service: class: Drupal\demo\DemoService 

Соглашение об именовании файлов: module_name.services.yml .

Первая строка создает массив сервисов. Вторая строка определяет первый сервис (называемый demo_service , с префиксом имени модуля). Третья строка указывает класс, который будет создан для этой службы. Следует создать DemoService.php класса DemoService.php в папке src/ нашего модуля. Вот что делает мой сервис (на самом деле ничего, только чтобы проиллюстрировать, как его использовать):

 <?php /** * @file * Contains Drupal\demo\DemoService. */ namespace Drupal\demo; class DemoService { protected $demo_value; public function __construct() { $this->demo_value = 'Upchuk'; } public function getDemoValue() { return $this->demo_value; } } 

Не нужно ничего здесь объяснять, так как это очень просто. Далее, давайте обратимся к нашему DemoController и используем этот сервис. Это можно сделать двумя способами: получить глобальный доступ к контейнеру через класс \Drupal или использовать внедрение зависимостей, чтобы передать объект этого класса нашему контроллеру. Лучшая практика говорит о том, что мы должны сделать это вторым способом, так что это то, что мы будем делать. Но иногда вам потребуется доступ к услуге по всему миру. Для этого вы можете сделать что-то вроде этого:

 $service = \Drupal::service('demo.demo_service'); 

И теперь $service — это объект класса DemoService мы только что создали. Но давайте посмотрим, как внедрить наш сервис в класс DemoController как зависимость. Сначала я объясню, что нужно сделать, затем вы увидите весь контроллер со всеми внесенными в него изменениями.

Во-первых, нам нужен доступ к сервисному контейнеру. С контроллерами это действительно легко. Мы можем расширить класс ControllerBase, который дает нам это в дополнение к некоторым другим помощникам. Кроме того, наш контроллер может реализовать ContainerInjectionInterface, который также дает нам доступ к контейнеру. Но мы будем придерживаться ControllerBase поэтому нам нужно будет использовать этот класс.

Затем нам также необходимо использовать Symfony 2 ContainerInterface как требование метода create() который создает экземпляр другого объекта нашего класса контроллера и передает ему нужные нам сервисы.

Наконец, нам понадобится конструктор, чтобы получить переданные служебные объекты (те, которые create() ) и назначить их свойствам для дальнейшего использования. Порядок, в котором объекты возвращаются методом create() должен отражаться в том порядке, в котором они передаются в конструктор.

Итак, давайте посмотрим наш пересмотренный DemoController :

 <?php /** * @file * Contains \Drupal\demo\Controller\DemoController. */ namespace Drupal\demo\Controller; use Drupal\Core\Controller\ControllerBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** * DemoController. */ class DemoController extends ControllerBase { protected $demoService; /** * Class constructor. */ public function __construct($demoService) { $this->demoService = $demoService; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('demo.demo_service') ); } /** * Generates an example page. */ public function demo() { return array( '#markup' => t('Hello @value!', array('@value' => $this->demoService->getDemoValue())), ); } } 

Как видите, все шаги есть. Метод create() создает новый экземпляр нашего класса контроллера, передавая ему наш сервис, извлеченный из контейнера. И, DemoService класса DemoService сохраняется в $demoService , и мы можем использовать его для вызова его getDemoValue() . И это значение затем используется в сообщении Hello . Очистите кеш и попробуйте. Зайдите в demo/ путь и увидите Hello Upchuk! напечатано на странице.

Я уверен, что вы можете увидеть всю мощь сервисного контейнера, поскольку теперь мы можем написать разделенную функциональность и передать ее там, где это необходимо. Я не показал вам, как, но вы также можете объявить зависимости при регистрации служб. Это означает, что когда Drupal создает экземпляр сервисного объекта, он также делает это для всех своих зависимостей и передает их своему конструктору. Вы можете прочитать больше о том, как это сделать, на этой странице документации .

Вывод

В этой статье мы рассмотрели много интересных вещей. Мы видели, как система конфигурации управляет простой конфигурацией и что у нас есть для этого в форме . Я призываю вас изучить, как реализован ConfigFormBase и что у вас есть, если вы его расширите. Кроме того, вы должны поиграть в пользовательском интерфейсе с импортом / экспортом конфигурации между сайтами. Это станет большим улучшением для процесса развертывания.

Затем мы посмотрели на сервисы, что они такое и как они работают. Отличный способ сохранить многократно используемые и разъединенные части функциональности, доступные из любой точки мира. И я надеюсь, что концепция внедрения зависимостей уже не так страшна (если бы она была для вас). По сути, это эквивалент передачи параметров процедурным функциям, но выполняется с помощью методов-конструкторов (или сеттеров) под капотом Symfony и его замечательного сервисного контейнера.