Drupal 8: Block Plugin API — программное создание блоков

В Drupal мы можем создавать блоки через административный интерфейс, выводить их в различных регионах, настраивать и т.д. Но иногда нам требуется объявить блок программно, в Drupal 7 для этого было много поводов, в 8-ке несколько меньше, но это не уменьшает потребность в программной работе с блоками.

В Drupal 8, в отличие от 7-й версии, блоки были сильно переработаны. Теперь многие вещи можно реализовать без использования Block API. Блоки теперь имеют свои собственные типы блоков, по принципу с типами содержимого, могут иметь поля и прочие данные. Но в данной статье не об изменениях блоков с точки зрения пользователя, а об изменениях с точки зрения разработчика. Про управление через админку я напишу в дальнейшем отдельную статью.

Обзор

В Drupal 8 есть два различных типа блоков:

  • Block Plugin (API) - это как раз о чем мы будем говорить, стандартный API для объявления своих блоков в виде плагинов;
  • Block Entity - сущность, которая хранит привязку блока в регион темы.

Теория

В теория всё очень просто, впрочем, как и на самой практике:

  • Мы объявляем свой класс для блока, наследуясь от BlockBase и описываем нужные нам методы;
  • Добавляем объявленный нами блок и используем.
  • ???
  • PROFIT

Методы блока

Давайте рассмотрим некоторые методы которые мы можем, должны и будем использовать, разумеется не все, но самые интересные и востребованные. Все методы BlockBase вы можете посмотреть на drupal.org.

build()

Данный метод является единственным обязательным методом, который вы должны определить в классе для своего блока. Он должен всегда возвращать render array.

public function build() {
  $block = [
    '#type' => 'markup',
    '#markup' => 'My <strong>example</strong> content.'
  ];
  return $block;
}

blockAccess()

Данный метод отвечает за права доступа. Возвращает TRUE/FALSE. Если TRUE, то блок будет доступен для просмотра, во всех остальных случаях не будет отображаться. Здесь вы можете описать любую нужную вам логику.

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
...
protected function blockAccess(AccountInterface $account) {
  // Отображаем блок только пользователям у которых есть
  // право доступа 'administer blocks'.
  return AccessResult::allowedIfHasPermission($account, 'administer blocks');
}

defaultConfiguration()

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

public function defaultConfiguration() {
  // Если, допустим, ваш блок выводит последних зарегистрированных
  // пользователей и вы хотите дать возможность выбирать сколько
  // показывать пользователей в блоке, то вы можете указать значение
  // по умолчанию. Например 10.
  return array(
    'users_count' => 10,
  );
}

blockForm()

При помощи данного метода вы сможете объявить форму с настройками для данного блока используя Form API.

use Drupal\Core\Form\FormStateInterface;
...
public function blockForm($form, FormStateInterface $form_state) {
  // Получаем оригинальную форму для блока, и добавляем
  // наши новые элементы прямо к ней.
  $form = parent::blockForm($form, $form_state);

  // Получаем конфиги для данного блока.
  $config = $this->getConfiguration();

  // Добавляем наше поле к форме.
  $form['email'] = array(
    '#type' => 'email',
    '#title' => t('E-mail address to send notification'),
    '#default_value' => isset($config['email_to_send']) ? $config['email_to_send'] : '',
  );
  return $form;
}

blockValidate()

Как и в Form API, здесь вы можете провести валидацию введенных данных в форме. В данном случае, есть форма по умолчанию, следовательно, даже не объявляя buildForm() вы можете объявлять данный метод и проводить валидацию.

use Drupal\Core\Form\FormStateInterface;
...
public function blockValidate($form, FormStateInterface $form_state) {
 $ages = $form_state->getValue('ages');

 if (!is_numeric($ages)) {
  $form_state->setErrorByName('ages', t('Needs to be an interger'));
 }
}

blockSubmit()

В случае с данным методом, всё точно также как и с blockValidate(), вы можете определять его не имея собственной формы, тем самым внедряясь в процесс отправки данных формы с настройками для блока и внедрения собственной логики.

use Drupal\Core\Form\FormStateInterface;
...
public function blockSubmit($form, FormStateInterface $form_state) {
  $this->configuration['email_to_send'] = $form_state->getValue('email_to_send');
}

Этих методов должно хватить на большинство потребностей.

Пример простого блока

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

Для создания блока, нам, первым делом, нужно создать файл для класса, через который мы и объявим блок. Классы plugin-блоков хранятся в папке /src/Plugin/Block/BlockName.php. Допустим мы хотим сделать блок SimpleExampleBlock, для этого нам нужно создать файл по данному пути /src/Plugin/Block/SimpleBlockExample.php относительно корня модуля в котором мы его объявляем.

А внутри пишем:

<?php

/**
 * @file
 * Contains \Drupal\helloworld\Plugin\Block\SimpleBlockExample.
 */

// Пространство имён для нашего блока.
// helloworld - это наш модуль.
namespace Drupal\helloworld\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Добавляем простой блок с текстом.
 * Ниже - аннотация, она также обязательна.
 *
 * @Block(
 *   id = "simple_block_example",
 *   admin_label = @Translation("Simple block example"),
 * )
 */
class SimpleBlockExample extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    $block = [
      '#type' => 'markup',
      '#markup' => '<strong>Hello World!</strong>'
    ];
    return $block;
  }

}

Всё, после этого наш блок уже будет доступен в административном интерфейсе для добавления.

Наш блок в административном интерфейсе

И если мы разместим блок, то увидим следующее.

Наш первый блок

Блок с собственной формой

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

Допустим, пусть у нас в форме будет два поля. Первое - текстовое, в котором мы будем вводить строку для содержимого нашего блока, а второе - числовое поле, в котором мы будем писать сколько раз вывести наше сообщение из первого поля в блоке. При этом, оба поля будут обязательными, первое будет требовать минимум 5 символов для ввода, а второе, чтобы введенное число было больше или равнялось единице (1).

Пусть наш блок называется PrintMyMessages, следовательно, нам нужно создать файл /src/Plugin/Block/PrintMyMessages.php. И следующего содержания:

<?php

/**
 * @file
 * Contains \Drupal\helloworld\Plugin\Block\PrintMyMessages.
 */

// Пространство имён для нашего блока.
namespace Drupal\helloworld\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * @Block(
 *   id = "print_my_messages",
 *   admin_label = @Translation("Print my messages"),
 * )
 */
class PrintMyMessages extends BlockBase {

  /**
   * Добавляем наши конфиги по умолчанию.
   *
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return array(
      'count' => 1,
      'message' => 'Hello World!',
    );
  }

  /**
   * Добавляем в стандартную форму блока свои поля.
   *
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {
    // Получаем оригинальную форму для блока.
    $form = parent::blockForm($form, $form_state);
    // Получаем конфиги для данного блока.
    $config = $this->getConfiguration();

    // Добавляем поле для ввода сообщения.
    $form['message'] = array(
      '#type' => 'textfield',
      '#title' => t('Message to printing'),
      '#default_value' => $config['message'],
    );

    // Добавляем поле для количества сообщений.
    $form['count'] = array(
      '#type' => 'number',
      '#min' => 1,
      '#title' => t('How many times display a message'),
      '#default_value' => $config['count'],
    );

    return $form;
  }

  /**
   * Валидируем значения на наши условия.
   * Количество должно быть >= 1,
   * Сообщение должно иметь минимум 5 символов.
   *
   * {@inheritdoc}
   */
  public function blockValidate($form, FormStateInterface $form_state) {
    $count = $form_state->getValue('count');
    $message = $form_state->getValue('message');

    // Проверяем введенное число.
    if (!is_numeric($count) || $count < 1) {
      $form_state->setErrorByName('count', t('Needs to be an interger and more or equal 1.'));
    }

    // Проверяем на длину строки.
    if (strlen($message) < 5) {
      $form_state->setErrorByName('message', t('Message must contain more than 5 letters'));
    }
  }

  /**
   * В субмите мы лишь сохраняем наши данные.
   *
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    $this->configuration['count'] = $form_state->getValue('count');
    $this->configuration['message'] = $form_state->getValue('message');
  }

  /**
   * Генерируем и выводим содержимое блока.
   * 
   * {@inheritdoc}
   */
  public function build() {
    $config = $this->getConfiguration();
    $message = '';

    for ($i = 1; $i <= $config['count']; $i++) {
      $message .= $config['message'] . '<br />';
    }

    $block = [
      '#type' => 'markup',
      '#markup' => $message,
    ];
    return $block;
  }

}

Уже при добавлении у нас появятся настройки, которые мы можем указать:

Настройки нашего блока

И на выходе мы получаем:

Результат

Вот и всё. Такой процесс работы с блоками в Drupal 8 на программном уровне.

Ссылки

Комментарии

Комментарии временно отключены

Слишком много спама. Нужно немного времени чтобы реализовать инструменты для модерации спама через Телеграм и вернуть их обратно.

andypost@drupal.org
пн, 19/10/2015 - 23:44

Рекомендую подправить:
Block Plugin API - это единственный API который есть и именно через него определяются блоки
Block Entity - это не API, а сущность, которая хранит привязку блока в регион темы
также стоит упомянуть модуль block_content (аналог bean в 7ке)

Niklan
вт, 20/10/2015 - 07:11

Спасибо, поправил. И спасибо за наводку на block_content, изучу и напишу.

Артём
пн, 18/01/2016 - 20:39

Привет.
Не попадалась ли тебе задача получить ID'шник истанса блока например на build() или в другом месте. не могу найти как это сделать. или ID инстанса или какой нибудь другой уникальный идентификатор блока.
Спасибо заранее

Инна
пт, 12/02/2016 - 13:33

можете подсказать, реализованна ли возможность добавлять класс для блока, как в drupal 7 это делал модуль block_class

Анна
ср, 30/03/2016 - 17:57

Здравствуйте!
Только начала изучать D8 и , вообще, Drupal. Начала с 8 версии.
Подскажите пожалуйста, как в вашем примере выводить не helloW, а допустим скрипт, полученный от Booking/com, чтобы его не просто вставить в готовый блок (включив phpmode), а правильно, примерно как у вас тут описано, предварительно добавив его в /js темы и прописав а тема.info
Спасибо.

Анна
сб, 02/04/2016 - 14:12

Ясно, вы сильны только в переводах, а не по сути. Простите за вторжение в ваше личное пространство...

Алексей
вт, 22/11/2016 - 17:21

Уважаемая Анна, у вас конкретная задача, никто её вам бесплатно решать не будет

Василий
сб, 13/05/2017 - 12:01

Подскажите , кеширование у блока будет включено автоматически? Если например, есть блок, выводящий слоган сайта. который может меняться со временем, то лучшей практикой будет дополнительно определять кеширование, как например, описано здесь: http://www.drupal.ru/node/127211?

Niklan
пт, 19/05/2017 - 06:56

Да, из коробки он кеширует себя. Можете задать собственные кэш теги:

public function build() {
    return array(
      '#markup' => $this->t('My custom block content'),
      '#cache' => array(
        'contexts' => array('user.roles'),
      ),
    );
  }
Niklan
пт, 05/11/2021 - 09:40

Поиск по проекту @FormElement( и @RenderElement(, либо все классы расширяющие \Drupal\Core\Render\Element\RenderElement.

Поделиться