Статьи

Drupal 8 пользовательских типов плагинов

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

Логотип Drupal 8

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

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

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

Мы начнем с создания нашего собственного типа плагина. Для этого у нас будет 2 интерфейса и 6 классов. Это звучит как много, но я вас уверяю, они довольно типичные и быстрые в настройке. Затем в следующем выпуске этой серии мы увидим, как использовать его для наших повторно используемых форм, прикрепленных к узлам.

Менеджер плагинов

Ответственный за обнаружение и загрузку плагинов, самая важная часть в любом типе плагинов — менеджер . Однако его очень просто создать, потому что Drupal уже предоставляет нам разумную базу по умолчанию для расширения. Таким образом, в папке нашего модуля /src мы можем иметь этот класс внутри файла ReusableFormManager.php (де-факто имя нашего типа плагина становится ReusableForm ):

 <? php namespace   Drupal \reusable_forms ; 

 use   Drupal \Core\Plugin\DefaultPluginManager ; 
 use   Drupal \Core\Cache\CacheBackendInterface ; 
 use   Drupal \Core\Extension\ModuleHandlerInterface ; 

 class   ReusableFormsManager   extends   DefaultPluginManager   { 

 public   function  __construct ( \Traversable $namespaces ,   CacheBackendInterface  $cache_backend ,   ModuleHandlerInterface  $module_handler )   { parent :: __construct ( 'Plugin/ReusableForm' ,  $namespaces ,  $module_handler ,   'Drupal\reusable_forms\ReusableFormPluginInterface' ,   'Drupal\reusable_forms\Annotation\ReusableForm' ); $this -> alterInfo ( 'reusable_forms_info' ); $this -> setCacheBackend ( $cache_backend ,   'reusable_forms' ); 
   } 
 } 

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

  • Plugin/ReusableForm — подкаталог, в котором плагины этого типа будут найдены в любом модуле
  • Drupal\reusable_forms\ReusableFormPluginInterface — интерфейс, который должен быть реализован каждым из наших плагинов
  • Drupal\reusable_forms\Annotation\ReusableForm — класс аннотаций, который будет определять свойства нашего плагина (такие как идентификатор, имя и т. Д.)

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

Интерфейс плагина

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

 <? php namespace   Drupal \reusable_forms ; 

 use   Drupal \Core\Entity\EntityInterface ; 
 use   Drupal \Core\Plugin\ContainerFactoryPluginInterface ; 
 use   Drupal \Component\Plugin\PluginInspectionInterface ; 

 interface   ReusableFormPluginInterface   extends   PluginInspectionInterface ,   ContainerFactoryPluginInterface   { 

   /** * Return the name of the reusable form plugin. * * @return string */ 
   public   function  getName (); 

   /** * Builds the associated form. * * @param $entity EntityInterface. * The entity this plugin is associated with. * * @return array(). * Render array of form that implements \Drupal\reusable_forms\Form\ReusableFormInterface */ 
   public   function  buildForm ( $entity ); 
 } 

Это очень простой интерфейс, который применяет только два метода: getName() и buildForm() . Первый вернет имя плагина, в то время как последний должен передать объект сущности и вернуть массив визуализации определения формы, который реализует \Drupal\reusable_forms\Form\ReusableFormInterface (интерфейс, который мы настроим для нашего фактического формы). Вы также заметите, что мы расширяем два других интерфейса. Они предоставляют нам некоторые дополнительные полезные методы и позволяют нам вводить зависимости из контейнера.

Плагин аннотации

Как определено в менеджере, давайте также настроим наш класс аннотаций внутри src/Annotation/ReusableForm.php :

 <? php namespace   Drupal \reusable_forms\Annotation ; 

 use   Drupal \Component\Annotation\Plugin ; 

 /** * Defines a reusable form plugin annotation object. * * @Annotation */ 
 class   ReusableForm   extends   Plugin   { 

   /** * The plugin ID. * * @var string */ 
   public  $id ; 

   /** * The name of the form plugin. * * @var \Drupal\Core\Annotation\Translation * * @ingroup plugin_translatable */ 
   public  $name ; 

   /** * The form class associated with this plugin * * It must implement \Drupal\reusable_forms\Form\ReusableFormInterface. * * @var string */ 
   public  $form ; 
 } 

Здесь мы просто расширяем класс аннотаций Plugin по умолчанию и определяем три свойства (id, name и form). Это будут три ключа, найденные в аннотации наших отдельных плагинов (мы увидим пример во второй части этой серии).

База плагинов

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

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

 <? php namespace   Drupal \reusable_forms ; 

 use   Drupal \Component\Plugin\PluginBase ; 
 use   Drupal \Core\Form\FormBuilder ; 
 use   Symfony \Component\DependencyInjection\ContainerInterface ; 

 abstract   class   ReusableFormPluginBase   extends   PluginBase   implements   ReusableFormPluginInterface   { 

   /** * The form builder. * * @var \Drupal\Core\Form\FormBuilder. */ 
   protected  $formBuilder ; 

   /** * Constructs a ReusableFormPluginBase object. * * @param array $configuration * @param string $plugin_id * @param mixed $plugin_definition * @param FormBuilder $form_builder */ 
   public   function  __construct ( array $configuration ,  $plugin_id ,  $plugin_definition ,   FormBuilder  $form_builder )   { parent :: __construct ( $configuration ,  $plugin_id ,  $plugin_definition ); $this -> formBuilder =  $form_builder ; 
   } 

   /** * {@inheritdoc} */ 
   public   static   function  create ( ContainerInterface  $container ,  array $configuration ,  $plugin_id ,  $plugin_definition )   { 
     return   new   static ( $configuration , $plugin_id , $plugin_definition , $container -> get ( 'form_builder' ) 
     ); 
   } 

   /** * {@inheritdoc} */ 
   public   function  getName ()   { 
     return  $this -> pluginDefinition [ 'name' ]; 
   } 

   /** * {@inheritdoc} */ 
   public   function  buildForm ( $entity )   { 
     return  $this -> formBuilder -> getForm ( $this -> pluginDefinition [ 'form' ],  $entity ); 
   } 
 } 

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

Дополнительную информацию о контейнере службы и внедрении зависимостей в Drupal 8 можно найти в одной из моих предыдущих статей на Sitepoint.com.

Как требует наш интерфейс, мы уже позаботились о реализации двух методов прямо здесь, в нашем базовом классе. Метод getName() просто возвращает имя плагина, как определено в аннотации плагина. Метод buildForm() , с другой стороны, будет использовать построитель форм для построения формы. Для этого он будет использовать класс, предоставленный в ключе form аннотации плагина (который должен быть полностью определенным именем класса, реализующего наш интерфейс Form, который мы еще не определили). При этом мы также передаем аргумент $entity в форму (что бы это ни было, если оно реализует EntityInterface ). Это делается для того, чтобы форма, отображаемая на странице узла, узнала об узле, с которым она отображается.

Интерфейс формы

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

 <? php namespace   Drupal \reusable_forms\Form ; 

 use   Drupal \Core\Form\FormInterface ; 

 interface   ReusableFormInterface   extends   FormInterface   {} 

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

Форма базы

Теперь, когда у нас есть интерфейс для реализации формы, давайте также создадим базовый класс форм, который могут расширять остальные формы. Внутри src/Form/ReusableFormBase.php у нас может быть следующий абстрактный класс:

 <? php namespace   Drupal \reusable_forms\Form ; 

 use   Drupal \Core\Entity\EntityInterface ; 
 use   Drupal \Core\Form\FormBase ; 
 use   Drupal \Core\Form\FormStateInterface ; 

 /** * Defines the ReusableFormBase abstract class */ 
 abstract   class   ReusableFormBase   extends   FormBase   implements   ReusableFormInterface   { 

   /** * @var EntityInterface. */ 
   protected  $entity ; 

   /** * {@inheritdoc}. */ 
   public   function  buildForm ( array $form ,   FormStateInterface  $form_state )   { $build_info =  $form_state -> getBuildInfo (); 
     if   ( $build_info [ 'args' ]   &&  $build_info [ 'args' ][ 0 ]   instanceof   EntityInterface )   { $this -> entity =  $build_info [ 'args' ][ 0 ]; 
     } $form [ 'first_name' ]   =  array ( 
       '#type'   =>   'textfield' , 
       '#title'   =>  $this -> t ( 'First name' ), 
     ); $form [ 'last_name' ]   =  array ( 
       '#type'   =>   'textfield' , 
       '#title'   =>  $this -> t ( 'Last name' ), 
     ); $form [ 'email' ]   =  array ( 
       '#type'   =>   'email' , 
       '#title'   =>  $this -> t ( 'Email' ), 
     ); $form [ 'actions' ][ '#type' ]   =   'actions' ; $form [ 'actions' ][ 'submit' ]   =  array ( 
       '#type'   =>   'submit' , 
       '#value'   =>  $this -> t ( 'Submit' ), 
       '#button_type'   =>   'primary' , 
     ); 

     return  $form ; 
   } 
 } 

Как вы можете видеть, мы реализуем интерфейс, но мы также расширяем класс Drupal FormBase по умолчанию. И в этом примере у нас есть buildForm метод buildForm который возвращает простую форму с тремя полями и кнопку отправки. Вы можете делать все, что вы хотите здесь с точки зрения того, что вы считаете хорошей базовой формой. Кроме того, хотя в начале этого метода мы проверяем, был ли EntityInterface объект EntityInterface в качестве аргумента при создании этой формы, и устанавливаем его как защищенное свойство. Классы, расширяющие это, смогут использовать эту сущность, если они сначала вызывают метод parent::buildForm() .

Вывод

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