Drupal 8 поставляется с отличным дополнением к бэкэнд-инструментарию для разработчиков в виде системы плагинов. Абсолютно новые, специфичные для Drupal и разработанные для решения лишь нескольких конкретных задач, плагины стали системой перехода к функциональности многократного использования в 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()
.
Вывод
В первой части этой серии статей мы сосредоточились на настройке нашего собственного типа плагина и подготовке его к использованию. При этом мы быстро прошли через этот процесс и увидели, как наши плагины должны работать с реальными классами форм. В следующей части мы будем работать над тем, чтобы сделать возможным их отображение вместе с узлами. Это означает добавление дополнительной конфигурации к объектам типа узла и отображение форм с использованием псевдополей, управляемых как часть обычных режимов просмотра содержимого. Будьте на связи!