xandeadx.ru Блог музицирующего веб-девелопера

Drupal → Drupal 8 Dev FAQ

Обновляемый список простых вопросов и ответов по Drupal 8.

Все консольные команды рассчитаны на выполнение из корня друпала. Windows пользователям перед выполнением команд в cmd.exe нужно заменить разделитель директорий с / на \, т.е. вместо например vendor/bin/drush писать vendor\bin\drush.

Содержание

  1. Общие вопросы
  2. Работа с сущностями и полями (Entity API, Field API)
  3. Темизация (Theming API, Render API, Twig)
  4. Работа с формами (Form API, Ajax API)
  5. Работа с базой данных (Database API, Entity Query API)
  6. Работа с меню и адресами, навигация, роутинг (Menu API, Url, Routing API)

Общие вопросы

Как установить Drush?
$ composer require drush/drush

Подробнее.
Как обновить ядро друпала?
Если друпал установлен с помощью composer и стоит пакет webflo/drupal-core-strict:
$ composer update drupal/core webflo/drupal-core-strict --with-all-dependencies
$ vendor/bin/drush updb

Если пакета webflo/drupal-core-strict нет, то просто:
$ composer update drupal/core --with-all-dependencies
$ vendor/bin/drush updb

Подробнее.

Перед обновлением обязательно делаем бэкап файлов и базы!

Как узнать, какие composer пакеты зависят от определённого пакета?
Команда выдаст список пакетов, которые зависят от symfony/process
$ composer why symfony/process
Как установить модуль?
# Последняя стабильная версия
$ composer require drupal/devel
 
# Dev версия
$ composer require drupal/devel:1.x-dev

Подробнее.
Как создать модуль?
$ vendor/bin/drush generate module

Или вручную
Как создать html ссылку? Какой аналог l()?
Как программно создать страницу?
src/Controller/ModulenameController.php:
<?php
namespace Drupal\modulename\Controller;
 
use Drupal\Core\Controller\ControllerBase;
 
class ModulenameController extends ControllerBase {
  public function helloWorld() {
    return [
      '#markup' => 'Hello, World!',
    ];
  }
}

modulename.routing.yml:
modulename.hello_world:
  path: '/hello/world'
  defaults:
    _controller: '\Drupal\modulename\Controller\ModulenameController::helloWorld'
    _title: 'Hello, World!'
  requirements:
    _permission: 'access content'

Подробнее.

Или с помощью drush:

$ vendor/bin/drush generate controller

Как программно создать блок?
src/Plugin/Block/HelloWorldBlock.php:
<?php
namespace Drupal\modulename\Plugin\Block;
 
use Drupal\Core\Block\BlockBase;
 
/**
 * @Block(
 *   id = "hello_world_block",
 *   admin_label = @Translation("Hello world block"),
 * )
 */
class HelloWorldBlock extends BlockBase {
  public function build() {
    return [
      '#title' => 'It\'s block title', // Optional
      '#markup' => 'Hello, World!',
    ];
  }
}

Подробнее.

Или с помощью drush:

$ vendor/bin/drush generate block

Как запретить кэширование своего блока?
Способ 1:
use Drupal\Core\Cache\UncacheableDependencyTrait;
 
/**
 * @Block(...)
 */
class MyBlock extends BlockBase {
 
  use UncacheableDependencyTrait;
 
  public function build() {
    ...
  }
 
}

Способ 2:
/**
 * @Block(...)
 */
class MyBlock extends BlockBase {
 
  public function build() {
    ...
  }
 
  public function getCacheMaxAge() {
    return 0;
  }
 
}
Как очистить кэш?

На странице admin/config/development/performance нажать кнопку "Clear all caches".

Или выполнить в консоли:

$ vendor/bin/drush cache-rebuild

Или выполнить php функцию:

drupal_flush_all_caches();

Как правильно форматировать php код?
// Для отступов используется два пробела
function example() {
  $foo = 'bar';
}
 
// Между бинарными операторами ставится пробел
$a = 1 + 2; // Good
$a=1+2; // Bad
 
// Блок elseif/else начинается с новой строки
if (...) {
  ...
}
elseif (...) {
  ...
}
else {
  ...
}
 
// Массивы создаются с помощью короткого синтаксиса
$array = ['foo', 'bar']; // Good
$array = array('foo', 'bar'); // Bad
 
// Строки заключаются в одиночные кавычки
$string = 'foo'; // Good
$string = "foo"; // Bad
 
// Переменные именуются с помощью нижнего подчёркивания
$my_variable = 'foo'; // Good
$myVariable = 'foo'; // Bad
 
// Названия классов именуются в CamelCase
class MyFirstClass { }
 
// Переменные класса именуются в camelCase
class MyFirstClass {
  public $myFirstVariable;
}

Подробнее.
Как замерить время выполнения участка кода? Какой аналог timer_start() и timer_read()?
use Drupal\Component\Utility\Timer;
 
Timer::start('test');
sleep(1);
debug(Timer::read('test') . ' ms');
Как в своём модуле подключить определённую library на все страницы сайта?
modulename.module:
/**
 * Implements hook_page_attachments().
 */
function modulename_page_attachments(array &$page) {
  $page['#attached']['library'][] = 'modulename/libraryname';
}

Подробнее.
Как получить текущего пользователя? Какой аналог $GLOBALS['user']?
/** @var \Drupal\Core\Session\AccountProxyInterface $current_user */
$current_user = \Drupal::currentUser();

Функция \Drupal::currentUser() возвращает прокси-объект AccountProxy, которого для большинства случаев будет достаточно, но если нужна именно сущность пользователя, то:
$current_user_uid = \Drupal::currentUser()->id();
/** @var \Drupal\user\Entity\User $current_user */
$current_user = \Drupal\user\Entity\User::load($current_user_uid);
Как проверить права текущего пользователя? Какой аналог user_access()?
if (\Drupal::currentUser()->hasPermission('administer site configuration')) {
  ...
}
Как получить данные из $_GET, $_POST и $_COOKIE?
// $_GET
$nid = \Drupal::request()->query->get('nid');
$all_get_params = \Drupal::request()->query->all();
 
// $_POST
$nid = \Drupal::request()->request->get('nid');
$all_post_params = \Drupal::request()->request->all();
 
// $_COOKIE
$nid = \Drupal::request()->cookies->get('nid');
$all_cookie_params = \Drupal::request()->cookies->all();
Как экранировать html код? Что использовать вместо check_plain()?
$string = \Drupal\Component\Utility\Html::escape('<b>Hello</b>'); // Переменная будет содержать "&lt;b&gt;Hello&lt;/b&gt;"

Подробнее.
Что использовать вместо format_string()?
$string = new \Drupal\Component\Render\FormattableMarkup('My name is: @name', [
  '@name' => $name,
]);
@variable — текст будет пропущен через Html::escape().
%variable — текст будет пропущен через Html::escape() и обёрнут в <em></em>.
:variable — текст будет пропущен через Html::escape() и UrlHelper::stripDangerousProtocols().

Подробнее.

Чтобы запретить обрабатывать текст с помощью Html::escape() нужно передать в плэйсхолдер объект MarkupInterface:

$string = new \Drupal\Component\Render\FormattableMarkup('My name is: @name', [
  '@name' => \Drupal\Core\Render\Markup::create('<b>Dries</b>'),
]);

Как склонять строки с числом? Какой аналог format_plural()?
$string = \Drupal::translation()->formatPlural(123, '@count day', '@count days');

Подробнее.
Как обрезать текст?
$truncated_text = \Drupal\Component\Utility\Unicode::truncate($text, 128);
Как сериализовать массив в JSON? Какой аналог drupal_json_encode()?
use Drupal\Component\Serialization\Json;
$string = Json::encode($array);
$array = Json::decode($string);
Как узнать дефолтный язык?
$default_language = \Drupal::languageManager()->getDefaultLanguage();
$default_langcode = $default_language->getId();
Как форматировать unix timestamp в дату? Какой аналог format_date()?
/** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
$date_formatter = \Drupal::service('date.formatter');
$formatted_date = $date_formatter->format(1558730206, 'short');
Как импортировать несколько конфигов из определённой папки?
$ vendor/bin/drush config-import --partial --source=modules/modulename/config/install
Как отправить e-mail/письмо?
Практически так-же, как в Drupal 6 и 7. В нужный момент вызываем:
\Drupal::service('plugin.manager.mail')->mail(
  'modulename',
  'example_mail_key',
  'to@gmail.com',
  'en',
  ['myvar' => 123]
]);

Плюс реализуем хук hook_mail():
function modulename_mail($key, &$message, $params) {
  if ($key == 'example_mail_key') {
    $message['subject'] = 'Example email subject';
    $message['body'][] = 'Example email body. myvar = ' . $params['myvar'];
  }
}

Можно обойтись без реализации хука, если указать модуль system и специальным образом сформировать массив $params:
\Drupal::service('plugin.manager.mail')->mail('system', 'example_mail_key', 'example@gmail.com', 'en', [
  'context' => [
    'subject' => 'Subject',
    'message' => \Drupal\Core\Render\Markup::create('Message'),
  ],
]);
Как получить текущее время в timestamp?
$current_timestamp = \Drupal::time()->getCurrentTime();
Как транслитерировать строку?
$transliterated_string = \Drupal::transliteration()->transliterate('Привет Мир', 'ru');
// --> Privet Mir
Как отдать пользователю текст в виде файла?
class ExampleController extends ControllerBase {
 
  public function export() {
    $response = new Response();
    $response->headers->set('Content-Type', 'text/csv; charset=utf-8');
    $response->headers->set('Content-Disposition', 'attachment; filename="example.txt"');
    $response->setContent('Hello World!');
 
    return $response;
  }
 
}
Как вывести страницу 404 (страница не найдена) или 403 (доступ запрещён)?
// 404
throw new NotFoundHttpException();
// 403
throw new AccessDeniedHttpException();
Как добавить свой токен для нод?
modulename.tokens.inc
/**
 * Implements hook_token_info().
 */
function modulename_token_info() {
  $token_info['tokens']['node']['example-token'] = [
    'name' => 'Example node token',
  ];
 
  return $token_info;
}
 
/**
 * Implements hook_tokens().
 */
function modulename_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
  $replacements = [];
 
  if ($type == 'node' && !empty($data['node'])) {
    $node = $data['node']; /** @var NodeInterface $node */
 
    foreach ($tokens as $name => $original) {
      if ($name == 'example-token') {
        $replacements[$original] = 'It\'s example token for node ' . $node->id();
      }
    }
  }
 
  return $replacements;
}

Подробнее.
Как заменить токены в тексте на их значения?
$string = \Drupal::token()->replace('Example string with [node:title] token', [
  'node' => Node::load(123),
]);
Как из своего контроллера добавить на страницу мета-тег?
class ExampleController extends ControllerBase {
 
  public function exampleAction() {
    $meta_description = [
      '#tag' => 'meta',
      '#attributes' => [
        'name' => 'description',
        'content' => ['#plain_text' => 'Random text'],
      ],
    ];
 
    return [
      '#attached' => [
        'html_head' => [
          [$meta_description, 'description']
        ],
      ],
      ...
    ];
  }
 
}
Как создать свой permission? Какой аналог hook_permission()?
modulename.permissions.yml
administer modulename:
  title: 'Administer Modulename'

Подробнее.
Как получить число комментариев у сущности?
$comment_count = (int)$entity->get('field_comment')->comment_count;

Работа с сущностями и полями (Entity API, Field API)

Как получить объект сущности по его id?
// С помощью статического метода load()
$node = \Drupal\node\Entity\Node::load(123);
$term = \Drupal\taxonomy\Entity\Term::load(234);
 
// С помощью сервиса entity_type.manager
$node = \Drupal::entityTypeManager()->getStorage('node')->load(123);
$term = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->load(234);
Как получить информацию о поле? Какой аналог field_info_field()?
Универсальный способ получить информацию о любом поле, хоть базовом, хоть настраиваемом:
$category_field = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', 'page')['field_category'];
$category_field_storage = $field_definition->getFieldStorageDefinition();

Для настраиваемых полей можно так же пользоваться методом \Drupal\field\Entity\FieldConfig::loadByName() и \Drupal\field\Entity\FieldStorageConfig::loadByName().
Как проверить, есть ли у сущности или бандла определённое поле?
Если есть доступ к объекту сущности, то:
if ($entity->hasField('field_example')) {
  ...
}

Важный момент — hasField() проверяет наличие поля у бандла сущности, а не наличие значения в этом поле.

Если доступа к объекту сущности нет, то:

$entity_type = 'node';
$entity_bundle = 'page';
$field_name = 'field_example';
 
$field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
if ($field_storage && in_array($entity_bundle, $field_storage->getBundles())) {
  ...
}

Как получить название/label поля сущности?
// Если есть доступ к объекту сущности
$field_example_label = $entity->get('field_example')->getFieldDefinition()->getLabel();
// Иначе
$field_example_label = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', 'page')['field_example']->getLabel();
Как вычислить число дней между датами в поле типа Date range?
$date_start = $node->field_daterange->start_date;
$date_end = $node->field_daterange->end_date;
$days_between_dates = $date_end->diff($date_start)->format('%a');
Как сделать необязательным второе значение в поле типа Date range?
До того, как не закрыта соответствующая issue, можно поставить модуль Optional End Date.
Как проверить тип поля на базовое/настраиваемое?
if ($node->get('title')->getFieldDefinition() instanceof BaseFieldDefinition) {
  // Поле title является базовым
}
if ($node->get('field_example')->getFieldDefinition() instanceof FieldConfigInterface) {
  // Поле field_example является настраиваемым
}
Как получить название значения у поля типa List?
$field_example_items = $entity->get('field_example');
$allowed_values = options_allowed_values($field_example_items->getFieldDefinition()->getFieldStorageDefinition(), $entity);
$value_label = $allowed_values[$field_example_items->value];
Как получить объект бандла сущности, например NodeType у ноды?
// Способ 1
$node_type = $node->get('type')->entity;
 
// Способ 2
$node_type = NodeType::load($node->bundle());
Как получить адрес/url/uri файла у сущности типа File?
$file = \Drupal\file\Entity\File::load(123);
$file_url = $file->url();
$file_uri = $file->getFileUri();
Как получить адрес/url/uri файла из поля сущности?
$file = $entity->get('field_example_file')->entity;
$file_url = $file->url();
$file_uri = $file->getFileUri();
Как получить объект сущности файла по его uri?
$file_uri = 'public://example.jpg';
if ($files = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['uri' => $file_uri])) {
  $file = current($files); /** @var FileInterface $file */
}
Как удалить сразу несколько материалов или сущностей?
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$nodes = $node_storage->loadMultiple([1, 2, 4]);
$node_storage->delete($nodes);
Как получить информацию обо всех полях определённого типа?
/** @var EntityFieldManagerInterface $entity_field_manger */
$entity_field_manger = \Drupal::service('entity_field.manager');
$entity_reference_fields = $entity_field_manger->getFieldMapByFieldType('entity_reference');
Как получить информацию обо всех полях у определённого бандла сущности?
/** @var EntityFieldManager $entity_field_manager */
$entity_field_manager = \Drupal::service('entity_field.manager');
$article_fields = $entity_field_manager->getFieldDefinitions('node', 'article');
Как получить ноду по её синониму url?
$path = \Drupal::service('path.alias_manager')->getPathByAlias('/example-url-alias');
if (preg_match('/node\/(\d+)/', $path, $matches)) {
  $node = \Drupal\node\Entity\Node::load($matches[1]);
}
Как в hook_entity_update() узнать, что значение поля изменилось?
function hook_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
  $original_entity = $entity->original;
  $field_example = $entity->get('field_example');
  if ($field_example->hasAffectingChanges($original_entity->get('field_example'), $field_example->getLangcode())) {
    // field_example changed
  }
}
Как получить дочерние термины у определённого термина?
/** @var TermStorageInterface $term_storage */
$term_storage = \Drupal::service('entity_type.manager')->getStorage('taxonomy_term');
 
// Способ 1 - по id термина. Получает только непосредственных детей термина 123.
// Результат не отсортирован.
$childrens = $term_storage->loadChildren(123, 'category');
 
// Способ 2 - по объекту термина. Получает только непосредственных детей термина $term.
// Результат не отсортирован.
$childrens = $term_storage->getChildren($term);
 
// Способ 3 - по id термина. Получает всех детей термина 123 независимо от вложенности.
// Результат отсортирован по весу термина.
$childrens = $term_storage->loadTree('category', 123, NULL, TRUE);

Темизация (Theming API, Render API, Twig)

Как переопределить шаблон в своей теме?
Скопировать нужный файл *.html.twig в папку templates своей темы, очистить кэш. Подробнее.
Как переопределить шаблон в своём модуле?

1. Найти hook_theme, в котором объявлен шаблон.
2. Скопировать информацию о шаблоне в hook_theme своего модуля.
3. Скопировать twig шаблон в свой модуль.
4. Сбросить кэш.

Должно получиться как-то так:

modules/custom/modulename/modulename.module

/**
 * Implements hook_theme().
 */
function modulename_theme() {
  return [
    'node' => [
      'render element' => 'elements',
    ],
  ];
}

modules/custom/modulename/templates/node.html.twig
{{ content }}

Как переопределить шаблон определённого блока?
У каждого блока есть машинное имя, которое прописывается в настройках блока при его добавлении в регион. Чтобы переопределить шаблон определённого блока, нужно скопировать файл core/modules/block/templates/block.html.twig в папку templates вашей темы, переименовать этот файл по шаблону block--machine-name.html.twig (где machine-name машинное имя нужного блока, нижние подчёркивания заменяются на тире) и сбросить кэш. Подробнее, раздел "Blocks".
Как переопределить шаблон определённого поля?
Скопировать файл core/modules/system/templates/field.html.twig в папку templates своей темы, переименовать файл по шаблону field--field-name.html.twig, где field-name это машинное имя поля, сбросить кэш. Подробнее, раздел "Fields".
Как в шаблоне проверить, есть ли у поля сущности значение?
{% if not node.field_example.isEmpty() %}
   field_example is not empty
{% endif %}
Как в шаблоне узнать, сколько у поля сущности значений?
{% if node.field_example|length > 1 %}
   Field items count more 1
{% endif %}
Как в шаблоне ноды/сущности вывести отдельные поля?
Отформатированное значение поля, прошедшее через field.html.twig:
{{ content.field_example }}

Отформатированное значение поля, но без использования field.html.twig:
{{ content.field_example.0 }}

Сырое значение поля:
{{ node.field_example.value }}
 
{# Или так, если в значении поля есть html код (небезопасно!) #}
{{ node.field_example.value|raw }}

Подробнее.
Как из javascript получить папку текущей темы?
THEMENAME.theme:
/**
 * Preprocess function for html.html.twig.
 */
function THEMENAME_preprocess_html(&$vars) {
  $vars['#attached']['drupalSettings']['path']['currentThemePath'] = $vars['directory'];
}

THEMENAME.libraries.yml:
my-library-name:
  js: ...
  dependencies:
    - core/drupalSettings

После этого путь к папке темы будет находиться в js переменной drupalSettings.path.currentThemePath

Как получить информацию о текущей теме?
$current_theme = \Drupal::service('theme.manager')->getActiveTheme(); /** @var ActiveTheme $current_theme */
// Машинное имя темы
$current_theme_machine_name = $current_theme->getName();
// Информация из файла themename.info.yml
$current_theme_info = $current_theme->getExtension()->info;
Как получить название дефолтной темы?
$default_theme_name = \Drupal::config('system.theme')->get('default');
Как в своей теме подключить определённую library на все страницы сайта?
themename.info.yml:
libraries:
  - themename/libraryname

Подробнее.

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

Как подключить js файл в head?
*.libraries.yml:
my-library:
  js:
    js/my-library.js: {}
  header: true # <--
Как в php получить отрендеренное поле сущности? Какой аналог field_view_field()?
$node = \Drupal\node\Entity\Node::load(123);
 
// Render-array поля, прошедшего через field.html.twig
$field_example_build = $node->field_example->view('full');
// Render-array первого значения поля, прошедшего через форматтер
$field_example_build = $node->field_example[0]->view('full');
Как в php получить отрендеренный материал? Какой аналог node_view()?
$node = \Drupal\node\Entity\Node::load(123);
$node_view_builder = \Drupal::entityTypeManager()->getViewBuilder('node');
$node_build = $node_view_builder->view($node, 'teaser');
Как создать свой шаблон?
MODULENAME.module или THEMENAME.theme:
/**
 * Implements hook_theme().
 */
function MODULENAME_theme() {
  return [
    'my_template' => [
      'variables' => [
        'my_variable' => NULL,
      ],
    ],
  ];
}

my-template.html.twig:
<div class="my-template">{{ my_variable }}</div>

Подробнее.
Как передать в twig шаблон переменную с html разметкой?
$variables['my_html_var'] = \Drupal\Core\Render\Markup::create('<b>Hello</b> <i>World</i>');
Как в twig шаблоне проверить, что массив не пустой?
{% if my_array is not empty %}
  ...
{% endif %}

или
{% if my_array|length > 0 %}
  ...
{% endif %}

Подробнее про empty и length.
Как в twig шаблоне проверить, что в массиве есть элемент с определённым ключом?
{% if (example_array.foo is defined) %}
  {{ example_array.foo }}
{% endif %}
 
{% if (example_array.foo is not defined) %}
  Empty message...
{% endif %}
Как темизировать форму?

Пример темизации формы с идентификатором example_form:

themename.theme

/**
 * Implements hook_theme().
 */
function themename_theme() {
  return [
    'example_form' => [
      'render element' => 'form',
    ],
  ];
}

templates/example-form.html.twig
<form{{ attributes }}>
  <header>
    {{ form.element1 }}
    {{ form.element2 }}
  </header>
  {{ form.without('element1', 'element2') }}
</form>

Подробнее.

Как отрендерить render-array?
В большинстве случаев этого делать не надо, потому что twig самостоятельно рендерит рендер-массивы, но если всё же понадобилось, то всё просто:
$render_array = [
  '#theme' => 'status_messages',
  '#message_list' => [
    'warning' => ['Warning!'],
  ],
];
$output = render($render_array);
Как инвалидировать рендер-кэш с определённым тэгом?
\Drupal::service('cache_tags.invalidator')->invalidateTags(['node:123']);

Подробнее.
Как вывести html таблицу?
$build = [
  '#theme' => 'table',
  '#header' => ['ID', 'Title', 'Date'],
  '#rows' => [
    [1, 'Title 1', '01.01.2019'],
    [2, 'Title 2', '02.01.2019'],
    [3, 'Title 3', '03.01.2019'],
  ],
  '#empty' => 'Empty...',
];
Как в twig воспользоваться функцией Element::children()?
{% for key, element in elements if key|first != '#' %}
  {{ element }}
{% endfor %}
Как создать псевдо-поле/extra-field?
Пример вывода автора материала для нод типа article:
/**
 * Implements hook_entity_extra_field_info().
 */
function MODULENAME_entity_extra_field_info() {
  $extra_fields['node']['article']['display']['author'] = [
    'label' => t('Author name'),
    'weight' => 0,
  ];
 
  return $extra_fields;
}
 
/**
 * Implements hook_ENTITY_TYPE_view(): node.
 */
function MODULENAME_node_view(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, $view_mode) {
  if ($display->getComponent('author')) {
    $build['author'] = ['#markup' => $node->getOwner()->getDisplayName()];
  }
}

Подробнее.
Как в field.html.twig получить доступ к объекту сущности?
Объект сущности лежит в переменной element['#object']. Пример вывода заголовка сущности:
{{ element['#object'].title.value }}
Как twig шаблоне склонять слова с числами?
{% trans %}
  {{ count }} review
{% plural count %}
  {{ count }} reviews
{% endtrans %}
Как в пунктах меню разрешить использовать html?
function THEMENAME_preprocess_menu(&$vars) {
  THEMENAME_preprocess_menu_items($vars['items']);
}
 
function THEMENAME_preprocess_menu_items(&$items) {
  foreach ($items as &$item) {
    $item['title'] = Markup::create($item['title']);
 
    if ($item['below']) {
      THEMENAME_preprocess_menu_items($item['below']);
    }
  }
}

Работа с формами (Form API, Ajax API)

Как изменить определённую форму?
Пример изменения формы регистрации с идентификатором user_register_form:
// modulename.module
 
use Drupal\Core\Form\FormStateInterface;
 
/**
 * Implements hook_form_FORM_ID_alter(): user_register_form.
 */
function modulename_form_user_register_form_alter(&$form, FormStateInterface $form_state) {
  $form['terms_of_use'] = [
    '#type' => 'checkbox',
    '#title' => t('I agree with terms and conditions.'),
    '#required' => TRUE,
  ];
}

Подробнее.
Как изменить раскрытую форму Views?
/**
 * Implements hook_form_FORM_ID_alter(): views_exposed_form.
 */
function modulename_form_views_exposed_form_alter(&$form, FormStateInterface $form_state) {
  $view = $form_state->get('view'); /** @var ViewExecutable $view */
  if ($view->id() == 'example_views' && $view->current_display == 'example_display') {
    ...
  }
}
Как создать свою форму?
src/Form/ExampleForm.php:
<?php
namespace Drupal\modulename\Form;
 
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
 
class ExampleForm extends FormBase {
 
  public function getFormId() {
    return 'example_form';
  }
 
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['example_text'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Text'),
    ];
    $form['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    ];
    return $form;
  }
 
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $text = $form_state->getValue('example_text');
  }
 
}

Или:
$ vendor/bin/drush generate form-simple

Подробнее.
Как создать GET форму?
public function buildForm(array $form, FormStateInterface $form_state) {
  ...
  $form['#method'] = 'get';
  $form['#action'] = \Drupal::urlGenerator()->generateFromRoute('example.route');
  ...
}
Как отключить кэширование GET формы?
public function buildForm(array $form, FormStateInterface $form_state) {
  ...
  $form['#cache']['max-age'] = 0;
  ...
}
Как очистить GET форму от системных параметров op, form_build_id и form_id?
public function buildForm(array $form, FormStateInterface $form_state) {
  ...
  $form['#pre_render'][] = [$this, 'preRender'];
 
  return $form;
}
 
public function preRender($form) {
  unset($form['form_id']);
  unset($form['form_build_id']);
  unset($form['form_token']);
  unset($form['submit']['#name']);
 
  return $form;
}

Подробнее.
Как разрешить кэшировать блок с POST формой?
Для авторизованных пользователей ко всем формам добавляется скрытый элемент form_token, который защищает формы от CSRF и попутно запрещает кэширование. Если форма не делает каких-то важных изменений, то можно удалить этот элемент, тем самым разрешить кэширование родительского элемента, например блока:
public function buildForm(array $form, FormStateInterface $form_state) {
  ...
  $form['#token'] = FALSE;
 
  return $form;
}
Как вывести форму на свой странице?
modulename.routing.yml
modulename.example_form:
  path: '/example/form'
  defaults:
    _form: 'Drupal\modulename\Form\ExampleForm'
    _title: 'Example form'
  requirements:
    _permission: 'access content'

Т.е. это обычный роут, только вместо routename.defaults._controller указывается routename.defaults._form.
Как при выводе формы на странице передать в buildForm() объект ноды?
modulename.routing.yml
modulename.example_form:
  path: '/node/{node}/example-form'
  defaults:
    _form: 'Drupal\modulename\Form\ExampleForm'
    _title: 'Example form'
  requirements:
    _permission: 'access content'

src/Form/ExampleForm.php
class ExampleForm extends FormBase {
  ...
  public function buildForm(array $form, FormStateInterface $form_state, NodeInterface $node = NULL) {
    ...
  }
}

Важное замечание — название параметра в buildForm ($node) должно быть таким же, как в роуте ({node}), иначе в него ничего не передастся.
Как вывести форму в своём блоке?
/**
 * @Block(
 *   id = "my_block_with_form",
 *   admin_label = @Translation("My block with form"),
 *   category = @Translation("Forms")
 * )
 */
class MyBlockWithForm extends BlockBase {
  public function build() {
    return [
      'form' => \Drupal::formBuilder()->getForm('Drupal\modulename\Form\ExampleForm')
    ];
  }
}
Как обойти элементы формы? Какой аналог element_children()?
foreach (\Drupal\Core\Render\Element::children($form) as $key) {
  debug($form[$key]);
}

Подробнее.
Как в submitForm() получить параметры, переданные в buildForm()?
class ExampleForm extends FormBase {
 
  public function buildForm(array $form, FormStateInterface $form_state, NodeInterface $node = NULL) {
    ...
  }
 
  public function submitForm(array &$form, FormStateInterface $form_state) {
    /** @var NodeInterface $node */
    $node = $form_state->getBuildInfo()['args'][0];
  }
 
}
Как в buildForm() получить текущее значение элемента?
public function buildForm(array $form, FormStateInterface $form_state) {
  $form['my_element'] = [
    '#type' => 'number',
    '#default_value' => 1,
  ];
 
  $my_element_current_value = $form_state->getValue('my_element', $form['my_element']['#default_value']);
}
Как в submitForm() сохранить данные, чтобы они были доступны при альтере формы?
Сохранение:
public function submitForm(array &$form, FormStateInterface $form_state) {
  ...
  $form_state->set('example_data', 123);
}

Доступ:
$data = $form_state->get('example_data');
Как показывать элемент только когда другой элемент имеет определённое значение? #states
В коде создаётся два селекта и второй селект будет показываться только когда в первом выбрано значение "Show"
$form['example_select'] = [
  '#type' => 'select',
  '#options' => [
    1 => 'Show',
    2 => 'Hide',
  ],
];
 
$form['dependent_select'] = [
  '#type' => 'select',
  '#options' => [...],
  '#states' => [
    'visible' => [
      ':input[name="example_select"]' => ['value' => 1],
    ],
  ],
];

Подробнее.
Как в #states задать условие "показывать если значение НЕ xxx"?
$form['second_element'] = [
  ...
  '#states' => [
    'invisible' => [
      ':input[name="first_element"]' => [
        ['value' => 'xxx'],
      ],
    ],
  ],
];
Как отключить html валидацию формы?
$form['#attributes']['novalidate'] = 'novalidate';
Как показать ошибки валидации в диалоговом окне?
class ExampleForm extends FormBase {
 
  /**
   * {@inheritDoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    ...
 
    $form['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
      '#ajax' => [
        'callback' => '::ajaxSubmit',
      ],
    ];
 
    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
 
    return $form;
  }
 
  /**
   * Ajax submit callback.
   */
  public function ajaxSubmit(array $form, FormStateInterface $form_state) {
    $response = new AjaxResponse();
 
    if ($form_state->hasAnyErrors()) {
      $response->addCommand(new OpenModalDialogCommand($this->t('Error'), ['#type' => 'status_messages']));
    }
    else {
      ...
    }
 
    return $response;
  }
 
}
Как разрешить отправлять форму не чаще одного раза в час?
public function validateForm(array &$form, FormStateInterface $form_state) {
  if (!\Drupal::flood()->isAllowed('example_form', 1)) {
    $form_state->setErrorByName('', $this->t('You cannot send more.'));
  }
}
 
public function submitForm(array &$form, FormStateInterface $form_state) {
  ...
  \Drupal::flood()->register('example_form');
}
Как из $form_state получить значение элемента при #tree=>true?
public function buildForm(array $form, FormStateInterface $form_state) {
  $form['foo'] = [
    '#type' => 'textfield',
  ];
  $form['bar'] = [
    '#tree' => TRUE,
  ];
  $form['bar']['baz'] = [
    '#type' => 'textfield',
  ];
}
 
public function submitForm(array &$form, FormStateInterface $form_state) {
  $foo_value = $form_state->getValue('foo');
  $baz_value = $form_state->getValue(['bar', 'baz']);
}
Как создать форму в виде таблицы (табличную форму)?
$form['table'] = [
  '#type' => 'table',
  '#header' => ['Key', 'Value'],
];
 
foreach ([1, 2, 3] as $key) {
  $form['table'][$key]['key'] = [
    '#markup' => 'Key #' . $key,
  ];
 
  $form['table'][$key]['value'] = [
    '#type' => 'textfield',
    '#title' => 'Value #' . $key,
  ];
}

Подробнее
Как создать табличную форму с чекбоксами?
class ExampleForm extends FormBase {
 
  /**
   * {@inheritDoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $rows = [
      1 => ['nid' => 1, 'title' => 'First node'],
      2 => ['nid' => 2, 'title' => 'Second node'],
      3 => ['nid' => 3, 'title' => 'Third node'],
    ];
 
    $form['nodes'] = [
      '#type' => 'tableselect',
      '#header' => [
        'nid'   => 'Node ID',
        'title' => 'Node title',
      ],
      '#options' => $rows,
      '#empty' => 'Empty...',
    ];
 
    $form['delete'] = [
      '#type' => 'submit',
      '#value' => 'Delete selected',
    ];
 
    return $form;
  }
 
  /**
   * {@inheritDoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    foreach ($form_state->getValue('nodes') as $nid => $state) {
      if ($state) {
        Node::load($nid)->delete();
      }
    }
  }
 
}

Подробнее.

Работа с базой данных (Database API, Entity Query API)

Как добавить в запрос несколько условий с оператором OR?
$or_conditions = $query->orConditionGroup();
$or_conditions->condition('n.status', 0);
$or_conditions->condition('n.status', 1);
$query->condition($or_conditions);
$query->condition('n.type', 'page');
// Сгенерирует условие:
// WHERE (n.status = 0 OR n.status = 1) AND n.type = 'page'

Подробнее.
Как в запросе проверить значение на NULL или NOT NULL?
$query->isNull('field_name');
$query->isNotNull('field_name');
Как подсчитать число записей с помощью COUNT(*)?
$count = \Drupal::database()
  ->select('node')
  ->condition('type', 'page')
  ->countQuery() // <--
  ->execute()
  ->fetchField();

Подробнее
Как получить максимальное/минимальное значение с помощью MAX()/MIN()?
// Max
$query = \Drupal::database()->select('node', 'n');
$query->addExpression('MAX(n.nid)');
$max_nid = $query->execute()->fetchField();
 
// Min
$query = \Drupal::database()->select('node', 'n');
$query->addExpression('MIN(n.nid)');
$min_nid = $query->execute()->fetchField();
Как использовать условие EXISTS?
$subquery = \Drupal::database()
  ->select('taxonomy_index', 't')
  ->fields('t')
  ->where('t.nid = n.nid');
 
$query = \Drupal::database()
  ->select('node', 'n')
  ->fields('n')
  ->exists($subquery);

Код сгенерит запрос вида:
SELECT * FROM node n
WHERE EXISTS (
  SELECT * FROM taxonomy_index t
  WHERE t.nid = n.nid
)
Как получить сущности по значениям полей?
$node_query = \Drupal::entityQuery('node');
$node_query->condition('type', 'article');
$node_query->condition('status', NodeInterface::PUBLISHED);
$node_query->condition('field_foo', 'bar');
$nids = $node_query->execute();

Подробнее.

Как в entity query добавить условие - "многозначное поле имеет значение 1 и 2"?
$node_query = \Drupal::entityQuery('node');
$node_query->condition($node_query->andConditionGroup()->condition('field_example', 'foo'));
$node_query->condition($node_query->andConditionGroup()->condition('field_example', 'bar'));
$nids = $node_query->execute();
Как в entity query добавить условие - "поле не имеет значений" или "поле не пустое"?
// Условие "поле field_foo не должно иметь значений"
$node_query->notExists('field_foo');
// Обратное условие "поле field_bar должно иметь хотя бы одно значение"
$node_query->exists('field_bar');
Как в entity query добавить условие - "многозначное поле имеет не больше одного значения"?
$node_query->notExists('field_example.1.value');

В зависимости от типа поля, вместо value может быть что-то другое, например target_id для поля типа entity reference.
Как с помощью entity query посчитать число сущностей (COUNT(*))?
$news_count = \Drupal::entityQuery('node')
  ->condition('type', 'news')
  ->count()
  ->execute();
Как посмотреть sql запрос, сгенерированный сервисом entity.query?
Для вывода sql запроса нужно перед выполнением добавить к объекту запроса тэг debug:
$node_query = \Drupal::entityQuery('node');
...
$node_query->addTag('debug');
$result = $node_query->execute();

Работает только с включённым модулем Devel.

Работа с меню и адресами, навигация, роутинг

Как из админки создать пункт меню без ссылки?
Как добавить свою ссылку на странице admin/config?
modulename.links.menu.yml:
modulename.settings:
  title: 'Modulename settings'
  description: 'Settings for modulename.'
  parent: system.admin_config_system
  route_name: modulename.settings

При этом роут modulename.settings уже должен существовать. Подробнее.
Как добавить свой таб/вкладку/локальную-задачу/local-task на страницы нод?
modulename.routing.yml:
entity.node.my_tab:
  path: '/node/{node}/my-tab'
  defaults:
    _controller: '\Drupal\modulename\Controller\MyController::myAction'
    _title: 'My tab'
  requirements:
    _permission: 'access content'

modulename.links.task.yml:
entity.node.my_tab:
  route_name: entity.node.my_tab
  base_route: entity.node.canonical
  title: My tab
  weight: 2

Подробнее.
Как получить адрес текущей страницы? Какой аналог current_path(), request_path() и request_uri()?
// Системный адрес. Аналог current_path().
$current_system_path = \Drupal::service('path.current')->getPath();
 
// Синоним адреса. Аналог request_path().
$current_system_path = \Drupal::service('path.current')->getPath();
$current_path_alias = \Drupal::service('path.alias_manager')->getAliasByPath($current_system_path);
 
// Полный путь из строки браузера, с GET параметрами. Аналог request_uri().
$current_request_uri = \Drupal::request()->getRequestUri();
Как получить адрес папки установки друпала?
$base_path = base_path();
Если друпал установлен в корень сайта, то base_path() вернёт /.
Если друпал установлен в папку /drupal/foler, то base_path() вернёт /drupal/foler/.
Как получить название текущего роута?
$current_route_name = \Drupal::routeMatch()->getRouteName();
Как проверить, является ли текущая страница страницей материала?
if (\Drupal::routeMatch()->getRouteName() == 'entity.node.canonical') {
  ...
}
Как получать параметры из текущего пути? Какой аналог arg() и menu_get_object()?
Пример для адреса node/123
$nid = \Drupal::routeMatch()->getRawParameter('node'); // Integer
$node = \Drupal::routeMatch()->getParameter('node'); // Node object
Как добавить класс ссылке меню, созданной с помощью *.links.menu.yml?
Как получить url файла по его uri?
// Вернёт http://example.com/sites/default/files/example.jpg
$file_url = file_create_url('public://example.jpg');
Как создать url с destination на текущую страницу?
$destination_array = \Drupal::destination()->getAsArray();
$url = \Drupal::urlGenerator()->generateFromRoute('example.route', [], ['query' => $destination_array]);
Как сделать редирект из контроллера?
class ExampleController extends ControllerBase {
 
  public function exampleAction() {
    return $this->redirect('<front>');
  }
 
}
Как получить синоним адреса (url alias) ноды?
$node_alias = $node->get('path')->alias; // "/example/node/path"
Написанное актуально для Drupal 8
Похожие записи

Комментарии RSS

Спасибо! Ценный материал, и в одном месте.

Насколько люблю 7-ку настолько же ненавижу 8-ку. Может я его неправильно курю? Простой пример, но вместо одной строки в info файле 7-ки делать отдельный файл с кучей строк(https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-menu...). Зачем так усложнять?

Из .info файла ссылку конечно не создать, нужен hook_menu, но то что писать теперь нужно больше это да. Усложнено в угоду масштабируемости, хоть большинству она и не нужна. Тут просто надо свыкнуться с мыслью, что семёрка мертва и придётся учить много нового.

Если использовать генераторы кода (drush и drupal console), то в некоторых местах приходится писать даже меньше кода, чем раньше. Да и многие вещи стали гибче, удобнее и логичнее. Но сложности добавилось, это факт.

Оставить комментарий

Содержимое этого поля является приватным и не будет отображаться публично. Если у вас есть аккаунт в Gravatar, привязанный к этому e-mail адресу, то он будет использован для отображения аватара.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Доступные HTML теги: <a> <i> <b> <strong> <code> <ul> <ol> <li> <blockquote> <em> <s>
  • Строки и параграфы переносятся автоматически.
  • Подсветка кода осуществляется с помощью тегов: <code>, <css>, <html>, <ini>, <javascript>, <sql>, <php>. Поддерживаемые стили выделения кода: <foo>, [foo].

Подробнее о форматировании