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

Drupal → Drupal 8 Dev FAQ

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

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

Содержание

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

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

С чего начать освоение Drupal?
С установки и настройки Xdebug.
Как правильно скачать Drupal? Как скачать Drupal с помощью composer?
Способ 1 (папка vendor будет выше web root):
$ composer create-project drupal/recommended-project

Способ 2 (папка vendor будет в web root):
$ composer create-project drupal/legacy-project

Подробнее
Как установить composer локально?
cd path/to/drupal
wget -O composer.phar https://getcomposer.org/composer-stable.phar

После этого можно пользоваться композером через команду php composer.phar, например:
php composer.phar require drupal/devel
Как обновить ядро друпала?
Если установленная версия свежее или равна 8.8.0:
$ composer update drupal/core* --with-all-dependencies
$ vendor/bin/drush updb

Подробнее.

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

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

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

Подробнее.
Как скачать модуль?
Последняя рекомендуемая версия:
$ composer require drupal/devel

RC версия:
$ composer require drupal/field_group:^3.0-rc2

Dev версия:
$ composer require drupal/devel:1.x-dev

Подробнее.

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

Как обновить модуль?
В большинстве случаев:
$ 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
Как удалить модуль?

1. Если модуль включён, то выключаем его на странице admin/modules/uninstall.

2. Удаляем файлы:

$ composer remove drupal/devel

Как узнать что блокирует обновление модуля/пакета?
Если не удаётся обновить какой-нибудь пакет или ядро с помощью composer, то поможет команда prohibits, которая выдаст причины:
$ composer prohibits drupal/core 8.8.1
Как узнать, какие composer пакеты зависят от определённого пакета?
Команда выдаст список пакетов, которые зависят от symfony/process
$ composer why symfony/process
Как создать модуль?
$ vendor/bin/drush generate module

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

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

Подробнее.

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

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

Подробнее.

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

$ vendor/bin/drush generate block

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

Способ 2:
/**
 * @Block(...)
 */
class MyBlock extends BlockBase {
 
  public function build() {
    ...
  }
 
  public function getCacheMaxAge() {
    return 0; // <---
  }
 
}

Стоит помнить, что отключение кэширование блока отключит кэширование страницы на которой этот блок выводится.
Как программно ограничить видимость своего блока?
class MyBlock extends BlockBase {
 
  ...
 
  /**
   * {@inheritdoc}
   */
  public function access(AccountInterface $account, $return_as_object = FALSE) {
    $access = FALSE;
 
    if (...) {
      $access = TRUE;
    }
 
    return $return_as_object ? AccessResult::allowedIf($access) : $access;
  }
 
}
Как получить название сайта, слоган и основной 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');
Как очистить кэш?

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

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

$ vendor/bin/drush cache-rebuild

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

drupal_flush_all_caches();

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

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

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

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

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

Подробнее.

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

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

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

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

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

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

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

Подробнее.
Как создать свой alter-хук? Какой аналог drupal_alter()?
$data = ['foo' => 'bar'];
\Drupal::moduleHandler()->alter('my_data', $data);

После этого другие модули смогут альтерить с помощью хука hook_my_data_alter:
function MODULENAME_my_data_alter(&$data) {
  $data['foo'] = 'baz';
}

Подробнее.
Как получить число комментариев у сущности?
$comment_count = (int)$entity->get('field_comment')->comment_count;
Как сделать, чтобы комментарии сортировались по дате создания, от свежих к старым?
/**
 * Implements hook_query_TAG_alter(): comment_filter.
 */
function hook_query_comment_filter_alter(QueryAlterableInterface $query) {
  if ($query instanceof PagerSelectExtender) {
    $order_by = &$query->getOrderBy();
    unset($order_by['c.cid']);
    $query->orderBy('c.created', 'DESC');
  }
}
Как получить номер страницы списка комментариев, на которой отображается нужный комментарий?
Функция возвращает номер страницы комментария:
function _get_comment_page($entity_id, $comment_cid, $per_page) {
  $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() {
    $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 }

Подробнее

Как удалить префикс дефолтного языка из адреса?
Идём на страницу admin/config/regional/language/detection/url и удаляем префикс дефолтного языка.
Как узнать, есть ли в моей версии php поддержка формата WebP?
Если для работы с изображениями используется GD, то надо выполнить в консоли следующий код, который покажет все поддерживаемые форматы:
vendor/bin/drush eval "print_r(gd_info())"
Где указать вес своего модуля? Как изменить вес своего модуля?
Вес модуля задаётся в hook_install():
// modulename.install
 
/**
 * Implements hook_install().
 */
function modulename_install() {
  module_set_weight('modulename', 123);
}

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

// modulename.install
 
/**
 * Set module weight.
 */
function modulename_update_8001() {
  module_set_weight('modulename', 123);
}

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

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

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

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

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

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

Как получить название/label поля сущности?
// Если есть доступ к объекту сущности
$field_example_label = $entity->get('field_example')->getFieldDefinition()->getLabel();
// Иначе
$field_example_label = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', 'page')['field_example']->getLabel();
Как вычислить число дней между датами в поле типа Date range?
$date_start = $node->field_daterange->start_date;
$date_end = $node->field_daterange->end_date;
$days_between_dates = $date_end->diff($date_start)->format('%a');
Как сделать необязательным второе значение в поле типа Date range?
До того, как не закрыта соответствующая issue, можно поставить модуль Optional End Date.
Как проверить тип поля на базовое/настраиваемое?
if ($node->get('title')->getFieldDefinition() instanceof BaseFieldDefinition) {
  // Поле title является базовым
}
if ($node->get('field_example')->getFieldDefinition() instanceof FieldConfigInterface) {
  // Поле field_example является настраиваемым
}
Как получить название значения у поля типa List?
$field_example_items = $entity->get('field_example');
$allowed_values = options_allowed_values($field_example_items->getFieldDefinition()->getFieldStorageDefinition(), $entity);
$value_label = $allowed_values[$field_example_items->value];
Как получить объект бандла сущности, например NodeType у ноды?
// Способ 1
$node_type = $node->get('type')->entity;
 
// Способ 2
$node_type = NodeType::load($node->bundle());
Как получить адрес/url/uri файла у сущности типа File?
$file = \Drupal\file\Entity\File::load(123); /** @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
Как получить адрес/url/uri файла из reference-поля сущности?
$file = $entity->get('field_example_file')->entity; /** @var \Drupal\file\FileInterface $file */
$file_url = $file->createFileUrl(); // /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 существующего файла?
$uri = 'public://example.jpg';
$file = \Drupal\file\Entity\File::create([
  'uri' => $uri,
  'filename' => basename($uri),
]);
$file->save();
Как программно добавить файл в многозначное файловое поле сущности?
$file_id = 123;
$entity->get('field_file')->appendItem(['target_id' => $file_id]);

или так:
$file = \Drupal\file\Entity\File::load(123);
$entity->get('field_file')->appendItem($file);
Как скачать файл на сервер по url файла?
$file = system_retrieve_file('http://example.com/image.jpg', 'public://image.jpg', TRUE);
Как программно удалить материал или другую сущность?
$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 (способ подходит для удаления небольшого количества сущностей, до тысячи):
$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 TermStorageInterface $term_storage */
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$terms = $term_storage->loadByProperties(['vid' => 'category']);
foreach ($terms as $term) {
  $term->save();
}
Как программно пересохранить все ноды определённого типа?
\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();
}
Как получить информацию обо всех полях определённого типа?
/** @var EntityFieldManagerInterface $entity_field_manger */
$entity_field_manger = \Drupal::service('entity_field.manager');
$entity_reference_fields = $entity_field_manger->getFieldMapByFieldType('entity_reference');
Как получить информацию обо всех полях у определённого бандла сущности?
/** @var EntityFieldManager $entity_field_manager */
$entity_field_manager = \Drupal::service('entity_field.manager');
$article_fields = $entity_field_manager->getFieldDefinitions('node', 'article');
Как получить ноду по её синониму url?
$path = \Drupal::service('path.alias_manager')->getPathByAlias('/example-url-alias');
if (preg_match('/node\/(\d+)/', $path, $matches)) {
  $node = \Drupal\node\Entity\Node::load($matches[1]);
}
Как в hook_entity_update() узнать, что значение определённого поля изменилось?
/**
 * Implements hook_entity_update().
 */
function hook_entity_update(EntityInterface $entity) {
  $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
  }
}
Как получить дочерние термины у определённого термина?
/** @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');
 
// Получает всех родителей независимо от глубины. В массиве так же возвращается и исходный термин.
$parents = $term_storage->loadAllParents(123); /** @var TermInterface[] $parents */
 
// Получает непосредственных родителей термина 123. В большинстве случаев это будет массив из одного элемента.
$parents = $term_storage->loadParents(123); /** @var TermInterface[] $parents */
 
// Непосредственные родители так же доступны через поле термина "parent"
$parents_items = $term->get('parent'); /** @var EntityReferenceFieldItemListInterface $parents_items */

Следует помнить, что непосредственных родителей у термина может быть несколько. Так же следует помнить, что метод $term->get('parent')->isEmpty() всегда возвращает FALSE, поэтому проверять есть ли у термина родители надо с помощью if ($term->get('parent')->entity).
Как программно удалить все значения многозначного поля?
$node = \Drupal\node\Entity\Node::load(123);
$node->set('field_tags', NULL); // Remove all values
$node->save();
Как удалить определённое значение многозначного поля?
$node = \Drupal\node\Entity\Node::load(123);
 
// Три способа удалить значение по его индексу
unset($node->get('field_tags')[2]);
$node->get('field_tags')->offsetUnset(2);
$node->get('field_tags')->removeItem(2);
 
// Способ удалить значение если индекс неизвестен
$node->get('field_tags')->filter(function ($item) {
  return $item->target_id != 123; // Удаляем значение с id термина 123
});
 
$node->save();

Темизация (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() {
  return [
    'node' => [
      'render element' => 'elements',
    ],
  ];
}

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

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

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

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

Как переопределить шаблон определённого поля в своей теме?
Скопировать файл core/modules/system/templates/field.html.twig в папку templates своей темы, переименовать файл по шаблону field--field-name.html.twig, где field-name это машинное имя поля, сбросить кэш. Подробнее, раздел "Fields".
Как из javascript получить папку текущей темы?
// THEMENAME.theme
 
/**
 * Preprocess function for html.html.twig.
 */
function THEMENAME_preprocess_html(&$vars) {
  $vars['#attached']['drupalSettings']['path']['currentThemePath'] = $vars['directory'];
}

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

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

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

Подробнее.

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

Как подключить js файл в head?
*.libraries.yml:
my-library:
  js:
    js/my-library.js: {}
  header: true # <--
Как в php получить отрендеренное поле сущности? Какой аналог field_view_field()?
$node = \Drupal\node\Entity\Node::load(123);
 
// Render-array поля, прошедшего через field.html.twig
$field_example_build = $node->get('field_example')->view('full');
// Render-array первого значения поля, прошедшего через форматтер
$field_example_build = $node->get('field_example')[0]->view('full');
Как в 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() {
  return [
    'my_template' => [
      'variables' => [
        'my_variable' => NULL,
      ],
    ],
  ];
}

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

Подробнее.
Как темизировать форму?

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

themename.theme

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

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

Подробнее.

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

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

Подробнее.
Как в пунктах меню разрешить использовать html?
function THEMENAME_preprocess_menu(&$vars) {
  THEMENAME_preprocess_menu_items($vars['items']);
}
 
function THEMENAME_preprocess_menu_items(&$items) {
  foreach ($items as &$item) {
    $item['title'] = Markup::create($item['title']);
 
    if ($item['below']) {
      THEMENAME_preprocess_menu_items($item['below']);
    }
  }
}
Как добавить css-класс определённому меню?
Пример добавления основному меню класса dragscroll:
function THEMENAME_preprocess_menu__main(&$vars) {
  $vars['attributes']['class'][] = 'dragscroll';
}
Как добавить css-класс определённому блоку?
Пример добавления классов блоку с идентификатором main_menu:
function THEMENAME_preprocess_block__main_menu(&$vars) {
  $vars['attributes']['class'][] = 'my-block-class';
  $vars['content_attributes']['class'][] = 'my-content-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() {
    $events[KernelEvents::VIEW][] = ['onKernelView', 10];
    return $events;
  }
 
  /**
   * KernelEvents::VIEW event callback.
   */
  public function onKernelView(GetResponseForControllerResultEvent $event) {
    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;
}

Работа с Twig

Как проверить, есть ли у поля сущности значение?
{% if not node.field_example.isEmpty() %}
   field_example is not empty
{% endif %}

или так:
{% if content.field_example[0] %}
   field_example is not empty
{% endif %}
Как узнать, сколько у поля сущности значений?
{% if node.field_example|length > 1 %}
   Field items count more 1
{% endif %}
Как в шаблоне ноды/сущности вывести отдельные поля сущности?
Отформатированное значение поля, прошедшее через field.html.twig:
{{ content.field_example }}

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

Отформатированные значения поля без использования field.html.twig (нужен модуль Twig Tweak):
{{ content.field_example|children }}

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

Подробнее.
Как проверить, что массив не пустой?
{% if my_array is not empty %}
  ...
{% endif %}

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

Подробнее про empty и length.
Как проверить, что в массиве есть элемент с определённым ключом? Какой аналог php-шной функции isset()?
{% if (example_array.foo is defined) %}
  {{ example_array.foo }}
{% endif %}
 
{% if (example_array.foo is not defined) %}
  Empty message...
{% endif %}
Как воспользоваться функцией Element::children()?
{% for key, element in elements if key|first != '#' %}
  {{ element }}
{% endfor %}

Или так, если стоит модуль Twig Tweak:
{% for element in elements|children %}
  {{ element }}
{% endfor %}
Как в field.html.twig получить доступ к объекту родительской сущности поля?
Объект сущности лежит в переменной element['#object']. Пример вывода заголовка сущности:
{{ element['#object'].title.value }}
Как вывести url сущности?
Пример генерации адреса для ноды 123:
{{ path('entity.node.canonical', {node: 123}) }}

Подробнее
Как перевести фразу? Какой аналог t()?
{{ 'Home'|t }}
{{ 'Order'|t({}, {'context': 'Commerce'}) }}
Как склонять слова с числами?
{% trans %}
  {{ count }} review
{% plural count %}
  {{ count }} reviews
{% endtrans %}
Как проверить залогинён ли пользователь?
{% if logged_in %}
  Current user is authenticated
{% else %}
  Current user is anonymouse
{% endif %}
Как в шаблоне вывести представление Views?
Если установлен модуль Twig Tweak:
{{ drupal_view('my_view', 'my_display_name') }}

Иначе:
{{ {'#type': 'view', '#name': 'my_view', '#display_id': 'my_display_name'} }}

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

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

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

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

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

Стоит помнить, что отключение кэширование формы отключит кэширование всех вышестоящих элементов, т.е. блока (если форма выводится в блоке), ноды (если форма выводится в ноде) и в итоге всей страницы.
Как очистить GET форму от системных параметров op, form_build_id и form_id?
public function buildForm(array $form, FormStateInterface $form_state) {
  ...
  $form['#pre_render'][] = [$this, 'preRender'];
 
  return $form;
}
 
public function preRender($form) {
  unset($form['form_id']);
  unset($form['form_build_id']);
  unset($form['form_token']);
  unset($form['submit']['#name']);
 
  return $form;
}

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

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

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

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

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

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

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

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

Подробнее.
Как вывести элемент формы без обёртки с классом form-item?
$form['element'] = [
  '#type' => 'textfield',
  '#title' => 'Element',
  '#theme_wrappers' => [], // <--
];

Замечание - после удаления обёртки не будут работать некоторые js функции, например #states.
Как загрузить файл на сервер без сохранения его в таблице file_managed?
public function buildForm(array $form, FormStateInterface $form_state) {
  $form['upload'] = [
    '#type' => 'file',
    '#title' => t('File'),
  ];
 
  ...
 
  return $form;
}
 
public function submitForm(array &$form, FormStateInterface $form_state) {
  $file_system = \Drupal::service('file_system'); /** @var FileSystemInterface $file_system */
  $all_files = \Drupal::request()->files->get('files', []); /** @var UploadedFile[] $all_files */
  if ($all_files['upload']) {
    $file_system->move($all_files['upload']->getRealPath(), 'public://file.' . $all_files['upload']->getExtension());
  }
}

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

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

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

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

Код сгенерит запрос вида:
SELECT * FROM node n
WHERE EXISTS (
  SELECT * FROM taxonomy_index t
  WHERE t.nid = n.nid
)
Как в условии использовать оператор LIKE?
$query = \Drupal::database()
  ->select('node', 'n')
  ->condition('n.title', '%' . \Drupal::database()->escapeLike('world') . '%', 'LIKE');
Как посмотреть получившийся SQL запрос?
Перед ->execute() вызываем:
$sql = $query->__toString();

Или так, если надо посмотреть запрос с уже подставленными параметрами:
$query->addTag('debug');

Entity Query API

Как получить сущности по значениям полей? Как пользоваться сервисом entity.query?
$nids = \Drupal::entityQuery('node');
  ->condition('type', 'article');
  ->condition('status', NodeInterface::PUBLISHED);
  ->condition('field_foo', 'bar')
  ->execute();

Подробнее.

Как получить объект одной сущности?
$nids = \Drupal::entityQuery('node');
  ->condition('type', 'article');
  ->condition('status', NodeInterface::PUBLISHED);
  ->condition('field_foo', 'bar')
  ->range(0, 1)
  ->execute();
if ($nids) {
  $node = Node::load(current($nids));
}
Как добавить условие - "многозначное поле имеет значение 1 и 2"?
$node_query = \Drupal::entityQuery('node');
$node_query->condition($node_query->andConditionGroup()->condition('field_example', 'foo'));
$node_query->condition($node_query->andConditionGroup()->condition('field_example', 'bar'));
$nids = $node_query->execute();
Как добавить условие - "поле не имеет значений" или наоборот "поле имеет хотя бы одно значение"?
// Условие "поле field_foo не должно иметь значений"
$entity_query->notExists('field_foo');
// Или
$entity_query->condition('field_bar', NULL, 'IS NULL');
 
// Обратное условие "поле field_bar должно иметь хотя бы одно значение"
$entity_query->exists('field_bar');
// Или
$entity_query->condition('field_bar', NULL, 'IS NOT NULL');
Как добавить условие - "многозначное поле имеет не больше одного значения"?
$node_query->notExists('field_example.1.value');

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

Работает только с включённым модулем Devel.
Как получить идентификатор последнего добавленного материала?
$result = \Drupal::entityQuery('node')
  ->sort('nid', 'DESC')
  ->range(0, 1)
  ->execute();
$last_nid = $result ? current($result) : NULL;

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

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

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

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

Подробнее.
Как получить адрес текущей страницы? Какой аналог current_path(), request_path() и request_uri()?
// Системный адрес. Аналог current_path().
$current_system_path = \Drupal::service('path.current')->getPath();
 
// Синоним адреса. Аналог request_path().
$current_system_path = \Drupal::service('path.current')->getPath();
$current_path_alias = \Drupal::service('path.alias_manager')->getAliasByPath($current_system_path);
 
// Относительный путь из строки браузера, с GET параметрами. Аналог request_uri().
$current_request_uri = \Drupal::request()->getRequestUri();
 
// Абсолютный путь из строки браузера, с GET параметрами.
$current_uri = \Drupal::request()->getUri();
Как получить адрес папки установки друпала?
$base_path = base_path();
Если друпал установлен в корень сайта, то base_path() вернёт /.
Если друпал установлен в папку /drupal/folder, то base_path() вернёт /drupal/folder/.
Как получить название текущего роута?
$current_route_name = \Drupal::routeMatch()->getRouteName();
Как проверить, является ли текущая страница страницей материала?
if (\Drupal::routeMatch()->getRouteName() == 'entity.node.canonical') {
  ...
}
Как получать параметры из текущего пути? Какой аналог arg() и menu_get_object()?
Пример для адреса node/123
$nid = \Drupal::routeMatch()->getRawParameter('node'); // Integer
$node = \Drupal::routeMatch()->getParameter('node'); // Node object
Как добавить класс ссылке меню, созданной с помощью *.links.menu.yml?
Как получить url файла по его uri?
// Вернёт http://example.com/sites/default/files/example.jpg
$file_url = file_create_url('public://example.jpg');
Как создать url с destination на текущую страницу?
$destination_array = \Drupal::destination()->getAsArray();
$url = \Drupal::urlGenerator()->generateFromRoute('example.route', [], ['query' => $destination_array]);
Как сделать редирект из контроллера?
class ExampleController extends ControllerBase {
 
  public function exampleAction() {
    return $this->redirect('<front>');
  }
 
}
Как получить синоним адреса ноды?
$node_alias = $node->get('path')->alias; // "/example/node/path"
Какой шаблон прописать в pathauto для терминов древовидного словаря?
Чтобы получить адреса терминов вида /catalog/category1/category2/category3, нужно прописать терминам следующий шаблон:
catalog/[term:parents:join-path]/[term:name]
Как получить адрес к картинке со стилем (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 = file_url_transform_relative($absolute_url); // /sites/default/files/styles/thumbnail/public/image.jpg?itok=BN-lOMvj

Работа с 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, $display_id, array &$args) {
  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, $display_id, array &$args) {
  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, $display_id, array &$args) {
  if ($view->id() == 'my_view_name') {
    $view->setItemsPerPage(10);
  }
}
Как изменить выводимое значение поля в Views?
Пример замены значения поля title в представлении content_recent
function THEMENAME_preprocess_views_view_field__content_recent__title(&$vars) {
  $vars['output'] = 'new field output';
}
Как для Views добавить css-класс, содержащий число результатов на текущей странице?
Пример для представления с именем example_view:
/**
 * Preprocess function for views-view--example-view.html.twig.
 */
function THEMENAME_preprocess_views_view__example_view(&$vars) {
  $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 = Views::getView('my_view_name');
$views->setDisplay('my_view_display_name');
$views->setArguments(['my_argument']);
$views->preExecute();
$views->execute();
$views_build = $views->render();

Способ 3 (из twig файла):
{{ drupal_view('my_view_name', 'my_view_display_name', 'my_argument') }}
Как в шаблоне вывести представление Views?
Если установлен модуль Twig Tweak:
{{ drupal_view('my_view', 'my_display_name') }}

Иначе:
{{ {'#type': 'view', '#name': 'my_view', '#display_id': 'my_display_name'} }}
Написанное актуально для Drupal 8
Похожие записи

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

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

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

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

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

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

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

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

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

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