Drupal → FAQ

06.04.2019

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

Все консольные команды рассчитаны на выполнение из корня друпала. Пользователям Windows перед выполнением команд в cmd.exe нужно заменить разделитель директорий с / на \, т.е. вместо vendor/bin/drush писать vendor\bin\drush, а лучше пользоваться башем из Cygwin. Так же пользователям Windows надо брать в двойные кавычки аргументы с символом ^, например composer require "foo/bar:^1.0".

Содержание

  1. Работа с Composer
  2. Общие вопросы
  3. Общие вопросы по работе с кодом (Drupal API)
  4. Работа с сущностями (Entity API)
  5. Работа с полями сущностей (Field API)
  6. Работа с таксономией (Taxonomy API)
  7. Темизация, рендеринг (Theming API, Render API)
  8. Работа с Twig
  9. Работа с формами (Form API, Ajax API)
  10. Работа с базой данных (Database API)
  11. Работа с сервисом entity.query (Entity Query API)
  12. Работа с меню и адресами, навигация, роутинг (Menu API, Url, Routing API)
  13. Работа с Views
  14. Работа с JavaScript
  15. Работа с многоязычностью (Language API, Translation, i18n, Multilingual)

Работа с Composer

Как установить composer локально (для одного конкретного сайта)?

cd /path/to/drupal
wget -O composer.phar https://getcomposer.org/composer-stable.phar

После этого можно пользоваться композером через команду php composer.phar, например:

php composer.phar require drupal/devel
Как правильно скачать Drupal? Как скачать Drupal с помощью composer?

Способ 1 (папка vendor будет выше web root):

composer create-project drupal/recommended-project

Способ 2 (папка vendor будет в web root):

composer create-project drupal/legacy-project

Подробнее

Что изменить в дефолтном composer.json?

1. Изменить "minimum-stability": "stable" на "minimum-stability": "dev"

2. В секцию config добавить "optimize-autoloader": true

3. Из require удалить пакеты drupal/core-composer-scaffold и drupal/core-project-message

4. Из секции extra удалить drupal-scaffold и drupal-core-project-message

Как обновить ядро друпала?

Если установленная версия свежее или равна 8.8.0:

composer update drupal/core* --with-all-dependencies
vendor/bin/drush updb

Подробнее.

Для версий меньше 8.8.0 сначала обновляемся до 8.8.0 по статье никлана, а потом до актуальной версии способом выше.

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

Как скачать модуль?

Последняя рекомендуемая версия:

composer require drupal/devel

RC версия:

composer require "drupal/field_group:^3.0-rc2"

Dev версия:

composer require drupal/devel:1.x-dev

Подробнее.

Замечание 1: надо всегда следить за тем, какую версию скачивает композер и какая актуальная на странице модуля. Он может легко скачать модуль на пару версий меньше, если например в composer.json есть "prefer-stable": true, а у модуля доступна только rc версия.

Как удалить модуль?

1. Сначала удаляем из базы:

vendor/bin/drush pm-uninstall devel

2. Потом удаляем из файловой системы:

composer remove drupal/devel
Как обновить модуль?

В большинстве случаев:

composer update drupal/devel --with-all-dependencies

Иногда модуль содержит несколько под-модулей и в каждом свой composer.json. Такие модули можно обновлять по маске:

composer update drupal/commerce* --with-all-dependencies

Если надо обновить модуль на новую мажорную версию, то необходимо явно указать её в require:

composer require "drupal/devel:^3.0"

Если надо обновить со стабильной до dev версии:

composer require drupal/devel:4.x-dev

После обновления с помощью composer не забываем выполнять

vendor/bin/drush updb
Как узнать что блокирует обновление модуля/пакета?

Если не удаётся обновить какой-нибудь пакет или ядро с помощью composer, то поможет команда prohibits (синоним why-not), которая выдаст причины:

composer prohibits drupal/core 8.9.2
Как обновить всё если ничего не помогает?

Бывает при попытке обновиться composer выплёвывает кучу ошибок и совершенно непонятно что делать. Я обычно поступаю кардинально:

0. Делаю бэкап файлов и базы

1. Удаляю папки /core, /vendor, /modules/contrib и файл /composer.lock

2. Обновляю composer — composer self-update (composer self-update --2 если нужно обновиться с первой до второй версии)

3. Проверяю, что composer работает нормально — composer diagnose

4. Изменяю composer.json (исправляю версии, добавляю/удаляю пакеты и т.д.)

5. Выполняю composer install

6. Если всё хорошо, то vendor/bin/drush updb

Как посмотреть список пакетов/модулей, нуждающихся в обновлении?

Все модули и пакеты:

composer outdated

Только drupal модули:

composer outdated drupal/*
Как узнать, какие composer пакеты зависят от определённого пакета?

Пример вывода пакетов, зависящих от symfony/process

composer why symfony/process

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

С чего начать освоение Drupal?

С установки и настройки Xdebug.
На какой версии друпала начинать разрабатывать новый сайт (7/8/9/10/11)?

Всегда начинайте на последней стабильной мажорной версии, с момента релиза которой прошло больше двух/трёх месяцев, в данный момент это Drupal 10.
Как инсталлировать Drupal из консоли? Как установить Drupal с помощью Drush?

composer create-project drupal/recommended-project .
composer require drush/drush
vendor/bin/drush site-install standard --db-url=mysql://root:root@localhost/drupal --account-pass=admin

Подробнее

Как очистить кэш?

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

Способ 2 - выполнить в консоли:

vendor/bin/drush cache-rebuild

Способ 3 - выполнить php функцию:

drupal_flush_all_caches();
Как изменить пароль администратора?

Пример изменения пароля у пользователя с именем admin:

vendor/bin/drush user-password admin qwerty

Подробнее

Как сделать, чтобы комментарии сортировались по дате создания, от свежих к старым?

/**
 * Implements hook_query_TAG_alter(): comment_filter.
 */
function hook_query_comment_filter_alter(QueryAlterableInterface $query): void {
  if ($query instanceof PagerSelectExtender) {
    $order_by = &$query->getOrderBy();
    unset($order_by['c.cid']);
    $query->orderBy('c.created', 'DESC');
  }
}
Как узнать, есть ли в моей версии php поддержка формата WebP?

Если для работы с изображениями используется GD, то надо выполнить в консоли следующий код, который покажет все поддерживаемые форматы:

vendor/bin/drush eval "print_r(gd_info())"
Как включить транслитерацию имён загружаемых файлов?

На странице /admin/config/media/file-system включаем опцию "Транслитерировать".

Общие вопросы по работе с кодом (Drupal API)

Как единоразово выполнить какой-то кусок php кода без создания модуля?

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

vendor/bin/drush eval "drupal_flush_all_caches();"

Или из админки с помощью модуля Devel PHP.

Как программно создать страницу с каким-то текстом?

// src/Controller/ModulenameController.php

namespace Drupal\modulename\Controller;

use Drupal\Core\Controller\ControllerBase;

class ModulenameController extends ControllerBase {

  public function helloWorld(): array {
    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

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 {

  /**
   * {@inheritDoc}
   */
  public function build(): array {
    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(): array {
    ...
  }

}

Способ 2:

/**
 * @Block(...)
 */
class MyBlock extends BlockBase {

  public function build(): array {
    ...
  }

  public function getCacheMaxAge() {
    return 0; // <---
  }

}
Как программно ограничить видимость своего блока?

class MyBlock extends BlockBase {
  
  ...

  /**
   * {@inheritdoc}
   */
  public function blockAccess(AccountInterface $account): AccessResultInterface {
    $result = TRUE;

    if (...) {
      $result = FALSE;
    }

    return AccessResult::allowedIf($result);
  }

}
Как программно ограничить видимость чужого блока?

Запрещаем показывать блок system_powered_by_block в регионах отличных от footer:

// MODULENAME.module

use Drupal\block\Entity\Block;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Access\AccessResult;

/**
 * Implements hook_block_access().
 */
function MODULENAME_block_access(Drupal\block\Entity\Block $block, string $operation, AccountInterface $account): AccessResultInterface {
  if ($operation == 'view' && $block->getPluginId() == 'system_powered_by_block') {
    return AccessResult::forbiddenIf($block->getRegion() != 'footer')->addCacheableDependency($block);
  }
  
  return AccessResult::neutral();
}

Работает только в файлах модуля, в файле .theme этот хук не вызывается.

Как получить значение конфигурации? Какой аналог variable_get()?

$site_mail = \Drupal::config('system.site')->get('mail'); // Вернёт строку "site@example.com"

Подробнее

Как получить название сайта, слоган и основной e-mail?

$site_config = \Drupal::config('system.site');

$site_name = $site_config->get('name');
$site_slogan = $site_config->get('slogan');
$site_mail = $site_config->get('mail');
Как закэшировать определённые данные? Как получить данные из кэша?

$cache_backend = \Drupal::cache('default');
if ($cache = $cache_backend->get('my_data')) {
  $my_data = $cache->data;
}
else {
  $my_data = ...;
  $cache_backend->set('my_data', $my_data, strtotime('+1 hour'));
}

Подробее.

Как закэшировать определённые данные до конца дня?

\Drupal::cache('default')->set('my_data', $my_data, strtotime('tomorrow'));
Как замерить время выполнения участка кода? Какой аналог timer_start() и timer_read()?

use Drupal\Component\Utility\Timer;

Timer::start('test');
sleep(1);
debug(Timer::read('test') . ' ms'); // Выведет "1000 ms"
Как получить текущего пользователя? Какой аналог $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_is_anonymous()?

if (\Drupal::currentUser()->isAuthenticated()) {
  ...
}

if (\Drupal::currentUser()->isAnonymous()) {
  ...
}
Как проверить права текущего пользователя? Какой аналог user_access()?

if (\Drupal::currentUser()->hasPermission('administer site configuration')) {
  ...
}
Как проверить роль текущего пользователя? Как проверить, является ли текущий пользователь администратором?

if (in_array('administrator', \Drupal::currentUser()->getRoles())) {
  // Current user has role "administrator"
}
if (array_intersect(['administrator', 'editor'], \Drupal::currentUser()->getRoles())) {
  // Current user has role "administrator" or "editor"
}
Как получить системное название административной роли?

$admin_role_name = current(
  \Drupal::entityQuery('user_role')
    ->condition('is_admin', TRUE)
    ->range(0, 1)
    ->execute()
); // Вернёт "administrator"
Как получить сущность администратора?

$admin_role_name = current(
  \Drupal::entityQuery('user_role')
    ->condition('is_admin', TRUE)
    ->range(0, 1)
    ->execute()
);
$admin_user_id = current(
  \Drupal::entityQuery('user')
    ->condition('status', 1)
    ->condition('roles', $admin_role_name)
    ->range(0, 1)
    ->execute()
);
$admin_user = \Drupal\user\Entity\User::load($admin_user_id);
Как в PHP получить данные из $_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();

Замечание — в Drupal 10 с помощью метода ->get('param-name') нельзя получать массивы, надо делать либо ->all('param-name'), либо ->all()['param-name'].

Как экранировать html код? Что использовать вместо check_plain()?

$escaped_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? Как декодировать JSON в массив? Какой аналог drupal_json_encode() и drupal_json_decode()?

$string = \Drupal\Component\Serialization\Json::encode(['foo' => 'bar']); // Вернёт строку {"foo":"bar"}
$array = \Drupal\Component\Serialization\Json::decode('{"foo":"bar"}'); // Вернёт массив ['foo' => 'bar']
Как получить текущее время в timestamp?

$current_timestamp = \Drupal::time()->getCurrentTime(); // Вернёт число, например 1598281486
Как форматировать unix timestamp в дату? Какой аналог format_date()?

/** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
$date_formatter = \Drupal::service('date.formatter');
$formatted_date = $date_formatter->format(1558730206, 'short');
$formatted_date = $date_formatter->format(1558730206, 'custom', 'd.m.Y');
Как сравнить две даты в DrupalDateTime?

$date1 = new \Drupal\Core\Datetime\DrupalDateTime('-1 day');
$date2 = new \Drupal\Core\Datetime\DrupalDateTime('+1 day');
dvm($date1 > $date2); // false
dvm($date1 < $date2); // true

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

$date1 = new \Drupal\Core\Datetime\DrupalDateTime('-1 day');
$date2 = new \Drupal\Core\Datetime\DrupalDateTime('+1 day');
dvm($date1->getTimestamp() > $date2->getTimestamp()); // false
dvm($date1->getTimestamp() < $date2->getTimestamp()); // true
Как форматировать временной интервал? Как вывести секунды в человекопонятном формате?

$timestamp_from = strtotime('-10 second');
$timestamp_to = strtotime('-3 second');
echo \Drupal::service('date.formatter')->formatDiff($timestamp_from, $timestamp_to);
// Выведет "7 секунд"

$timestamp_from = strtotime('-10 hours');
$timestamp_to = strtotime('-30 minutes');
echo \Drupal::service('date.formatter')->formatDiff($timestamp_from, $timestamp_to);
// Выведет "9 часов 30 минут"
Как отправить e-mail/письмо/почту?

Практически так-же, как в Drupal 6 и 7. В нужный момент вызываем \Drupal::service('plugin.manager.mail')->mail(), куда передаём необходимые переменные:

\Drupal::service('plugin.manager.mail')->mail(
  'modulename',
  'example_mail_key',
  'to@gmail.com',
  'en',
  ['myvar' => 123]
);

Плюс реализуем хук hook_mail(), в котором формируем заголовок и текст письма:

/**
 * Implements hook_mail().
 */
function MODULENAME_mail(string $key, array &$message, array $params): void {
  if ($key == 'example_mail_key') {
    $message['subject'] = 'Example email subject';
    $message['body'][] = 'Example email body. myvar = ' . $params['myvar'];
  }
}

Можно обойтись без реализации хука hook_mail(), если указать модуль 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'),
  ],
]);
Как изменить текст или заголовок какого-либо письма/e-mail?

Пример изменения письма об успешном заказе в Commerce 2:

/**
 * Implements hook_mail_alter().
 */
function MODULENAME_mail_alter(array &$message): void {
  if ($message['module'] == 'commerce' && $message['key'] == 'order_receipt') {
    $message['subject'] = 'New subject';
    $message['body'] = ['New body'];
  }
}

Подробнее

Как получить ip адрес текущего пользователя?

$ip = \Drupal::request()->getClientIp();

Если на сервере используется reverse proxy, например nginx поверх apache, то надо добавить в settings.php:

$settings['reverse_proxy'] = TRUE;
$settings['reverse_proxy_addresses'] = ['ip адрес вашего сервера'];
Как транслитерировать строку?

$transliterated_string = \Drupal::transliteration()->transliterate('Привет Мир', 'ru');
// Вернёт строку "Privet Mir"
Как отдать пользователю текст в виде файла?

// src/Controller/ExampleController.php

class ExampleController extends ControllerBase {

  public function export() {
    $response = new Response();
    $response->headers->set('Content-Type', 'text/plain; 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();
Как добавить свой токен в [node:*]? Как создать свой токен вида "[node:example-token]"?

// modulename.tokens.inc

/**
 * Implements hook_token_info().
 */
function MODULENAME_token_info(): array {
  $token_info['tokens']['node']['example-token'] = [
    'name' => 'Example node token',
  ];

  return $token_info;
}

/**
 * Implements hook_tokens().
 */
function MODULENAME_tokens(string $type, array $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata): array {
  $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('String with "[node:title]" token', [
  'node' => \Drupal\node\Entity\Node::load(123),
]);
Как программно добавить свой html-тэг в head? Как программно добавить inline-скрипт в head?

Пример добавления тега <script> в head:

/**
 * Implements hook_page_attachments().
 */
function MODULENAME_page_attachments(array &$attachments): void {
  $attachments['#attached']['html_head'][] = [[
    '#tag' => 'script',
    '#value' => "document.documentElement.classList.add('js')",
  ], 'has_js'];
}

Добавлять #attached можно добавлять в любой рендер-массив, необязательно в hook_page_attachments().

Как программно вывести на своей странице <meta name="description" ... />?

В любой рендер-массив добавляем специально сформированный элемент ['#attached']['html_head']. Пример добавления метатега description из своего контроллера:

class ExampleController extends ControllerBase {

  public function exampleAction(): array {
    $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'

Подробнее

Как создать свой хук?

\Drupal::moduleHandler()->invokeAll('my_custom_hook', ['foo', 'bar']);

После этого другие модули смогут реализовывать хук hook_my_custom_hook():

function MODULENAME_my_custom_hook($arg1, $arg2): void {
  ...
}

Подробнее

Как создать свой alter-хук? Какой аналог drupal_alter()?

$data = ['foo' => 'bar'];
\Drupal::moduleHandler()->alter('my_data', $data);

После этого другие модули смогут альтерить данные с помощью хука hook_my_data_alter:

function MODULENAME_my_data_alter(&$data): void {
  $data['foo'] = 'baz';
}

Подробнее

Как получить число комментариев у сущности?

$comment_count = (int)$entity->get('field_comment')->comment_count;
Как получить номер страницы списка комментариев, на которой отображается нужный комментарий?

Функция возвращает номер страницы комментария:

function _get_comment_page(int $entity_id, int $comment_cid, int $per_page): int {
  $comment_index = \Drupal::database()
    ->select('comment_field_data')
    ->condition('entity_id', $entity_id)
    ->condition('status', 1)
    ->condition('cid', $comment_cid, '>=')
    ->countQuery()
    ->execute()
    ->fetchField();

  return ceil($comment_index / $per_page) - 1;
}

$entity_id - id сущности с комментариями

$comment_cid - id комментария

$per_page - число комментариев на страницу

Как подписаться на событие?

// src/EventSubscriber/ModulenameEventSubscriber.php

class ModulenameEventSubscriber implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    $events['example_event_name'][] = ['onExampleEventName', 0];
    return $events;
  }

  /**
   * Event callback.
   */
  public function onExampleEventName(Event $event) {
    ...
  }

}
# modulename.services.yml

services:
  modulename.event_subscriber:
    class: Drupal\modulename\EventSubscriber\ModulenameEventSubscriber
    tags:
      - { name: event_subscriber }

Подробнее

Где указать вес своего модуля? Как изменить вес своего модуля?

Вес модуля задаётся в hook_install():

// modulename.install

/**
 * Implements hook_install().
 */
function modulename_install(): void {
  module_set_weight('modulename', 123);
}

Если нужно изменить вес уже установленного модуля, то пользуемся hook_update_N():

// modulename.install

/**
 * Set module weight.
 */
function modulename_update_8001(): void {
  module_set_weight('modulename', 123);
}
Как программно проверить, что включён определённый модуль?

if (\Drupal::service('module_handler')->moduleExists('views')) {
  // Views module enabled
}
Как скачать файл на сервер по url файла?

$file = system_retrieve_file('http://example.com/image.jpg', 'public://image.jpg', TRUE);
Как создать свой форматтер поля? Какой аналог hook_field_formatter_*?

Пример форматтера выводящий e-mail в виде ссылки:

// src/Plugin/Field/FieldFormatter/EmailLinkFormatter.php

namespace Drupal\random\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;

/**
 * @FieldFormatter(
 *   id = "email_link",
 *   label = @Translation("E-mail link"),
 *   field_types = {
 *     "email",
 *   },
 * )
 */
class EmailLinkFormatter extends FormatterBase {

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode): array {
    $element = [];

    foreach ($items as $delta => $item) {
      $element[$delta] = [
        '#markup' => '<a href="mailto:' . $item->value . '">' . $item->value . '</a>',
      ];
    }

    return $element;
  }

}

Подробнее

Как регулярно раз в день выполнять определённый код? Как выполнять свой hook_cron() раз в день?

// MODULENAME.module

/**
 * Implements hook_cron().
 */
function MODULENAME_cron(): void {
  $current_timestamp = \Drupal::time()->getRequestTime();
  $cron_last_run_timestamp = \Drupal::state()->get('system.cron_last');
  if (date('Ymd', $current_timestamp) != date('Ymd', $cron_last_run_timestamp)) {
    // code
  }
}
Как в php получить результат выполнения определённого метода контроллера?

$cart_page_result = \Drupal::service('controller_resolver')
  ->getControllerFromDefinition(\Drupal\commerce_cart\Controller\CartController::class)
  ->cartPage();
Как в контроллере вывести raw-текст вместо html страницы?

// src/ExampleController.php

class ExampleController extends ControllerBase {

  public function exampleText() {
    return new Response('Hello World!');
  }

}
# example.routing.yml

example.text:
  path: '/example/text'
  defaults:
    _controller: '\Drupal\example\ExampleController::exampleText'
  requirements:
    _access: 'TRUE'
Как изменить хлебные крошки на определённой странице?

Пример изменения хлебных крошек на страницах нод типа Article:

// MODULENAME.module

/**
 * Implements hook_system_breadcrumb_alter().
 */
function MODULENAME_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context): void {
  if (
    $route_match->getRouteName() == 'entity.node.canonical' &&
    ($node = $route_match->getParameter('node')) &&
    $node->bundle() == 'article'
  ) {
    $links = [
      Link::createFromRoute(t('Home'), '<front>'),
      $node->get('field_category')->entity->toLink(),
      Link::createFromRoute($node->label(), '<nolink>')
    ];
    $breadcrumb = new Breadcrumb();
    $breadcrumb->setLinks($links);
    $breadcrumb->addCacheContexts(['url']);
  }
}

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

Как получить объект сущности по её id? Как загрузить сущность? Какой аналог entity_load()/node_load()?

// С помощью статического метода 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);
Как программно создать ноду?

$node = \Drupal\node\Entity\Node::create([
  'type' => 'article',
  'title' => 'My article',
  'body' => 'Article body',
]);
$node->save();

Подробнее

Как получить ноду по её синониму 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]);
}
Как получить объект бандла сущности, например NodeType у ноды?

// Способ 1
$node_type = $node->get('type')->entity;

// Способ 2
$node_type = NodeType::load($node->bundle());
Как получить человеческое название типа материала?

$node_type_label = $node->get('type')->entity->label(); // Возвратит например "Статья" или "Страница"
Как получить адрес/url/uri файла у сущности типа File?

$file = \Drupal\file\Entity\File::load(123); /** @var \Drupal\file\FileInterface $file */
$file_relative_url = $file->createFileUrl(); // /sites/default/files/example.jpg
$file_absolute_url = $file->createFileUrl(FALSE); // http://example.com/sites/default/files/example.jpg
$file_uri = $file->getFileUri(); // public://example.jpg
Как получить объект сущности файла по его uri?

$file_uri = 'public://example.jpg';
if ($files = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['uri' => $file_uri])) {
  $file = current($files); /** @var FileInterface $file */
}
Как создать файловую сущность по uri существующего файла?

$file = \Drupal\file\Entity\File::create(['uri' => 'public://example.jpg']);
$file->save();
Как программно удалить ноду? Как программно удалить сущность? Какой аналог entity_delete(), node_delete()?

$node = \Drupal\node\Entity\Node::load(123);
$node->delete();

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

vendor/bin/drush entity-delete node 123
Как программно удалить сразу несколько материалов или сущностей?

Пример удаления трёх нод:

$entity_storage = \Drupal::entityTypeManager()->getStorage('node');
$entities = $entity_storage->loadMultiple([1, 2, 3]);
$entity_storage->delete($entities);

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

vendor/bin/drush entity-delete node 1,2,3
Как программно удалить все сущности определённого бандла? Как программно удалить все ноды определённого типа?

Пример удаления всех нод типа article (способ подходит для удаления небольшого количества сущностей, до тысячи):

\Drupal\Component\Utility\Environment::setTimeLimit(0);
$entity_storage = \Drupal::entityTypeManager()->getStorage('node');
$entities = $entity_storage->loadByProperties(['type' => 'article']);
$entity_storage->delete($entities);

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

vendor/bin/drush entity-delete node --bundle=article
Как программно пересохранить все ноды определённого типа?

\Drupal\Component\Utility\Environment::setTimeLimit(0);
/** @var NodeStorageInterface $node_storage */
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$nodes = $node_storage->loadByProperties(['type' => 'article']);
foreach ($nodes as $node) {
  $node->save();
}

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

vendor/bin/drush entity-save node --bundle=article
Как отсортировать конфиг-сущности по весу?

У всех конфиг-сущностей есть статический метод sort(), который можно использовать в uasort(). Пример сортировки сущностей модуля Domain Access:

/** @var \Drupal\domain\DomainInterface[] $domains */
$domains = \Drupal\domain\Entity\Domain::loadMultiple();
uasort($domains, '\Drupal\domain\Entity\Domain::sort');

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

Как получить значение поля сущности?

В большинстве случаев:

$field_value = $entity->get('field_name')->value;

У некоторых полей название свойства, в котором хранится значение, может отличаться, например у полей типа entity reference это target_id:

$category_id = $entity->get('field_category')->target_id;

Бывают составные поля с несколькими свойствами:

$body_text = $node->get('body')->value;
$body_format = $node->get('body')->format;

Бывают поля с вычисляемыми (computed) свойствами, которые не хранятся в базе:

$category = $entity->get('field_category')->entity;
Как получить все значения многозначного-поля в виде массива?

$values = array_map(function (FieldItemInterface $item) {
  return $item->value;
}, iterator_to_array($entity->get('field_name')));
Как пройтись по всем значениям поля?

foreach ($entity->get('field_name') as $item) {
  $item_value = $item->value;
}
Как проверить, есть ли у поля сущности значение?

if (!$entity->get('field_example')->isEmpty()) {
  // field_example not empty
}
Как получить машинное имя поля заголовка сущности?

$label_key = $entity->getEntityType()->getKey('label'); // Вернёт "title" для ноды и "name" для термина
Как получить лейбл (label) поля?

// Если есть доступ к объекту сущности
$field_example_label = $entity->get('field_example')->getFieldDefinition()->getLabel();
// Иначе
$field_example_label = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', 'page')['field_example']->getLabel();
Как проверить, есть ли у сущности или бандла определённое поле?

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

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())) {
  ...
}
Как получить информацию о поле? Какой аналог field_info_field()?

Универсальный способ получить информацию о любом поле, хоть базовом, хоть настраиваемом:

$field_definition = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', 'page')['field_category'];
$field_storage_definition = $field_definition->getFieldStorageDefinition();

Для настраиваемых полей можно так же пользоваться методом \Drupal\field\Entity\FieldConfig::loadByName() и \Drupal\field\Entity\FieldStorageConfig::loadByName().

Как получить информацию обо всех полях определённого типа?

/** @var EntityFieldManagerInterface $entity_field_manger */
$entity_field_manger = \Drupal::service('entity_field.manager');
$entity_reference_fields = $entity_field_manger->getFieldMapByFieldType('entity_reference');

Результат:

[node] => Array
        (
            [uid] => Array
                (
                    [type] => entity_reference
                    [bundles] => Array
                        (
                            [page] => page
                            [article] => article
                        )
                )
        )
...
Как получить информацию обо всех полях у определённого бандла сущности?

/** @var EntityFieldManager $entity_field_manager */
$entity_field_manager = \Drupal::service('entity_field.manager');
$article_fields = $entity_field_manager->getFieldDefinitions('node', 'article');
Как вычислить число дней между датами в поле типа Date range?

/** @var \Drupal\Core\Datetime\DrupalDateTime $date_start */
$date_start = $node->get('field_daterange')->start_date;
/** @var \Drupal\Core\Datetime\DrupalDateTime $date_end */
$date_end = $node->get('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_items = $entity->get('field_example');
$allowed_values = options_allowed_values($field_items->getFieldDefinition()->getFieldStorageDefinition(), $entity);

Иначе:

$field_name = 'field_example';
$entity_type = 'node';
$entity_bundle = 'article';

$field_manager = \Drupal::service('entity_field.manager'); /** @var EntityFieldManagerInterface $field_manager */
$field_definition = $field_manager->getFieldDefinitions($entity_type, $entity_bundle)[$field_name];
$field_storage_definition = $field_definition->getFieldStorageDefinition();
$allowed_values = options_allowed_values($field_storage_definition);
Как получить название значения у поля типa List?

$field_items = $entity->get('field_example');
$allowed_values = options_allowed_values($field_items->getFieldDefinition()->getFieldStorageDefinition(), $entity);
$value_label = $allowed_values[$items->value] ?? NULL;
Как получить адрес/url/uri файла из reference-поля сущности?

$file = $entity->get('field_file')->entity; /** @var \Drupal\file\FileInterface $file */
$file_url = $file->createFileUrl(); // /sites/default/files/example.jpg
$file_uri = $file->getFileUri(); // public://example.jpg
Как получить адрес из поля типа Link?

Как программно добавить файл в многозначное файловое поле сущности?

$file_id = 123;
$entity->get('field_file')->appendItem(['target_id' => $file_id]);
$entity->save();

или так:

$file = \Drupal\file\Entity\File::load(123);
$entity->get('field_file')->appendItem($file);
$entity->save();
Как в hook_entity_update() узнать, что значение определённого поля изменилось?

/**
 * Implements hook_entity_update().
 */
function hook_entity_update(EntityInterface $entity): void {
  $field_example_items = $entity->get('field_example');
  $original_field_example_items = $entity->original->get('field_example');
  if ($field_example_items->hasAffectingChanges($original_field_example_items, $field_example_items->getLangcode())) {
    // field_example changed
  }
}
Как удалить определённое значение многозначного поля?

$node = \Drupal\node\Entity\Node::load(123);

// Три способа удалить значение по его индексу (дельте)
unset($node->get('field_tags')[2]); // Способ 1
$node->get('field_tags')->offsetUnset(2); // Способ 2
$node->get('field_tags')->removeItem(2); // Способ 3

// Способ удалить значение если индекс неизвестен
$node->get('field_tags')->filter(function ($item) {
  return $item->target_id != 123; // Оставляем все значения кроме термина с id=123
});

$node->save();
В каком типе поля хранить дробные числа, в decimal или float? Чем отличаются типы полей integer/decimal/float?

Если числа будут только целые, то выбираем тип поля integer, если дробные, то decimal. Тип поля float обходим стороной, потому что значения в базе хранятся приблизительные и будут проблемы с выборками.
Как в поле типа Image разрешить загружать SVG файлы?

1. Поставить модуль Svg Image.

2. В настройках поля к допустимым расширениям добавить svg

Работа с таксономией (Taxonomy API)

Как получить все термины определённого словаря?

$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$terms = $term_storage->loadByProperties(['vid' => 'category']);
Как получить массив с именами терминов определённого словаря?

// Способ 1
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$terms = $term_storage->loadByProperties(['vid' => 'category']);
$names = array_map(function (TermInterface $term) {
  return $term->label();
}, $terms);

// Способ 2
$names = \Drupal::database()
  ->select('taxonomy_term_field_data', 't')
  ->fields('t', ['tid', 'name'])
  ->condition('t.vid', 'category')
  ->orderBy('t.weight')
  ->execute()
  ->fetchAllKeyed();
Как программно пересохранить все термины определённого словаря?

\Drupal\Component\Utility\Environment::setTimeLimit(0);
/** @var TermStorageInterface $term_storage */
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$terms = $term_storage->loadByProperties(['vid' => 'category']);
foreach ($terms as $term) {
  $term->save();
}

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

vendor/bin/drush entity-save taxonomy_term --bundle=category
Как получить дочерние термины у определённого термина?

/** @var TermStorageInterface $term_storage */
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');

// Способ 1 - по id термина. Получает только непосредственных детей термина 123.
// Результат не отсортирован по весу.
$children_terms = $term_storage->loadChildren(123, 'category'); /** @var TermInterface[] $children_terms */

// Способ 2 - по объекту термина. Получает только непосредственных детей термина $term.
// Результат не отсортирован по весу.
$children_terms = $term_storage->getChildren($term); /** @var TermInterface[] $children_terms */

// Способ 3 - по id термина. Получает всех детей термина 123 независимо от вложенности.
// Переданный термин в массив не добавляется.
// Результат отсортирован по весу терминов.
$children_terms = $term_storage->loadTree('category', 123, NULL, TRUE); /** @var TermInterface[] $children_terms */
Как получить родительские термины у определённого термина?

/** @var TermStorageInterface $term_storage */
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');

// Получает всех родителей независимо от глубины.
// ВАЖНО - в массиве так же возвращается и исходный термин.
// Если термин не имеет родителей, то возвратится массив с исходным термином.
$all_parents = $term_storage->loadAllParents(123); /** @var TermInterface[] $all_parents */

// Получает непосредственных родителей термина 123.
// Если термин не имеет родителей, то возвратится пустой массив.
$direct_parents = $term_storage->loadParents(123); /** @var TermInterface[] $direct_parents */

// Непосредственные родители так же доступны через поле термина "parent".
// Если термин не имеет родителей, то в "parent" будет "0", но referencedEntities() вернёт пустой массив.
$direct_parents_items = $term->get('parent'); /** @var EntityReferenceFieldItemListInterface $direct_parents_items */
$direct_parent_id = $direct_parents_items->target_id;
$direct_parents = $direct_parents_items->referencedEntities(); /** @var TermInterface[] $direct_parents */
$direct_parent = $direct_parents_items->entity; /** @var ?TermInterface $direct_parent */

// Самый верхний родитель.
$all_parents = $term_storage->loadAllParents(123); /** @var TermInterface[] $all_parents */
$root_parent = current($all_parents); // Если термин не имеет родителей, то в $root_parent будет текущий термин.

Следует помнить, что непосредственных родителей у термина может быть несколько. Так же следует помнить, что метод $term->get('parent')->isEmpty() всегда возвращает FALSE, потому что там как минимум есть 0, поэтому проверять есть ли у термина родители надо с помощью if ($term->get('parent')->entity).

Темизация, рендеринг (Theming API, Render API)

Как переопределить шаблон в своей теме?

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

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

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

modules/custom/MODULENAME/MODULENAME.module

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

modules/custom/MODULENAME/templates/node.html.twig

{{ content }}

Важно помнить, что вес модуля должен быть больше того, в котором объявлен шаблон.

Как переопределить шаблон suggestion-а в своём модуле?

Пример переопределения шаблона node--article.html.twig.

MODULENAME.module:

/**
 * Implements hook_theme().
 */
function MODULENAME_theme(): array {
  return [
    'node__article' => [
      'base hook' => 'node',
    ],
  ];
}

templates/node--article.html.twig:

<article>
  {{ content }}
</article>
Как переопределить шаблон определённого блока в своей теме?

У каждого блока есть машинное имя, которое прописывается в настройках блока при его добавлении в регион, запоминаем его. Дальше копируем файл core/modules/block/templates/block.html.twig в папку templates вашей темы. Переименовываем этот файл по шаблону block--machine-name.html.twig (где machine-name машинное имя нужного блока, нижние подчёркивания заменяются на тире). Сбрасываем кэш. Подробнее.

Как переопределить шаблон определённого поля в своей теме?

Скопировать файл core/modules/system/templates/field.html.twig в папку templates своей темы, переименовать файл по шаблону field--field-name.html.twig, где field-name это машинное имя поля, сбросить кэш. Подробнее, раздел "Fields".
Как переопределить шаблон page.html.twig для определённого типа материала?

1. Добавляем suggestion для шаблона page.html.twig:

function THEMENAME_theme_suggestions_page_alter(array &$suggestions, array $variables): void {
  $route_match = \Drupal::routeMatch();
  if ($route_match->getRouteName() == 'entity.node.canonical') {
    $suggestions[] = 'page__node__' . $route_match->getParameter('node')->bundle();
  }
}

2. Создаём шаблон page--node--NODETYPE.html.twig (например page--node--article.html.twig).

3. Сбрасываем кэш.

Как получить информацию о текущей теме?

$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

Подробнее.

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

Как в php получить отрендеренное поле сущности? Какой аналог field_view_field()?

$node = \Drupal\node\Entity\Node::load(123);

// Render-array поля, прошедшего через field.html.twig
$field_example_build = $node->get('field_example')->view('full');
// Render-array первого значения поля, прошедшего через форматтер
$field_example_build = $node->get('field_example')[0]->view('full');
Как в field.html.twig получить доступ к объекту родительской сущности поля?

Объект сущности лежит в переменной element['#object']. Пример вывода заголовка сущности:

{{ element['#object'].title.value }}
Как в field.html.twig получить сырое значение поля?

{% for delta, item in items %}
  {% set real_item = element['#items'][delta] %}
  Raw item value: {{ real_item.value }}<br />
{% endfor %}
Как в php получить отрендеренный материал? Какой аналог node_view()?

Пример рендеринга тизера ноды 123:

$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(): array {
  return [
    'my_template' => [
      'variables' => [
        'my_variable' => NULL,
      ],
    ],
  ];
}

templates/my-template.html.twig:

<div class="my-template">{{ my_variable }}</div>

Подробнее

Как изменить/добавить переменную в шаблон?

Для изменения/добавления переменных в шаблон надо реализовать хук hook_preprocess_TEMPLATE(), где вместо TEMPLATE подставить название шаблона. Пример добавления в шаблон views-view.html.twig переменной с количеством результатов на текущей странице:

// THEMENAME.theme

/**
 * Preprocess function for views-view.html.twig.
 */
function THEMENAME_preprocess_views_view(array &$variables): void {
  $variables['result_count'] = count($vars['view']->result);
}

Вместо THEMENAME пишем машинное имя темы или модуля, сбрасываем кэш. Подробнее

Как передать в twig шаблон переменную с html разметкой и чтобы она не экранировалась?

В друпале по умолчанию включён twig autoescape, поэтому все выводимые переменные проходят через htmlspecialchars(). Чтобы этого не происходило переменная должна иметь тип MarkupInterface:

$variables['my_html_var'] = \Drupal\Core\Render\Markup::create('<b>Hello</b> <i>World</i>');
Как темизировать форму? Как переопределить шаблон формы?

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

1. В THEMENAME.theme добавляем:

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

2. Создаём файл templates/example-form.html.twig с нужной разметкой (в имени файла подчёркивания заменяются на тире):

<div class="container">
  <div class="row">
    <div class="col-sm">{{ form.element1 }}</div>
    <div class="col-sm">{{ form.element2 }}</div>
  </div>
</div>
{{ form|without('element1', 'element2') }}

Последняя строчка, с рендерингом служебной информации о форме, обязательна!

3. Сбрасываем кэш.

Подробнее

Как отрендерить render-array?

В большинстве случаев этого делать не надо, потому что twig самостоятельно рендерит рендер-массивы, но если всё же понадобилось, то всё просто:

$build = [
  '#theme' => 'status_messages',
  '#message_list' => [
    'warning' => ['Warning!'],
  ],
];
$html = \Drupal::service('renderer')->render($build);
// Или
$html = \Drupal::service('renderer')->renderRoot($build);
// Или
$html = \Drupal::service('renderer')->renderPlain($build);
Как инвалидировать рендер-кэш с определённым тэгом?

\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...',
];
Как создать псевдо-поле/extra-field?

Пример вывода автора материала для нод типа article:

use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\node\NodeInterface;

/**
 * Implements hook_entity_extra_field_info().
 */
function MODULENAME_entity_extra_field_info(): array {
  $extra_fields['node']['article']['display']['author'] = [
    'label' => t('Author name'),
    'weight' => 0,
    'visible' => FALSE,
  ];

  return $extra_fields;
}
 
/**
 * Implements hook_ENTITY_TYPE_view(): node.
 */
function MODULENAME_node_view(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, string $view_mode): void {
  if ($display->getComponent('author')) {
    $build['author'] = ['#markup' => $node->getOwner()->getDisplayName()];
  }
}

Подробнее

Как в пунктах меню разрешить использовать html?

// THEMENAME.theme

function THEMENAME_preprocess_menu(array &$vars): void {
  THEMENAME_preprocess_menu_items($vars['items']);
}

function THEMENAME_preprocess_menu_items(array &$items): void {
  foreach ($items as &$item) {
    $item['title'] = Markup::create($item['title']);

    if ($item['below']) {
      THEMENAME_preprocess_menu_items($item['below']);
    }
  }
}
Как добавить css-класс определённому меню?

Пример добавления меню main класса dragscroll:

function THEMENAME_preprocess_menu__main(array &$vars): void {
  $vars['attributes']['class'][] = 'dragscroll';
}
Как добавить css-класс определённому блоку?

Пример добавления классов блоку с идентификатором main_menu:

function THEMENAME_preprocess_block__main_menu(array &$vars): void {
  $vars['attributes']['class'][] = 'my-block-class';
  $vars['content_attributes']['class'][] = 'my-content-class';
}
Как программно добавить css-класс в body?

function THEMENAME_preprocess_html(array &$vars): void {
  $vars['attributes']['class'][] = 'my-body-class';
}
Как с помощью item_list вывести древовидный список?

Каждый элемент в item_list может быть рендер массивом, поэтому просто вместо строки передаём такой же массив с '#theme' => 'item_list':

$build = [
  '#theme' => 'item_list',
  '#items' => [
    1 => 'Item 1',
    2 => [
      'value' => [
        '#markup' => 'Item 2',
      ],
      'below' => [
        '#theme' => 'item_list',
        '#items' => [
          1 => 'Item 2.1',
          2 => 'Item 2.2',
        ],
      ],
      '#wrapper_attributes' => [
        'class' => ['open'],
      ],
    ],
    3 => 'Item 3',
  ],
];

На выходе будет:

<ul>
  <li>Item 1</li>
  <li class="open">
    Item 2
    <ul>
      <li>Item 2.1</li>
      <li>Item 2.2</li>
    </ul>
  </li>
  <li>Item 3</li>
</ul>
Как в item_list добавить класс каждому li?

$build = [
  '#theme' => 'item_list',
  '#items' => [
    0 => [
      'item' => ['#markup' => 'Item 1'],
      '#wrapper_attributes' => ['class' => ['item-1-class']],
    ],
    1 => [
      'item' => ['#markup' => 'Item 2'],
      '#wrapper_attributes' => ['class' => ['item-2-class']],
    ],
  ],
];

На выходе будет:

<ul>
  <li class="item-1-class">Item 1</li>
  <li class="item-2-class">Item 2</li>
</ul>
Как из ноды убрать ссылку ответа на комментарий?

Как изменить результат возвращаемый контроллером? Какой аналог hook_page_alter()? Как убрать вывод комментируемой сущности на странице добавления комментария?

Пример скрытия комментируемой сущности на странице добавления комментария:

// src/ModulenameEventSubscriber.php

/**
 * Event Subscriber MyEventSubscriber.
 */
class ModulenameEventSubscriber implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    $events[KernelEvents::VIEW][] = ['onKernelView', 10];
    return $events;
  }

  /**
   * KernelEvents::VIEW event callback.
   */
  public function onKernelView(ViewEvent $event): void {
    if (\Drupal::routeMatch()->getRouteName() == 'comment.reply') {
      $result = $event->getControllerResult();
      $result['commented_entity']['#access'] = FALSE;
      $event->setControllerResult($result);
    }
  }

}
# modulename.services.yml

services:
  modulename.event_subscriber:
    class: Drupal\modulename\ModulenameEventSubscriber
    tags:
      - { name: event_subscriber }
Как сделать плавное появление jQuery UI Dialog?

@keyframes dialog-animation {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.ui-widget-overlay,
.ui-dialog {
  animation-duration: 0.3s;
  animation-name: dialog-animation;
}
Как превратить массив с html-атрибутами в строку вида foo="bar"?

$attributes_array = [
  'id' => 'cat',
  'class' => ['cat', 'cat--white'],
  'data-cat-name' => 'Kittie',
];
$attributes_object = new \Drupal\Core\Template\Attribute($attributes_array);
echo '<div' . $attributes . '>Cat</div>';

Результат:

<div id="cat" class="cat cat--white" data-cat-name="Kittie">Cat</div>
Как создать html ссылку? Какой аналог l()?

Как создать html ссылку без адреса, но с анкором (href="#anchor")?

Как отсортировать элементы рендер-массива по свойству #weight?

$build = [
  'foo' => [
    '#weight' => 2,
  ],
  'bar' => [
    '#weight' => 1,
  ],
];
uasort($build, '\Drupal\Component\Utility\SortArray::sortByWeightProperty');

В результате в $build будет:

[
  'bar' => [
    '#weight' => 1,
  ],
  'foo' => [
    '#weight' => 2,
  ],
]

Работа с Twig

Как пройтись по элементам массива?

{% for value in array %}
  {{ value }}
{% endfor %}

{% for key, value in array %}
  {{ key }}: {{ value }}
{% endfor %}

{% for key in array|keys %}
  {{ key }}
{% endfor %}

Подробнее

Как проверить, что массив не пустой?

{% if my_array is not empty %}
  ...
{% endif %}

или

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

Подробнее про empty и length.

Как проверить, что в массиве есть элемент с определённым ключом? Какой аналог php функций isset() или array_key_exists()?

{% if (example_array.foo is defined) %}
  {{ example_array.foo }}
{% endif %}

{% if (example_array.foo is not defined) %}
  Empty message...
{% endif %}
Как добавить значение в массив?

{% set arr = ['foo', 'bar'] %}
Добавляем в массив arr значение 'baz':
{% set arr = arr|merge(['baz']) %} 

В arr будет ['foo', 'bar', 'baz']

Как присвоить значение элементу массива?

{% set arr = {'foo': 'bar', 'baz': 'bat'} %}
Меняем значение arr.baz на 'new':
{% set arr = arr|merge({'baz': 'new'}) %} 
Как проверить, что в строке есть определённый текст? Какой аналог php функции str_contains()?

{% if 'Hello' in str %}
  ...
{% endif %}

Замечание — если в str будет объект, условие будет всегда возвращать false, потому что метод __toString() не вызывается.

Как обрезать текст?

{{ 'English'|slice(0, 3) }}
{{ 'English'|truncate(3) }}

Выведет две строки Eng

Как проверить, есть ли у поля сущности значение?

node.html.twig:

{% if not node.field_example.isEmpty() %}
   field_example is not empty
{% endif %}

или так:

{% if content.field_example[0] %}
   field_example is not empty
{% endif %}
Как узнать, сколько у поля сущности значений?

node.html.twig:

{% if node.field_example|length > 1 %}
   Field items count more 1
{% endif %}
Как в шаблоне ноды/сущности вывести отдельные поля сущности?

Отформатированное значение поля, прошедшее через field.html.twig:

{{ content.field_example }}

Отформатированные значения поля без использования field.html.twig:

{{ content.field_example|without('#theme') }}

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

{{ content.field_example[0] }}

Сырое значение поля:

{{ node.field_example.value }}

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

Подробнее

Как в node.html.twig получить сырое значение поля ноды?

Node ID: {{ node.id }}
Node title: {{ node.label }}
String field: {{ node.field_string.value }}
Reference entity label: {{ node.field_reference.entity.label }}
Как в node.html.twig вывести url файла из file reference поля ноды? Как вывести адрес изображения из поля ноды?

{% if not node.field_image.isEmpty() %}
  <img src="{{ file_url(node.field_image.entity.getFileUri()) }}" />
{% endif %}

Или:

{% if not node.field_image.isEmpty() %}
  <img src="{{ file_url(node.field_image.entity.uri.value) }}" />
{% endif %}
Как вывести адрес изображения со стилем?

{% if not node.field_image.isEmpty() %}
  <img src="{{ node.field_image.entity.getFileUri()|image_style('thumbnail') }}" />
{% endif %}

Или так:

{{ drupal_image(node.field_image.entity.getFileUri(), 'thumbnail') }}

Нужен модуль Twig Tweak.

Как воспользоваться функцией Element::children()? Как обойти вложенные элементы render-массива?

{% for key, element in elements if key|first != '#' %}
  {{ element }}
{% endfor %}

Или так, если стоит модуль Twig Tweak:

{% for element in elements|children %}
  {{ element }}
{% endfor %}
Как вывести url сущности?

Пример генерации адреса для ноды 123:

{{ path('entity.node.canonical', {'node': 123}) }}

Подробнее

Как в шаблоне перевести фразу? Какой аналог t()?

{{ 'Home'|t }}
{{ 'Order'|t({}, {'context': 'Commerce'}) }}
Как склонять слова с числами?

{% set count = 123 %}
{% trans %}
  {{ count }} review
{% plural count %}
  {{ count }} reviews
{% endtrans %}
Как проверить залогинён ли текущий пользователь?

Во всех шаблонах есть переменная logged_in:

{% if logged_in %}
  Current user is authenticated
{% else %}
  Current user is anonymouse
{% endif %}
Как в шаблоне проверить права текущего пользователя?

Во всех шаблонах есть переменная user типа AccountProxyInterface:

{% if user.hasPermission('administer comments') %}
  Current user has permission "administer comments"
{% endif %}
Как в шаблоне вывести представление Views?

Если установлен модуль Twig Tweak:

{{ drupal_view('my_view', 'my_display_name') }}

Иначе:

{{ {'#type': 'view', '#name': 'my_view', '#display_id': 'my_display_name'} }}
Как присвоить переменной значение?

{% set foo = 'bar' %}
{{ foo }}

Выведет bar

Как в php отрендерить инлайновый twig шаблон?

$build = [
  '#type' => 'inline_template',
  '#template' => '<div>{{ message }}</div>',
  '#context' => [
    'message' => 'Hello world',
  ],
];
$output = \Drupal::service('renderer')->renderRoot($build);

В результате $output будет содержать:

<div>Hello world</div>
Как вынести часть шаблона в другой twig файл?

Пример выноса шапки из page.html.twig.

1. /themes/mytheme/templates/page.html.twig:

<div class="layout__header">
  {% include '@mytheme/page-header.html.twig' %}
</div>
<div class="layout__content">
  {{ page.content }}
</div>
<div class="layout__footer">
  {{ page.footer }}
</div>

2. /themes/mytheme/templates/page-header.html.twig:

<a class="site-logo" href="/"></a>
{{ page.header }}

В подключаемом файле будут доступны все переменные из основного файла. Важно, что в {% include '@mytheme/...' %} надо указывать путь к файлу относительно папки templates, т.е. если файл лежит в /themes/custom/mytheme/templates/includes/template.html.twig, то подключать надо так: {% include '@mytheme/includes/template.html.twig' %}.

Как в шаблоне вывести отрендеренное поле произвольной сущности?

Если есть объект сущности: {{ entity.field_name|view('teaser') }}
Если есть только id сущности: {{ drupal_field('field_name', 'node', 123, 'teaser') }}

Подробнее про фильтр view. Подробнее про функцию drupal_field()

Как в шаблоне вывести значение токена?

{{ drupal_token('site:name') }}

Необходим модуль twig_tweak.

Как в своей теме создать twig-функцию?

По умолчанию в темах нельзя создавать twig-функции, однако это позволяет делать модуль twig_tweak:

// THEMENAME.theme

/**
 * Implements hook_twig_tweak_functions_alter().
 */
function THEMENAME_twig_tweak_functions_alter(array &$functions): void {
  $functions[] = new TwigFunction('registration_link', 'THEMENAME_registration_link');
}

/**
 * Return registration link build.
 */
function THEMENAME_registration_link(string $text = 'Registration', string $class = 'form-button'): array {
  return [
    '#type' => 'link',
    '#title' => $text,
    '#url' => Url::fromRoute('user.register'),
    '#attributes' => [
      'class' => $class,
    ],
  ];
}

Использование:

{{ registration_link() }}

Работа с формами (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(array &$form, FormStateInterface $form_state): void {
  $form['terms_of_use'] = [
    '#type' => 'checkbox',
    '#title' => t('I agree with terms and conditions.'),
    '#required' => TRUE,
  ];
}

Подробнее

Как изменить раскрытую форму Views?

// MODULENAME.module

use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewExecutable;

/**
 * Implements hook_form_FORM_ID_alter(): views_exposed_form.
 */
function MODULENAME_form_views_exposed_form_alter(array &$form, FormStateInterface $form_state): void {
  $view = $form_state->get('view'); /** @var ViewExecutable $view */
  if ($view->id() == 'example_views' && $view->current_display == 'example_display') {
    ...
  }
}
Как создать свою форму?

src/Form/ExampleForm.php:

namespace Drupal\modulename\Form;

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

class ExampleForm extends FormBase {

  /**
   * {@inheritDoc}
   */
  public function getFormId(): string {
    return 'example_form';
  }

  /**
   * {@inheritDoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array  {
    $form['example_text'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Text'),
    ];
    
    $form['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    ];
    
    return $form;
  }

  /**
   * {@inheritDoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $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(array $form) {
  unset($form['form_id']);
  unset($form['form_build_id']);
  unset($form['form_token']);
  unset($form['submit']['#name']);

  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) {
  $form[$key]['#attributes']['class'][] = 'form-item';
}

Подробнее

Как в submitForm() получить параметры, переданные в buildForm()?

class ExampleForm extends FormBase {

  public function buildForm(array $form, FormStateInterface $form_state, NodeInterface $node = NULL): array {
    ...
  }

  public function submitForm(array &$form, FormStateInterface $form_state): void {
    /** @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): void {
  ...
  $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' => [...],
  // Показываем селект dependent_select только когда в example_select выбрано "Show" (т.е. 1)
  '#states' => [
    'visible' => [
      ':input[name="example_select"]' => ['value' => 1],
    ],
  ],
];

Пример с чекбоксом:

$form['make_link'] = array(
  '#type' => 'checkbox',
  '#title' => 'Сделать ссылкой',
);

$form['in_new_window'] = array(
  '#type' => 'checkbox',
  '#title' => 'Открывать в новом окне',
  // Показывать чекбокс in_new_window только когда отмечен чекбокс make_link
  '#states' => [
    'visible' => [
      ':input[name="make_link"]' => [
        'checked' => TRUE,
      ],
    ],
  ],
);

Подробнее

Как в #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',
      ],
    ];

    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',
        '#attached' => [
          'library' => ['core/drupal.dialog.ajax'],
        ],
      ]));
    }
    else {
      ...
    }

    return $response;
  }

}
Как с помощью ajax команды добавить или удалить html класс?

$response->addCommand(new InvokeCommand('.my-element', 'addClass', ['my-new-class']));
$response->addCommand(new InvokeCommand('.my-element', 'removeClass', ['my-old-class']));
Как с помощью ajax команды тригернуть javascript событие?

Пример эмуляции клика на элементе .my-element:

$response->addCommand(new InvokeCommand('.my-element', 'trigger', ['click']));
Как разрешить отправлять форму не чаще одного раза в час?

public function validateForm(array &$form, FormStateInterface $form_state): void {
  if (!\Drupal::flood()->isAllowed('example_form', 1)) {
    $form_state->setErrorByName('', $this->t('You cannot send more.'));
  }
}

public function submitForm(array &$form, FormStateInterface $form_state): void {
  ...
  \Drupal::flood()->register('example_form');
}
Как в submitForm() получить значение элемента при #tree=>true?

public function buildForm(array $form, FormStateInterface $form_state): array {
  $form['foo'] = [
    '#type' => 'textfield',
  ];
  $form['bar'] = [
    '#tree' => TRUE,
  ];
  $form['bar']['baz'] = [
    '#type' => 'textfield',
  ];
  return $form;
}

public function submitForm(array &$form, FormStateInterface $form_state): void {
  $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): array {
    $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): void {
    foreach ($form_state->getValue('nodes') as $nid => $checked) {
      if ($checked) {
        Node::load($nid)->delete();
      }
    }
  }

}

Подробнее

Как в submitForm() получить только отмеченные чекбоксы у элемента #type=>checkboxes?

public function buildForm(array $form, FormStateInterface $form_state): array {
  $form['example_checkboxes'] = [
    '#type' => 'checkboxes',
    '#title' => 'Example checkboxes',
    '#options' => [
      'foo' => 'Foo',
      'bar' => 'Bar',
      'baz' => 'Baz',
    ],
  ];

  return $form;
}

public function submitForm(array &$form, FormStateInterface $form_state): void {
  $checked_checkboxes = \Drupal\Core\Render\Element\Checkboxes::getCheckedCheckboxes($form_state->getValue('example_checkboxes'));
  // Если были отмечены Foo и Baz, то в $checked_checkboxes будет ['foo', 'baz']
}
Как указать html атрибут конкретному чекбоксу в элементе '#type'=>'checkboxes'?

$form['example_checkboxes'] = [
  '#type' => 'checkboxes',
  '#title' => 'Example checkboxes',
  '#options' => [
    'foo' => 'Foo',
    'bar' => 'Bar',
  ],
  'foo' => [
    '#attributes' => [
      'data-dummy' => 123,
    ],
  ],
];

На выходе будет:

<label><input type="checkbox" value="example_checkboxes[foo]" data-dummy="123" /> Foo</label>
<label><input type="checkbox" value="example_checkboxes[bar]" /> Bar</label>
Как заблокировать определённый чекбокс в элементе '#type'=>'checkboxes'?

$form['example_checkboxes'] = [
  '#type' => 'checkboxes',
  '#title' => 'Example checkboxes',
  '#options' => [
    'foo' => 'Foo',
    'bar' => 'Bar',
  ],
  'foo' => [
    '#disabled' => TRUE,
  ],
];

На выходе будет:

<label><input type="checkbox" value="example_checkboxes[foo]" disabled /> Foo</label>
<label><input type="checkbox" value="example_checkboxes[bar]" /> Bar</label>
Как вывести элемент формы без обёртки с классом form-item?

$form['element'] = [
  '#type' => 'textfield',
  '#title' => 'Element',
  '#theme_wrappers' => [], // <--
];

Замечание - после удаления обёртки не будут работать некоторые js функции, например #states.

Как загрузить файл на сервер без сохранения его в таблице file_managed?

public function buildForm(array $form, FormStateInterface $form_state): array {
  $form['example_file'] = [
    '#type' => 'file',
    '#title' => t('File'),
  ];
  ...
}

public function submitForm(array &$form, FormStateInterface $form_state): void {
  /** @var \Drupal\Core\File\FileSystemInterface $file_system */
  $file_system = \Drupal::service('file_system');
  /** @var \Symfony\Component\HttpFoundation\File\UploadedFile[] $files */
  $files = \Drupal::request()->files->get('files', []);
  if ($files['example_file']) {
    $file_source = $files['example_file']->getRealPath();
    $file_destination = 'public://example-file.' . $files['example_file']->getExtension();
    $file_system->move($file_source, $file_destination);
  }
}
Как сделать редирект из buildForm()?

public function buildForm(array $form, FormStateInterface $form_state) {
  if (...) {
    return $this->redirect('<front>');
  }
}
Как сделать редирект из submitForm()?

public function submitForm(array $form, FormStateInterface $form_state) {
  $form_state->setRedirect('<front>');
  
  // Или
  $redirect_url = Url::fromRoute('<front>');
  $form_state->setRedirectUrl($redirect_url);
}

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

Как выполнить sql запрос?

Пример выборки нод:

$result = \Drupal::database()
  ->select('node', 'n')
  ->fields('n', ['nid', 'title'])
  ->condition('n.type', 'article')
  ->orderBy('n.nid')
  ->execute();

Подробнее

Как пройтись по результатам запроса?

$result = \Drupal::database()->select('node')->execute();
foreach ($result as $row) {
  $nid = $row->nid;
}

// Или так
$rows = \Drupal::database()->select('node')->execute()->fetchAll();
foreach ($rows as $row) {
  $nid = $row->nid;
}
Как добавить в запрос несколько условий с оператором OR?

$query = \Drupal::database()->select(...);
...
$or_conditions = $query->orConditionGroup();
$or_conditions->condition('status', 0);
$or_conditions->condition('status', 1);
$query->condition($or_conditions);
$query->condition('type', 'page');

Получим условие:

WHERE (status = 0 OR status = 1) AND type = 'page'

Подробнее

Как использовать условие с оператором IN?

$result = \Drupal::database()->select('node', 'n')
  ->fields('n')
  ->condition('n.type', ['page', 'article'], 'IN') // <--
  ->execute();

Подробнее

Как в запросе проверить значение на NULL или NOT NULL?

$query->isNull('field_name');
$query->isNotNull('field_name');

// или
$query->condition('field_name', NULL, 'IS NULL');
$query->condition('field_name', NULL, 'IS NOT NULL');
Как подсчитать число записей с помощью COUNT(*)?

$count = \Drupal::database()
  ->select('node')
  ->condition('type', 'page')
  ->countQuery() // <--
  ->execute()
  ->fetchField();

// Или так
$query = \Drupal::database()->select('node');
$query->addExpression('COUNT(*)'); // <--
$query->condition('type', 'page');
$count = $query->execute()->fetchField();

Подробнее. Замечание — countQuery() не работает в подзапросах.

Как получить максимальное/минимальное значение с помощью 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
)
Как в условии использовать оператор LIKE?

$query->condition('title', '%' . \Drupal::database()->escapeLike('world') . '%', 'LIKE');
Как добавить в FROM подзапрос?

$query = \Drupal::database()
  ->select('node', 'n')
  ->fields('n', ['nid', 'title'])
  ->condition('n.type', 'article');

$subquery = \Drupal::database()
  ->select('taxonomy_index', 'ti')
  ->addExpression('COUNT(*)');
  ->where('ti.nid = n.nid');
$query->addExpression('(' . $subquery . ')', 'nodes_count');

$result = $query->execute()->fetchAll();

Получившийся SQL запрос:

SELECT n.nid, n.title, (
  SELECT COUNT(*)
  FROM taxonomy_index ti
  WHERE ti.nid = n.nid
) AS nodes_count
WHERE n.type = 'article'

Замечание — в $subquery нельзя пользоваться аргументами, т.е. вместо $subquery->condition('field', $value) надо писать $subquery->where("field = '$value'")

Как присоединить таблицу с помощью INNER JOIN/LEFT JOIN?

// Пример 1, когда вы сами придумываете синоним присоединяемой таблицы
$query = \Drupal::database()->select('node_field_data', 'n');
$query->fields('n', ['nid']);
$query->fields('u', ['name']);
$query->innerJoin('users_field_data', 'u', 'u.uid = n.uid');
$result = $query->execute();

// Пример 2, когда синоним присоединяемой таблицы генерируется автоматически
$query = \Drupal::database()->select('node_field_data', 'n');
$query->fields('n', ['nid']);
$users_table_alias = $query->innerJoin('users_field_data', NULL, '%alias.uid = n.uid');
$query->fields($users_table_alias, ['name']);
$result = $query->execute();

Важно помнить, что методы innerJoin() и leftJoin() возвращают синоним присоединённой таблицы, а не объект SelectInterface, поэтому их нельзя использовать в цепочках вызовов.

Как добавить поле с GROUP_CONCAT?

$query = \Drupal::database()->select('node', 'n');
$query->fields('n', ['nid']);

$query->leftJoin('node__field_tags', 'ft', 'ft.entity_id = n.nid');
$query->addExpression("GROUP_CONCAT(ft.field_tags_target_id)", 'tags');
$query->groupBy('n.nid');

$result = $query->execute()->fetchAll();

Результат:

0 => [
  'nid' => 1,
  'tags' => '2,3',
],
1 => [
  'nid' => 2,
  'tags' => '3,4,7',
],
2 => [
  'nid' => 3,
  'tags' => '1',
]
Как добавить подзапрос с GROUP_CONCAT?

$query = \Drupal::database()->select('node', 'n')
  ->fields('n', ['nid']);

$subquery = \Drupal::database()->select('node__field_tags', 'ft');
$subquery->addExpression("GROUP_CONCAT(ft.field_tags_value)");
$subquery->where('ft.entity_id = n.nid');

$query->addExpression('(' . $subquery . ')', 'tags');
$result = $query->execute()->fetchAll();

Результат будет содержать:

0 => [
  'nid' => 1,
  'tags' => 'foo,bar',
],
1 => [
  'nid' => 2,
  'tags' => 'baz',
],
2 => [
  'nid' => 3,
  'tags' => 'foo,baz',
]
Как посмотреть получившийся SQL запрос?

Перед ->execute() вызываем:

$sql = $query->__toString();

Или так, если надо посмотреть запрос с уже подставленными параметрами:

$query->addTag('debug');
Как очистить таблицу? Какой аналог db_truncate()?

\Drupal::database()->truncate('flood')->execute();
Как сделать дамп/бэкап базы данных?

Создание дампа базы в файл database.sql в корне друпала:

vendor/bin/drush sql-dump --result-file=database.sql --structure-tables-list=batch,cache_*,cachetags,flood,queue,sessions,watchdog

В опции --structure-tables-list перечисляются таблицы, данные которых экспортировать не надо

Как восстановить базу из дампа/бэкапа?

Восстановление базы из файла database.sql, находящегося в корне друпала:

vendor/bin/drush sql-drop
vendor/bin/drush sql-cli < database.sql

Если не работает, то так:

vendor/bin/drush sql-drop
cat database.sql | vendor/bin/drush sql-cli

Или даже так:

vendor/bin/drush sql-drop
mysql --user=USERNAME --password=PASSWORD DATABASENAME < database.sql
Как получить название таблицы БД, в которой хранятся данные определённого типа сущностей?

// Способ 1
$node_data_table = \Drupal::entityTypeManager()->getStorage('node')->getDataTable();

// Способ 2
$node_data_table = \Drupal::entityTypeManager()->getDefinition('node')->getDataTable();

// Способ 3
/** @var NodeStorage $node_storage */
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
/** @var DefaultTableMapping $node_table_mapping */
$node_table_mapping = $node_storage->getTableMapping();
$node_data_table = $node_table_mapping->getDataTable();
Как получить название таблицы БД, в которой хранятся значения определённого поля?

/** @var NodeStorage $node_storage */
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
/** @var DefaultTableMapping $node_table_mapping */
$node_table_mapping = $node_storage->getTableMapping();
$field_example_table = $node_table_mapping->getFieldTableName('field_example');
Как сделать запрос в другую базу данных?

1. Добавляем в settings.php информацию о второй БД:

$databases['wordpress']['default'] = array (
  'database' => 'wordpress',
  'username' => 'root',
  'password' => 'root',
  'prefix' => '',
  'host' => 'localhost',
  'port' => '',
  'isolation_level' => 'READ COMMITTED',
  'namespace' => 'Drupal\\mysql\\Driver\\Database\\mysql',
  'driver' => 'mysql',
  'autoload' => 'core/modules/mysql/src/Driver/Database/mysql/',
);

2. Делаем запрос:

$wp_database = \Drupal\Core\Database\Database::getConnection('default', 'wordpress');

$query = $wp_database->select('bl_posts', 'p');
$query->addField('p', 'ID', 'id');
$query->addField('p', 'post_date', 'date');
$query->addField('p', 'post_title', 'title');
$query->addField('p', 'post_content', 'content');
$query->condition('p.post_type', 'post');
$query->orderBy('p.ID');

dsm($query->execute()->fetchAll());

Entity Query API

Как получить сущности по значениям полей? Как пользоваться сервисом entity.query?

Пример получения опубликованных нод типа article со значением поля field_foo равным bar:

$nids = \Drupal::entityQuery('node');
  ->condition('type', 'article');
  ->condition('status', \Drupal\node\NodeInterface::PUBLISHED);
  ->condition('field_foo', 'bar')
  ->accessCheck(FALSE)
  ->execute();
$nodes = \Drupal\node\Entity\Node::loadMultiple($nids);

Подробнее

Как получить объект одной сущности по значению её полей?

$nids = \Drupal::entityQuery('node');
  ->condition('type', 'article');
  ->condition('status', \Drupal\node\NodeInterface::PUBLISHED);
  ->condition('field_example1', 'foo')
  ->condition('field_example2', 'bar')
  ->range(0, 1) // <--
  ->accessCheck(FALSE)
  ->execute();
if ($nids) {
  $node = \Drupal\node\Entity\Node::load(current($nids));
}
Как добавить несколько условий с логическим оператором ИЛИ?

$query = \Drupal::entityQuery('node');
// "node.uid=1 OR node.sticky=1"
$or_group = $query->orConditionGroup()
  ->condition('uid', 1)
  ->condition('sticky', 1);
$query->condition($or_group);
Как добавить условие - "поле имеет значение foo ИЛИ bar"?

$nids = \Drupal::entityQuery('node')
  ->condition('field_example', ['foo', 'bar'], 'IN')
  ->accessCheck(FALSE)
  ->execute();
Как добавить условие - "многозначное поле имеет значения foo И bar"?

Пример получения нод с полем field_example содержащим как минимум значения foo и bar, т.е. значений у поля ноды может быть больше, но обязательно наличие этих двух:

$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->accessCheck(FALSE)->execute();

SQL запрос будет выглядеть как-то так:

SELECT vid, nid
FROM node
INNER JOIN node__field_example AS field_example_1 ON field_example_1.entity_id = node.nid
INNER JOIN node__field_example AS field_example_2 ON field_example_2.entity_id = node.nid
WHERE
  field_example_1.field_example_value = 'foo' AND
  field_example_2.field_example_value = 'bar'

Если надо получить ноды у которых field_example равно foo И bar и не содержит других значений:

$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'));
$node_query->notExists('field_example.2.value');
$nids = $node_query->accessCheck(FALSE)->execute();

В field_example.2.value вместо 2 подставляем число значений, а вместо value название свойства в котором хранится значение (main property name). Запрос получится:

SELECT vid, nid
FROM node
INNER JOIN node__field_example AS field_example_1 ON field_example_1.entity_id = node.nid
INNER JOIN node__field_example AS field_example_2 ON field_example_2.entity_id = node.nid
LEFT JOIN node__field_example AS field_example_3 ON field_example_3.entity_id = node.nid AND field_example_3.delta = '2'
WHERE
  field_example_1.field_example_value = 'foo' AND
  field_example_2.field_example_value = 'bar' AND
  field_example_3.field_example_value IS NULL
Как добавить условие - "поле не имеет значений" или наоборот "поле имеет хотя бы одно значение"?

// Условие "поле field_example не должно иметь значений"
$entity_query->notExists('field_example');
// Или
$entity_query->condition('field_example', NULL, 'IS NULL');

// Обратное условие "поле field_example должно иметь хотя бы одно значение"
$entity_query->exists('field_example');
// Или
$entity_query->condition('field_example', NULL, 'IS NOT NULL');
Как добавить условие - "многозначное поле имеет не больше одного значения"?

$node_query->notExists('field_example.1.value');

В зависимости от типа поля, вместо value может быть что-то другое, например target_id для поля типа entity reference.

Как добавить условие - "поле типа date меньше текущей даты"?

// Если в поле хранится только дата
$current_date = new \Drupal\Core\Datetime\DrupalDateTime();
$query->condition('field_date', $current_date->format(\Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface::DATE_STORAGE_FORMAT), '<');

// Если в поле хранится дата и время
$current_datetime = new \Drupal\Core\Datetime\DrupalDateTime();
$query->condition('field_date', $current_date->format(\Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface::DATETIME_STORAGE_FORMAT), '<');

Сложность в том, что в базе значения поля типа date хранятся в виде строк, поэтому сравнивать надо даты в одном формате — если это только дата, то Y-m-d, если дата с временем, то Y-m-d\TH:i:s.

Как добавить условие по значению поля дочерней сущности?

Пример поиска сущностей у которых термин из поля field_category имеет значение поля status равным 1:

$query->condition('field_category.entity.status', 1);
Как посчитать число сущностей с помощью COUNT(*)?

$news_count = \Drupal::entityQuery('node')
  ->condition('type', 'news')
  ->count() // <--
  ->accessCheck(FALSE)
  ->execute();
Как посмотреть sql запрос, сгенерированный сервисом entity.query?

Вариант 1:

Перед выполнением execute() добавить к объекту запроса тэг debug:

$node_query = \Drupal::entityQuery('node');
...
$node_query->addTag('debug'); // <---
$result = $node_query->execute();

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

Вариант 2:

$node_query = \Drupal::entityQuery('node')->condition('type', 'article');
debug($node_query->__toString());
Как добавить сортировку? Как сделать ORDER BY?

$result = \Drupal::entityQuery('node')
  ->sort('nid', 'DESC')
  ->sort('title', 'DESC')
  ->accessCheck(FALSE)
  ->execute();
Как выбрать определённое количество сущностей? Как сделать LIMIT?

$result = \Drupal::entityQuery('node')
  ->range(0, 10) // <--
  ->accessCheck(FALSE)
  ->execute();
Как получить идентификатор последнего добавленного материала?

$result = \Drupal::entityQuery('node')
  ->sort('nid', 'DESC')
  ->range(0, 1)
  ->accessCheck(FALSE)
  ->execute();
$last_nid = $result ? current($result) : NULL;

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

Как из админки создать пункт меню без ссылки?

Как программно добавить свою ссылку в меню?

Как программно добавить свою ссылку на страницу admin/config?

modulename.links.menu.yml:

modulename.settings:
  title: 'Modulename settings'
  description: 'Settings for modulename.'
  route_name: modulename.settings
  parent: system.admin_config_system

При этом роут 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()?

// Системный адрес без $_GET параметров. Аналог current_path().
$current_system_path = \Drupal::service('path.current')->getPath(); // Вернёт например "/node/123"

// Синоним адреса без $_GET параметров. Аналог request_path().
$current_system_path = \Drupal::service('path.current')->getPath();
$current_path_alias = \Drupal::service('path_alias.manager')->getAliasByPath($current_system_path); // Вернёт например "/article/my-example-article"

// Относительный путь из строки браузера, с GET параметрами. Аналог request_uri().
$current_request_uri = \Drupal::request()->getRequestUri(); // Вернёт например "/article/my-example-article?foo=bar"

// Абсолютный путь из строки браузера, с GET параметрами.
$current_uri = \Drupal::request()->getUri(); // Вернёт например "http://example.com/article/my-example-article?foo=bar"
Как получить адрес папки установки друпала?

$base_path = base_path();
Если друпал установлен в корень сайта, то base_path() вернёт /.
Если друпал установлен в папку /drupal/folder, то base_path() вернёт /drupal/folder/.
Как получить путь к папке определённого модуля?

$path = \Drupal::service('extension.path.resolver')->getPath('module', 'views_ui'); // core/modules/views_ui
Как получить название текущего роута?

$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 вида public://example.jpg?

$file_local_url = Drupal::service('file_url_generator')->generateString('public://example.jpg'); // Вернёт /sites/default/files/example.jpg
$file_absolute_url = Drupal::service('file_url_generator')->generateAbsoluteString('public://example.jpg'); // Вернёт http://example.com/sites/default/files/example.jpg
Как получить полный внутренний путь к файлу по его uri вида public://...?

$realpath = \Drupal::service('file_system')->realpath('public://image.jpg'); // Вернёт что-нибудь типа "/var/www/public_html/sites/default/files/image.jpg"
Как создать url с destination на текущую страницу?

$destination_query = \Drupal::destination()->getAsArray();
$url = \Drupal::urlGenerator()->generateFromRoute('example.route', [], ['query' => $destination_query]);
// /example/route?destination=/current/path
Как сделать редирект из контроллера?

class ExampleController extends ControllerBase {

  public function exampleAction() {
    return $this->redirect('<front>');
  }

}
Как получить синоним адреса по системному url?

$alias = \Drupal::service('path_alias.manager')->getAliasByPath('/node/123');
Как получить синоним адреса ноды?

$node_alias = $node->get('path')->alias; // "/example/node/path"
Какой шаблон прописать в pathauto для терминов древовидного словаря?

Чтобы получить адреса терминов вида /catalog/category1/category2/category3, нужно прописать терминам следующий шаблон:

catalog/[term:parents:join-path]/[term:name]
Как получить url адрес к картинке со стилем (image style)? Как применить стиль к изображению?

$image_uri = 'public://image.jpg';
$image_style = ImageStyle::load('thumbnail'); /** @var ImageStyleInterface $image_style */

$absolute_url = $image_style->buildUrl($image_uri); // http://example.com/sites/default/files/styles/thumbnail/public/image.jpg?itok=BN-lOMvj
$relative_url = \Drupal::service('file_url_generator')->transformRelative($absolute_url); // /sites/default/files/styles/thumbnail/public/image.jpg?itok=BN-lOMvj
Как получить домен/хост сайта?

$domain = \Drupal::request()->getHost(); // "www.example.com"
Как программно удалить все пункты определённого меню?

Пример удаления всех пунктов меню main:

\Drupal\Component\Utility\Environment::setTimeLimit(0);
$menu_items_ids = \Drupal::entityQuery('menu_link_content')
  ->condition('menu_name', 'main')
  ->execute();
foreach ($menu_items_ids as $menu_item_id) {
  $menu_item = \Drupal\menu_link_content\Entity\MenuLinkContent::load($menu_item_id);
  $menu_item->delete();
}

Почему не используется \Drupal::entityTypeManager()->getStorage('menu_link_content')->delete()? Потому что при удалении пункта, который имеет дочерние пункты, обновляются все его подпункты, что приводит к коллизии.

Работа с Views

Как для пустого Views возвращать http-статус 404?

На странице редактирования Views, в блоке No results behavior добавляем плагин Response status code с кодом 404.
Как в рантайме удалить фильтр Views?

Пример удаления фильтра status для пользователя с правом administer comments:

/**
 * Implements hook_views_pre_view().
 */
function MODULENAME_views_pre_view(ViewExecutable $view, string $display_id, array &$args): void {
  if ($view->id() == 'my_view_name') {
    if (\Drupal::currentUser()->hasPermission('administer comments')) {
      $view->removeHandler($view->current_display, 'filter', 'status');
    }
  }
}
Как в рантайме удалить блок в шапке или подвале Views?

/**
 * Implements hook_views_pre_view().
 */
function MODULENAME_views_pre_view(ViewExecutable $view, string $display_id, array &$args): void {
  if ($view->id() == 'my_view_name') {
    $view->removeHandler($view->current_display, 'header', 'my_header_id');
    $view->removeHandler($view->current_display, 'footer', 'my_footer_id');
  }
}
Как в рантайме изменить число записей на страницу в Views?

/**
 * Implements hook_views_pre_view().
 */
function MODULENAME_views_pre_view(ViewExecutable $view, string $display_id, array &$args): void {
  if ($view->id() == 'my_view_name') {
    $view->setItemsPerPage(10);
  }
}
Как в рантайме изменить тип пагинатора Views?

Пример изменения тип пагинатора на "Display all items":

/**
 * Implements hook_views_pre_view().
 */
function MODULENAME_views_pre_view(ViewExecutable $view, string $display_id, array &$args): void {
  if ($view->id() == 'my_view_name') {
    $view->display_handler->setOption('pager', ['type' => 'none']);
  }
}
Как программно изменить выводимое значение поля в Views?

Пример замены значения поля title в представлении content_recent

/**
 * Preprocess function for views-view-field--content-recent--title.html.twig.
 */
function THEMENAME_preprocess_views_view_field__content_recent__title(array &$vars): void {
  $vars['output'] = 'new field output';
  $vars['field']->last_render = $vars['output'];
}
Как в рантайме удалить некоторые строки из результата?

Пример удаления двух строк из представления myview и дисплея page:

// MODULENAME.module

use Drupal\views\ViewExecutable;

/**
 * Implements hook_views_post_execute().
 */
function MODULENAME_views_post_execute(ViewExecutable $view): void {
  if ($view->id() == 'myview' && $view->current_display == 'page') {
    unset($view->result[2]);
    unset($view->result[5]);
  }
}
Как в рантайме удалить определённое поле/колонку?

Пример удаления из представления myview поля edit_node для пользователей, которые не могут управлять материалами:

// MODULENAME.module

use Drupal\views\ViewExecutable;

/**
 * Implements hook_views_pre_render().
 */
function MODULENAME_views_pre_render(ViewExecutable $view): void {
  if ($view->id() == 'myview') {
    if (!\Drupal::currentUser()->hasPermission('administer nodes')) {
      $view->field['edit_node']->options['exclude'] = TRUE;
    }
  }
}
Как для Views добавить css-класс, содержащий число результатов на текущей странице?

Пример для представления с id example_view:

/**
 * Preprocess function for views-view--example-view.html.twig.
 */
function THEMENAME_preprocess_views_view__example_view(array &$vars): void {
  $view = $vars['view']; /** @var ViewExecutable $view */
  $vars['attributes']['class'][] = 'views--count-' . count($view->result);
}
Как программно вывести Views?

Способ 1 (без рендер-кеша):

$views_build = views_embed_view('my_view_name', 'my_view_display_name', 'my_argument');

Способ 2 (без рендер-кеша):

$views_build = [
  '#type' => 'view',
  '#name' => 'my_view_name',
  '#display_id' => 'my_view_display_name',
  '#arguments' => ['arg1', 'arg2'],
];

Способ 3 (с рендер-кешем):

$views_build = \Drupal\views\Plugin\views\display\Page::buildBasicRenderable('my_view_name', 'my_view_display_name', ['arg1', 'arg2'], \Drupal::routeMatch()->getRouteObject());

Способ 4 (без рендер-кеша):

$views = \Drupal\views\Views::getView('my_view_name'); /** @var \Drupal\views\ViewExecutable $views */
$views->setDisplay('my_view_display_name');
$views->setArguments(['my_argument']); // Optional
$views->preExecute();
$views->execute();
$views_build = $views->render();

Способ 5 (из twig файла) (без рендер-кеша):

{{ drupal_view('my_view_name', 'my_view_display_name', 'my_argument') }}

Способ 6 (из twig файла) (без рендер-кеша):

{{ {
  '#type': 'view',
  '#name': 'my_view_name',
  '#display_id': 'my_view_display_name',
  '#arguments': ['arg1', 'arg2'],
} }}
Как вывести представление Views в twig шаблоне?

Если установлен модуль Twig Tweak:

{{ drupal_view('my_view', 'my_display_name') }}

Иначе:

{{ {'#type': 'view', '#name': 'my_view', '#display_id': 'my_display_name'} }}
Какое название роута у страницы созданной с помощью Views?

Имя роута строится по шаблону:

view.VIEWS_NAME.VIEWS_DISPLAY_NAME

Например для страницы управления материалами это будет:

view.content.page
Как обновить Views из Javascript?

$('.views-selector').trigger('RefreshView');

При этом в настройках Views должен быть включён ajax.

Работа с javascript

Как в своём модуле подключить js файл на все страницы сайта?

1. В папке модуля создаём файл my-script.js

2. В MODULENAME.libraries.yml описываем library:

my-library:
  js:
    my-script.js: {}

3. В MODULENAME.module подключаем library:

/**
 * Implements hook_page_attachments().
 */
function MODULENAME_page_attachments(array &$page): void {
  $page['#attached']['library'][] = 'MODULENAME/my-library';
}

Подробнее

Как подключить js файл в head?

# THEMENAME.libraries.yml

my-library:
  js:
    js/my-library.js: {}
  header: true # <--
Как в javascript сохранить данные в cookie? Как из javascript работать с cookie?

Как из javascript сохранить в cookie объект?

Как из javascript получить папку текущей темы?

// THEMENAME.theme

/**
 * Preprocess function for html.html.twig.
 */
function THEMENAME_preprocess_html(array &$vars): void {
  $vars['#attached']['drupalSettings']['path']['currentThemePath'] = $vars['directory'];
}
# THEMENAME.libraries.yml

my-library-name:
  js: ...
  dependencies:
    - core/drupalSettings

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

Как с помощью ajax команды тригернуть javascript событие?

$response->addCommand(new InvokeCommand('.my-element', 'trigger', ['click']));
Как из js-скрипта показать текст в jQuery UI Dialog?

var $dialog = $('#drupal-modal');
if ($dialog.length == 0) {
  $dialog = $('<div id="drupal-modal" class="ui-front"/>').appendTo('body');
}
$dialog.append('Hello World!');
Drupal.dialog($dialog, {}).showModal();

Библиотека core/drupal.dialog должна быть уже подключена.

Как добавить класс к jQuery UI Dialog?

Если диалог открывается с помощью ссылки:

<a href="/contact/feedback" class="use-ajax" data-dialog-type="modal" data-dialog-options='{"classes":{"ui-dialog":"ui-dialog--full-width"}}'>Открыть диалог</a>
Как правильно подписываться на события? Как однократно подписаться на событие? Как пользоваться плагином once?

Пример подписки на клик по заголовку блока меню:

(function (Drupal) {

  Drupal.behaviors.myModule = {
    attach: function (context, settings) {
      once('menu-toggle', '.main-menu-block__title', context).forEach(element => {
        element.addEventListener('click', event => {
          element.closest('.main-menu-block').classList.toggle('main-menu-block--open');
        });
      });
    }
  };

})(Drupal);

Не забываем прописать зависимость либы от core/once:

my-library:
  js:
    ...
  dependencies:
    - core/once

Подробнее на английском, на русском.

Работа с многоязычностью

Как в php коде получить название дефолтного языка?

/** @var \Drupal\Core\Language\LanguageInterface $default_language */
$default_language = \Drupal::languageManager()->getDefaultLanguage();
$default_langcode = $default_language->getId(); // Строка "ru", "en" или другой код языка.
Как в php коде получить название текущего языка?

/** @var \Drupal\Core\Language\LanguageInterface $current_language */
$current_language = \Drupal::languageManager()->getCurrentLanguage();
$current_langcode = $current_language->getId(); // Строка "ru", "en" или другой код языка.
Как в php коде получить значение поля сущности на определённом языке?

$node = \Drupal\node\Entity\Node::load(123);
$node_en = $node->hasTranslation('en') ? $node->getTranslation('en') : $node;
$node_en_foo_value = $node_en->get('field_foo')->value;
Как в twig перевести фразу? Какой аналог t()?

{{ 'Home'|t }}
{{ 'Order'|t({}, {'context': 'Commerce'}) }}
{% trans %}Product{% endtrans %}
Как в twig шаблоне получить значение поля сущности на определённом языке?

{% set node_en = node.hasTranslation('en') ? node.getTranslation('en') : node %}
{{ node_en.field_foo.value }}
Как удалить префикс дефолтного языка из адресов?

Идём на страницу admin/config/regional/language/detection/url и удаляем префикс дефолтного языка.
Как сделать чтобы при аутентификации на основном домене пользователь стал залогинённым на всех языковых поддоменах?

В файле services.yml расскоментируем и изменяем значение cookie_domain:

parameters:
  session.storage.options:
    ...
    cookie_domain: '.example.com'

Сбрасываем кэш.

Написанное актуально для
Drupal 8+
Похожие записи

Комментарии

Евгений
06.04.2019, 20:49

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

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

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

Огромное Вам спасибо!
Это должно быть в закладках у всякого начинающего адепта секты "Друпал - наше все" )))

Эххх, такой бы гайд еще по коммерц 2....

Наталия
19.10.2020, 16:04

Замечательная статья, благодарю, много полезного для новичков и не только.

Это просто ахринительски. Спасибо.

Александр
16.01.2021, 13:25

Спасибо, очень крутая подборка, теперь я в познании друпал настолько преисполнен что как-будто сто миллиардов миллионов лет с друпалом работаю, мне уже этот мир абсолютно понятен... )))

Дмитрий
20.01.2021, 10:35

Спасибо! Закрепить бы где-нибудь, что бы не потерялось.

Здравствуйте. Добавьте в шапке, что в Windows надо прописывать 4 циркумфлекса при указании к версии иначе пакеты могут не ставиться. Например, composer require "foo/bar:^^^^1.0"

Привет. Я тут решил потренироваться по твоим заметкам.
Нюанс... http://xandeadx.ru/blog/drupal/946#faq-load-vocabulary-terms-names
Способ 2 через запрос в базу не покажет все термины словаря, а покажет все термины словаря, которые задействованы в энтити.
Другими словами покажет ТОЛЬКО те термины, которые уже используются (т.е. есть в базе data данных).
И спасибо за FAQ, очень полезно, черпнул для себя всякого.

@Dishvola таблица taxonomy_term_field_data содержит все термины, а используемые находятся в таблице taxonomy_index, поэтому запрос покажет все термины словаря, независимо, используются они или нет

Игорь
15.01.2022, 00:29

Круто!
А библиотеки composer устанавливает? Я предпологал, что ставит модуль, все зависимости включая библиотеки. Нужно ручками?
и . я ещё не понимаю что такое twg2, а уже говорят нужно обновлять до 3. Может уже смысл 10 ку сразу разворачивать?

Я понял, у меня была опечатка vid вместо tid, поэтому выдавало один термин, а не оба для моего кейса.

  $names = \Drupal::database()->select('taxonomy_term_field_data', 't')
    ->fields('t', ['Vid', 'name'])
    ->condition('t.vid', 'blog_type')
    ->execute()
    ->fetchAllKeyed();

Добавить комментарий