Статьи

Учебник по эффективному использованию производных плагинов Drupal 8

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

Логотип Drupal 8

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

Так что насчет Drupal 8? Мы видели, что для каждого плагина определенного типа нам нужно объявить свой класс PHP. Чтобы создать новый блок, нам нужен новый класс. Чтобы создать еще один блок, нам нужен еще один класс. Так где же будет этот цикл, который мы видим в Drupal 7? Короткий ответ на это: внутри производной плагина .

В этой статье мы рассмотрим длинный ответ и узнаем, что такое производные и как мы можем их использовать. Для последнего мы создадим пример внутри demo модуля, который можно найти в этом git-репозитории и который, мы надеемся, поможет нам лучше понять, что происходит. Для немного более сложного примера система Menu великолепна, поскольку она предоставляет отдельный блок для каждого из своих меню (аналогично Drupal 7, но с использованием плагинов).

То, что мы собираемся сделать, на самом деле очень просто. Мы собираемся реализовать базовую функциональность Node Block, с помощью которой для всех узлов статьи на нашем сайте у нас будет блок. Смешной? Конечно. Должны ли мы делать это для всех узлов на нашем сайте? Точно нет! Но это очень простая реализация, предназначенная для краткости и демонстрации использования производных плагинов.

Плагин Производные

Производные плагинов — это способ, с помощью которого плагин определенного типа может быть представлен в системе как несколько его экземпляров. Другими словами, плагин может ссылаться на класс производного, который отвечает за предоставление списка определений плагинов, которые основаны на исходном плагине (начинаются с того же базового определения), но имеют немного отличающиеся данные конфигурации или определения. SystemMenuBlock мы говорили выше, является отличным примером. Это один плагин, который имеет столько же производных, сколько есть меню на сайте.

Более того, когда запрашивается список всех плагинов определенного типа, менеджер плагинов использует механизм обнаружения для загрузки всех плагинов этого типа. Если этот механизм украшен DerivativeDiscoveryDecorator , менеджер сможет также получать производные. Чтобы сделать это, производное обнаружение ищет производный класс в каждом плагине и, если он его находит, запрашивает у него этот список.

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

Производный класс

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

Внутри src/Plugin/Derivative/NodeBlock.php demo модуля у нас есть следующее:

 <?php /** * @file * Contains \Drupal\demo\Plugin\Derivative\NodeBlock. */ namespace Drupal\demo\Plugin\Derivative; use Drupal\Component\Plugin\Derivative\DeriverBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides block plugin definitions for nodes. * * @see \Drupal\demo\Plugin\Block\NodeBlock */ class NodeBlock extends DeriverBase implements ContainerDeriverInterface { /** * The node storage. * * @var \Drupal\Core\Entity\EntityStorageInterface */ protected $nodeStorage; /** * Constructs new NodeBlock. * * @param \Drupal\Core\Entity\EntityStorageInterface $node_storage * The node storage. */ public function __construct(EntityStorageInterface $node_storage) { $this->nodeStorage = $node_storage; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, $base_plugin_id) { return new static( $container->get('entity.manager')->getStorage('node') ); } /** * {@inheritdoc} */ public function getDerivativeDefinitions($base_plugin_definition) { $nodes = $this->nodeStorage->loadByProperties(['type' => 'article']); foreach ($nodes as $node) { $this->derivatives[$node->id()] = $base_plugin_definition; $this->derivatives[$node->id()]['admin_label'] = t('Node block: ') . $node->label(); } return $this->derivatives; } } 

Все, что нужно реализовать нашему классу — это DeriverInterface и реализовать два его метода. Вместо этого мы используем ContainerDeriverInterface потому что мы хотим, чтобы наш контейнер производного знал. Почему? Потому что мы используем внедрение зависимостей для загрузки менеджера сущностей Drupal, чтобы мы могли получить доступ к хранилищу Node (это то, что делают конструктор и метод create() ). Кроме того, наш производный класс происходит от класса DeriverBase поскольку он уже позаботился об одном из обязательных методов ( getDerivativeDefinition() ).

Наконец, getDerivativeDefinitions() является методом, отвечающим за предоставление массива определений плагинов, которые происходят от плагина, который использует этот класс. Он получает $base_plugin_definition в качестве аргумента (определение фактического плагина, который использует этот производный), и мы используем его для построения наших производных определений. В нашем случае мы без разбора загружаем все узлы Article и для каждого из них создаем отдельное определение, которое отличается только наличием различной admin_label (это свойство класса аннотаций Drupal\Core\Block\Annotation\Block ). Массив производных имеет идентификатор ID производной (в нашем случае ID узла, который мы будем использовать позже).

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

Блок Плагин

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

Внутри src/Plugin/Block/NodeBlock.php :

 <?php namespace Drupal\demo\Plugin\Block; use Drupal\Core\Block\BlockBase; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityViewBuilderInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; use Drupal\node\NodeInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a 'NodeBlock' block plugin. * * @Block( * id = "node_block", * admin_label = @Translation("Node block"), * deriver = "Drupal\demo\Plugin\Derivative\NodeBlock" * ) */ class NodeBlock extends BlockBase implements ContainerFactoryPluginInterface { /** * @var EntityViewBuilderInterface. */ private $viewBuilder; /** * @var NodeInterface. */ private $node; /** * Creates a NodeBlock instance. * * @param array $configuration * @param string $plugin_id * @param array $plugin_definition * @param EntityManagerInterface $entity_manager */ public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->viewBuilder = $entity_manager->getViewBuilder('node'); $this->nodeStorage = $entity_manager->getStorage('node'); $this->node = $entity_manager->getStorage('node')->load($this->getDerivativeId()); } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, $plugin_definition, $container->get('entity.manager') ); } /** * {@inheritdoc} */ public function build() { if (!$this->node instanceof NodeInterface) { return; } $build = $this->viewBuilder->view($this->node, 'full'); return $build; } /** * {@inheritdoc} */ public function blockAccess(AccountInterface $account, $return_as_object = FALSE) { return $this->node->access('view', NULL, TRUE); } } 

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

Большая часть остального — это базовое строительство блоков, с которым мы должны быть знакомы . Что интересно, мы можем использовать метод getDerivativeId() для получения идентификатора узла, который мы использовали также в качестве идентификатора отображаемой производной, и, используя его, мы загружаем объект узла и строим блок как фактический выход узла. Наконец, внутри blockAccess() мы гарантируем, что этот блок имеет те же проверки доступа, что и сам фактический узел. Таким образом, если текущий пользователь не имеет доступа для просмотра текущего узла, блок даже не будет отображаться.

Теперь, если мы очистим кеши и перейдем к интерфейсу Block Layout, мы должны увидеть некоторые блоки, называемые Node Block: [Node title] . Вы можете разместить их там, где хотите, и они отобразят соответствующий узел.

Вывод

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

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

Вопросов? Комментарии? Что-нибудь, что вы хотели бы объяснить дальше? Дайте нам знать!