Построение модуля Drupal 8: блоки и модули

Эта статья является второй из трех частей серии о том, как построить модуль Drupal 8.
Как построить модуль Drupal 8:

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

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

В этой статье мы продолжим работу с нашим модулем, который вы можете посмотреть в этом хранилище, и рассмотрим два других важных функциональных элемента: блоки и формы.

Для этого мы создадим пользовательский блок, который возвращает некоторый настраиваемый текст. После этого, мы создадим простую форму, используемую для вывода на экран введенных пользователем данных.

Блоки Drupal 8

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

Вы можете создать в пользовательском интерфейсе блок и использовать его по всему сайту — вы больше не ограничены в использовании блока только одним местом применения.

Давайте продолжим и создадим простой тип блока, который выводит на экран по умолчанию «Привет, Мир!«. Все, что нам понадобится — это один файл классов, расположенный в папке src/Plugin/Block корневой директории нашего модуля.

Давайте назовем наш новый тип блока DemoBlock, и, естественно, он должен будет содержаться в файле с именем DemoBlock.php. В этот файл мы для начала можем добавить следующее:

<?php

namespace DrupaldemoPluginBlock;

use DrupalblockBlockBase;
use DrupalCoreSessionAccountInterface;

/**
 * Provides a 'Demo' block.
 *
 * @Block(
 *   id = "demo_block",
 *   admin_label = @Translation("Demo block"),
 * )
 */

class DemoBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {    
    return array(
      '#markup' => $this->t('Привет, Мир!'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function access(AccountInterface $account) {
    return $account->hasPermission('access content');
  }

}

Как и для любого другого файла классов, мы начинаем с определения области имен нашего класса. Затем мы используем класс BlockBase, так чтобы мы могли расширить его, а также класс AccountInterface, так чтобы мы могли получить доступ к текущему активному пользователю. Затем следует то, с чем вы определенно не встречались в Drupal 7: аннотации.

Аннотации являются инструментом пояснения PHP, они расположены в блоке комментариев того же файла, в котором определяется класс. Используя аннотации, мы сообщаем Drupal, что мы хотим зарегистрировать новый тип блока (@Block) с идентификатором demo_block и admin_label — Demo block (передаваемым через систему перевода).

Далее, мы расширяем класс BlockBase в наш собственный DemoBlock, внутри которого мы реализуем два метода (наиболее распространенные из них мы будем рассматривать подробно позже). Метод build() — наиболее важный из них, так как он возвращает отображаемый массив, который будет выводить блок. Метод access() контролирует права доступа для просмотра этого блока.

Параметр, передаваемый в него, является экземпляром объекта класса AccountInterface, в качестве которого в этом случае будет выступать текущий пользователь.

Еще одна интересная вещь, которую я хочу отметить. Мы больше не используем для перевода глобально функцию t(), а ссылаемся на метод t(), реализованный в родительском классе.

Вот и все. Теперь вы можете очистить кэш и перейти на страницу конфигурации Block layout. Здорово, что типы блоков располагаются справа (где вы можете их отфильтровать), и вы можете разместить один или несколько из этих типов блоков в различных местах на вашем сайте.

Конфигурация блока Drupal 8

Теперь, когда мы рассмотрели, как создать новый тип блока, используемый в пользовательском интерфейсе, давайте продолжим и добавим для него форму конфигурации.

Мы сделаем так, чтобы вы могли отредактировать блок, указать в текстовом поле имя, после чего блок будет говорить «Привет» не миру, а этому имени.

Во-первых, мы должны определить форму, содержащую текстовое поле. Так в нашем классе DemoBlock мы можем добавить новый метод blockForm():

/**
 * {@inheritdoc}
 */
public function blockForm($form, &$form_state) {

  $form = parent::blockForm($form, $form_state);

  $config = $this->getConfiguration();

  $form['demo_block_settings'] = array(
    '#type' => 'textfield',
    '#title' => $this->t('Who'),
    '#description' => $this->t('Кому Вы хотите сказать "Привет"?'),
    '#default_value' => isset($config['demo_block_settings']) ? $config['demo_block_settings'] : '',
  );

  return $form;
}

Эта форма реализации API очень похожа на Drupal 7. Однако есть и определенные новые элементы. Во-первых, мы извлекаем массив $form из родительского класса (так мы создаем свою форму на базе существующей, добавляя собственное поле).

Это элементы стандарта OOP. Затем мы извлекаем и сохраняем конфигурацию этого блока.

Класс BlockBase определяет метод getConfiguration(), который делает это для нас. И в случае, если он был уже установлен, мы устанавливаем для demo_block_settings значение #default_value.

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

/**
* {@inheritdoc}
*/
public function blockSubmit($form, &$form_state) {

 $this->setConfigurationValue('demo_block_settings', $form_state['values']['demo_block_settings']);

}

Этот метод также находится внутри класса DemoBlock, и все, что он делает, это сохраняет значение поля demo_block_settings в качестве нового пункта конфигурации блока (с ключом того же имени для соответствия).

Наконец, мы должны настроить наш метод build(), так чтобы включить в него имя, которому блок должен говорить «Привет!«:

/**
 * {@inheritdoc}
 */
public function build() {

  $config = $this->getConfiguration();

  if (isset($config['demo_block_settings']) && !empty($config['demo_block_settings'])) {
    $name = $config['demo_block_settings'];
  }
  else {
    $name = $this->t('никто');
  }

  return array(
    '#markup' => $this->t('Привет, @name!', array('@name' => $name)),
  );  
}

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

Для этого отредактируйте блок, чтобы назначить ему регион, и добавьте имя, которому блок должен сказать «Привет

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

Формы Drupal 8

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

В Drupal 8 функции определения формы сгруппированы внутри класса. Давайте определим простой класс DemoForm в файле src/Form/DemoForm.php:

<?php

/**
 * @file
 * Contains DrupaldemoFormDemoForm.
 */

namespace DrupaldemoForm;

use DrupalCoreFormFormBase;

class DemoForm extends FormBase {

  /**
   * {@inheritdoc}.
   */
  public function getFormId() {
    return 'demo_form';
  }

  /**
   * {@inheritdoc}.
   */
  public function buildForm(array $form, array &$form_state) {

    $form['email'] = array(
      '#type' => 'email',
      '#title' => $this->t('Your .com email address.')
    );
    $form['show'] = array(
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    );

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, array &$form_state) {

    if (strpos($form_state['values']['email'], '.com') === FALSE ) {
      $this->setFormError('email', $form_state, $this->t('This is not a .com email address.'));
    } 
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, array &$form_state) {

    drupal_set_message($this->t('Your email address is @email', array('@email' => $form_state['values']['email'])));
  }

}

Помимо элементов стандарта ООР, все остальное должно выглядеть для вас очень знакомым по Drupal 7. Форма API осталась в значительной степени такой же (за исключением добавления некоторых новых элементов формы и этого класса инкапсуляции). Так что же происходит в приведенном выше коде?

Во-первых, мы объявляем область имен класса и используем класс ядра FormBase, чтобы мы могли расширить его нашим собственным классом DemoForm. После этого мы реализуем четыре метода, три из которых должны быть вам знакомы. Метод getFormId() является новым. Его использование обязательно. Он применяется, просто чтобы возвращать машинное имя формы.

Метод buildForm() также обязателен, он создает форму. Как? Так же, как и в Drupal 7. Метод validateForm() является необязательным, и его предназначение должно быть вам понятно по Drupal 7. И, наконец, метод submitForm() производит обработку предоставления данных. Все очень логично и организовано.

Так чего мы пытаемся достичь с помощью этой формы? У нас есть поле электронной почты (новый элемент формы в Drupal 8), мы хотим, чтобы пользователи заполняли его. По умолчанию Drupal проверяет, является ли входное значение в действительности адресом электронной почты.

Но наша функция проверки определяет, принадлежит ли этот адрес электронной почты серверу с доменом .com, и если нет, то форма выдает ошибку. И в конце обработчик предоставленных данных просто выводит сообщение на странице.

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

demo.form:
  path: '/demo/form'
  defaults:
    _form: 'DrupaldemoFormDemoForm'
    _title: 'Demo Form'
  requirements:
    _permission: 'access content'

Этот код должен выглядеть для вас знакомым по предыдущей статье, в которой мы рассматривали маршрутизацию для простой страницы. Единственное отличие заключается в том, что вместо _content в блоке defaults, мы используем _form, чтобы указать, что целью является класс формы. И поэтому значением является имя класса, который мы только что создали.

Очистите кэш и перейдите по адресу demo/form, чтобы проверить форму.

Если вы знакомы с drupal_get_form(), и задаетесь вопросом, как вам загрузить форму способом, к которому вы привыкли по Drupal 7, ответ заключается в глобальном классе Drupal.

Таким образом, чтобы получить форму, вы можете использовать его метод formBuilder() и сделать что-то вроде этого:

$form = Drupal::formBuilder()->getForm('DrupaldemoFormDemoForm');

После этого вы можете вернуть $form, которая будет представлять собой отображаемый массив формы.

Заключение

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

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

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

Перевод статьи «Build a Drupal 8 Module: Blocks and Forms» был подготовлен дружной командой проекта Сайтостроение от А до Я.