Ця стаття є третьою з трьох частин серії про те, як побудувати модуль Drupal 8.

Як побудувати модуль Drupal 8:

• Побудова модуля Drupal 8: маршрутизація, контролери і меню посилань
• Побудова модуля Drupal 8: блоки і форми
• Побудова модуля Drupal 8: управління конфігурацією і контейнер служб

У попередній статті ми розглянули створення типів блоків і форм.

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

Аналогічно, функція, що генерує форми, також укладена в одному класі з методами, які виконують завдання, аналогічні тим, до яких ми звикли в Drupal 7.

У цій статті ми продовжимо з того місця, на якому зупинилися в попередній статті. Я покажу вам, як ми можемо перетворити DemoForm у форму, що використовується через систему конфігурації Drupal 8 для зберігання значення. Після цього, ми поговоримо трохи про контейнері служб і встановлення залежностей.

Не забувайте, що, якщо вам потрібен буде весь код, який ми створюємо по ходу цієї серії статей, ви можете подивитися це сховище.

Конфігурація форми

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

Тепер ми збираємося перетворити форму DemoForm у форму, яка буде використовуватися для зберігання адреси електронної пошти, який вводить користувач. Перше, що ми повинні зробити, це замінити розширений клас на ConfigFormBase (і, звичайно, застосувати його):

use DrupalCoreFormConfigFormBase;
class DemoForm extends ConfigFormBase {

Перш, ніж ми перейдемо до зміни інших елементів форми, давайте розглянемо, як в Drupal 8 працює проста конфігурація. Я кажу проста, тому що існують також і види конфігурації, які є набагато більш складними, ніж та, що ми будемо розглядати сьогодні.

Станом на поточний момент, конфігурація забезпечується модулями (модулями ядра і сторонніми), що зберігаються в YAML-файли. При включенні модуля, ці дані імпортуються в базу даних (для підвищення продуктивності при роботі з ним).

Через інтерфейс ми можемо змінити цю конфігурацію, після чого ми можемо легко експортувати її в YAML файли для розгортання на різних сайтах.

Модуль може задавати в YAML-файлі конфігурації за замовчуванням. Файл знаходиться в папці config/install кореневої директорії модуля. Конвенція призначення імен передбачає додавання до імені файлу префікса з назвою модуля. Давайте створимо файл demo.settings.yml. І додамо в нього наступний код:

demo:
email_address: [email protected]

Це вкладена структура (зразок асоційованого масиву в PHP). Нижче ключа demo, ми розміщуємо іншу пару ключ/значення. І, як правило, щоб отримати доступ до цих вкладеним значень ми використовуємо точку (.). У нашому випадку demo.email_address.

Головне, що ви повинні пам’ятати цей файл імпортується тільки тоді, коли встановлений модуль. Тому, після того, як ми створили цей файл, нам потрібно перевстановити модуль. І тепер ми можемо повернутися до форми і налаштувати методи.

Ось, як повинен виглядати метод 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: DrupaldemoDemoService

Ім’я файлу задається за шаблоном module_name.services.yml.

У першій рядку створюється масив служб. Друга рядок визначає першу службу (викликану файлом demo_service, з префіксом імені модуля). Третій рядок визначає клас, який буде створений для цієї служби. З цього випливає, що створюється файл класу DemoService.php у папці src/ нашого модуля.

Це те, що робить моя служба (насправді нічого, я наводжу цей код, просто щоб показати, як його використовувати):

demo_value = ‘Upchuk’;
}
public function getDemoValue() {
return $this->demo_value;
}
}

Я не буду докладно пояснювати цей код, оскільки він носить загальний характер. Далі давайте повернемося до нашого DemoController і використовуємо цю службу.

Є два способи, за допомогою яких ми можемо це зробити: отримати доступ до контейнера глобально через клас Drupal або використовувати введення залежностей, щоб передати об’єкт цього класу в наш контролер.

Більш правильно буде використовувати другий спосіб, саме це я і збираюся зробити. Але в деяких випадках вам потрібно буде отримати доступ до служби глобально. Для цього, ви можете зробити щось на зразок цього:

$service = Drupal::service(‘demo.demo_service’);

І тепер $service є об’єктом класу DemoService, який ми тільки що створили. Але давайте подивимося, як вводити нашу службу в клас DemoController, як залежності. Я покажу, що ви повинні зробити спочатку, а потім ви побачите весь контролер з усіма змінами, внесеними в нього.

По-перше, нам потрібен доступ до контейнера служби. З допомогою контролерів це дійсно просто зробити. Ми можемо розширити клас ControllerBase, який разом з деякими іншими допоміжними компонентами дозволяє нам зробити це.

З іншого боку, наш контролер може реалізувати ContainerInjectionInterface, який також дає нам доступ до контейнера. Але ми будемо використовувати ControllerBase, тому нам потрібно застосувати цей клас.

Далі, ми повинні застосувати ContainerInterface з Symfony 2, це вимога методу create(), який створює інший об’єкт нашого класу контролера і передає йому потрібну нам службу.

І у кінці нам потрібен конструктор, щоб отримати передані об’єкти служби (ті, які повертає метод create()) і призначити їх властивості для подальшого використання.

Черговість, у якій об’єкти повертаються методом create(), повинна відповідати черговості їх передачі в конструктор.

Отже, давайте розглянемо наш змінений DemoController:

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, і ми можемо використовувати його, щоб викликати його метод getDemoValue().

Це значення потім використовується в повідомленні «Привіт, …!». Очистіть кеш і спробуйте перейти за адресою demo/. Ви повинні побачити на сторінці напис «Привіт, Upchuk!»

Я впевнений, що ви оціните всі можливості, які дає нам контейнер служб, так як тепер ми можемо прописувати окремі функції і передавати їх туди, куди нам потрібно. Я не буду показувати, але ви також можете оголошувати залежності при реєстрації служби.

Це означає, що, коли в Drupal встановлюється об’єкт служби, він буде встановлюватися для всіх залежностей, а також передаватися в конструктор. Ви можете дізнатися більше про те, як це робиться з цієї сторінки документації.

Висновок

У цій статті ми розглянули багато цікавого матеріалу. Ми побачили, як через систему конфігурації здійснюється управління простою конфігурацією, і які у нас є для цього інструменти. Я раджу вам вивчити, як реалізується ConfigFormBase і які у вас є можливості для її розширення.

Крім того, ви можете пограти в інтерфейсі з імпортом/експортом конфігурації між сайтами. Це дозволить вам у майбутньому удосконалити процес розгортання сайтів.

Потім ми розглянули служби, що це таке і як вони працюють. Це відмінний спосіб підтримки багаторазово використовуваних і розділених частин функціоналу. І я сподіваюся, що концепція впровадження залежностей також вам вже набагато більш зрозуміла.

Це фактично еквівалент передачі параметрів функцій процедур, але виконаної за допомогою методів конструктора (або сеттерів), з допомогою Symfony і його відмінного контейнера служб.

Переклад статті «Building a Drupal 8 Module – Configuration Management and the Service Container» був підготовлений дружною командою проекту Сайтостроение від А до Я