12.8. Работа с формами в Drupal 8. Добавляем форму администрирования.

Drupal 8 Form API

В этом уроке мы разберемся с Drupal 8 Form API и создадим форму настроек для модуля. Мы уже создали модули для вывода страницы и блока, давайте теперь создадим конфигурационную форму, в которой мы будем хранить данные для подключению к условному сервису. Допустим, что нам нужно хранить на сайте API Key и API Client ID, например для Google Maps API.

Примеры кода можно посмотреть на github:
https://github.com/levmyshkin/drupalbook8

Мы можем хранить эти данные в settings.php и добавить эти настройки в git. Но это будет не безопасно, хранит доступы к сервисам лучше в базе данных. 

Drupal 8 Configuration form

Давайте добавим еще один роут для нашей формы:

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