В этом уроке мы разберемся с Drupal 8 Form API и создадим форму настроек для модуля. Мы уже создали модули для вывода страницы и блока, давайте теперь создадим конфигурационную форму, в которой мы будем хранить данные для подключению к условному сервису. Допустим, что нам нужно хранить на сайте API Key и API Client ID, например для Google Maps API.
Примеры кода можно посмотреть на github:
https://github.com/levmyshkin/drupalbook8
Мы можем хранить эти данные в settings.php и добавить эти настройки в git. Но это будет не безопасно, хранит доступы к сервисам лучше в базе данных.
Давайте добавим еще один роут для нашей формы:
modules/custom/drupalbook/drupalbook.routing.yml
drupalbook.settings: path: '/admin/structure/drupalbook/settings' defaults: _form: '\Drupal\drupalbook\Form\DrupalbookSettingsForm' _title: 'DrupalBook Settings form' requirements: _permission: 'administer site configuration'
В отличии от прежних роутов в defaults мы указываем не _controller, а _form. Дело в том, что мы будем создавать не Controller класс для формы, а класс формы. Давайте создадим файл для класса формы:
modules/custom/drupalbook/src/Form/DrupalbookSettingsForm.php
Вам нужно будет создать отдельную папку Form в папке src для ваших форм. Это позволяет разделить код модуля по отдельным папкам и вы также легко можете найти нужный вам код ориентируюсь по названием папок.
Добавьте следующий код формы и мы разберем каждый из блоков кода и как это работает:
<?php namespace Drupal\drupalbook\Form; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; /** * Configure example settings for this site. */ class DrupalbookSettingsForm extends ConfigFormBase { /** * {@inheritdoc} */ public function getFormId() { return 'drupalbook_admin_settings'; } /** * {@inheritdoc} */ protected function getEditableConfigNames() { return [ 'drupalbook.settings', ]; } /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $config = $this->config('drupalbook.settings'); $form['drupalbook_api_key'] = array( '#type' => 'textfield', '#title' => $this->t('API Key'), '#default_value' => $config->get('drupalbook_api_key'), ); $form['drupalbook_api_client_id'] = array( '#type' => 'textfield', '#title' => $this->t('API Client ID'), '#default_value' => $config->get('drupalbook_api_client_id'), ); return parent::buildForm($form, $form_state); } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { // Retrieve the configuration $this->configFactory->getEditable('drupalbook.settings') // Set the submitted configuration setting ->set('drupalbook_api_key', $form_state->getValue('drupalbook_api_key')) // You can set multiple configurations at once by making // multiple calls to set() ->set('drupalbook_api_client_id', $form_state->getValue('drupalbook_api_client_id')) ->save(); parent::submitForm($form, $form_state); } }
Мы уже разбирались с namespace и use операторами и что друпал использует их для подключения автоматически только тех классов, которые нужны:
namespace Drupal\drupalbook\Form; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface;
Чтобы создать конфигурационную форму нужно наследоваться от класса ConfigFormBase, ConfigFormBase подразумевает, что вы будете сохранять данные с формы в конфигурацию.
class DrupalbookSettingsForm extends ConfigFormBase {
Дальше указываем Form ID, он должен быть уникальным для каждой формы. Если вы будете писать id вашей формы начиная с названия модуля, то наверняка ваш id будет уникальным:
public function getFormId() { return 'drupalbook_admin_settings'; }
Указываем группу конфигов в которой мы будем хранить данные:
protected function getEditableConfigNames() { return [ 'drupalbook.settings', ]; }
Теперь давайте разберемся как мы создаем сами поля форм. Вы можете оценить возможности Form API и какие поля вы сможете вывести, прочитав документацию:
https://api.drupal.org/api/drupal/elements/8.5.x
Мы использовали пока что только textfield:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Textfield.php/class/Textfield/8.5.x
Но вам часто придется сталкиватся с выпадающими списками:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Select.php/class/Select/8.5.x
Чекбоксами и радио баттонами:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Checkboxes.php/class/Checkboxes/8.5.x
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21Radios.php/class/Radios/8.5.x
Попробуйте добавить свои поля в форму, не ограничивайтесь только текстовыми полями.
public function buildForm(array $form, FormStateInterface $form_state) { $config = $this->config('drupalbook.settings');
Мы формируем массив формы в $form и потом строим из него HTML. Переменная $form_state хранит в себе все данные с формы, которые мы отправляем, здесь и values всех полей, id формы, CSRF-токен для защиты от автоматической отправки формы. $form_state также позволяет передавать данные между шагами мультистеп формы, мы будем это использовать в одном из следующих уроках. Также каждый раз когда форма отправляется через AJAX, форма перестраивается заново, а $form_state позволяет построить копию формы, которая была перед нажатием на кнопку submit. И если форма не будет отправлена из-за ошибки, например какое-то поле не будет заполнено, то все поля будет с сохраненями значениями, которые хранились в $form_state. Поэтому $form и $form_state всегда идут вместе.
Здесь также мы подгружаем данные из конфигов. Возможно вы уже что-то сохранили в drupalbook.settings и $config уже не пустой, это позволит выставить текущие значения в поля с помощью #default_value в каждом из текстовых полей, получая данные из конфигов с помощью метода get().
$form['drupalbook_api_key'] = array( '#type' => 'textfield', '#title' => $this->t('API Key'), '#default_value' => $config->get('drupalbook_api_key'), ); $form['drupalbook_api_client_id'] = array( '#type' => 'textfield', '#title' => $this->t('API Client ID'), '#default_value' => $config->get('drupalbook_api_client_id'), );
И в конце метода мы возвращаем $form и $form_state, чтобы форма была построена.
return parent::buildForm($form, $form_state);
Дальше у нас идет метод submitForm(), он срабатывает если форма была отправлена и никаких ошибок не возникло. Если все-таки у вас было не заполненное обязательное поле и друпал выдает ошибку, то submitForm не будет срабатывать. Если вы хотите проверить значения отправленых данных с формы, то вам нужно использовать validateForm(), validate сработает даже если в форме будет ошибка и с помощью validate можно отменить отправку формы и вызвать ошибку, если что-то вас не устраивает в данных. Мы рассмотрим validate в одном из следующих уроков по формам.
В методе submitForm() мы проходим по всем полям, собираем их значения и обновляем конфигурацию drupalbook.settings:
public function submitForm(array &$form, FormStateInterface $form_state) { // Retrieve the configuration $this->configFactory->getEditable('drupalbook.settings') // Set the submitted configuration setting ->set('drupalbook_api_key', $form_state->getValue('drupalbook_api_key')) // You can set multiple configurations at once by making // multiple calls to set() ->set('drupalbook_api_client_id', $form_state->getValue('drupalbook_api_client_id')) ->save(); parent::submitForm($form, $form_state); }
Мы также вызывает родительский метод submitForm в котором выводится сообщение об удачном отправление формы. Вы можете закомментрировать эту строку и написать свое сообщение:
//parent::submitForm($form, $form_state); drupal_set_message($this->t('My Cool Form have been saved!'));
Не забудьте почистить кеш, чтобы ваш роут применился. Теперь вы можете попробовать вашу форму в действие. Когда вам нужно будет подгрузить API Key, вы можете использовать этот код:
<?php $config = \Drupal::config('example.settings'); $api_key =$config->get('drupalbook_api_key'); $api_client_id = $config->get('drupalbook_api_client_id');
Этот код будет работать в любом модуле или preprocess функции, потому что в друпал единая система конфигурации.
На этом все в следующем уроке по формам, мы разберем как делать мультистеп формы.
Примеры кода можно посмотреть на github:
https://github.com/levmyshkin/drupalbook8