Drupal → Drupal 8/9 FAQ

06.04.2019

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

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

Содержание

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

Подробнее

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

Если установленная версия свежее или равна 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 версия.

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

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

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, которая выдаст причины:

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

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

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

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

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

4. Выполняю composer install и если всё хорошо vendor/bin/drush updb

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

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

composer outdated

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

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

Команда выдаст список пакетов, которые зависят от symfony/process

composer why symfony/process
Как удалить модуль?

1. Сначала деинсталируем:

vendor/bin/drush pm-uninstall devel

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

composer remove drupal/devel

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

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

С установки и настройки Xdebug.
Как инсталлировать Drupal из консоли с помощью Drush?

vendor/bin/drush site-install standard --db-url=mysql://root:root@localhost/drupal --account-pass=admin

Подробнее.

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

// 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;
  }

}
Как получить значение конфигурации? Какой аналог 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');
Как очистить кэш?

Способ 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');
Как в своём модуле подключить js файл на все страницы сайта?

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

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

my-library:
  js:
    my-js: {}

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

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

Подробнее.

Как получить текущего пользователя? Какой аналог $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"
}
Как в 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();
Как экранировать 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');
Как импортировать несколько конфигов из определённой папки?

vendor/bin/drush config-import --partial --source=modules/modulename/config/install
Как отправить 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) {
  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) {
  if ($message['module'] == 'commerce' && $message['key'] == 'order_receipt') {
    $message['subject'] = 'New subject';
    $message['body'] = ['New body'];
  }
}

Подробнее

Как транслитерировать строку?

$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:*]? Как создать свой токен вида "[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(string $type, array $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('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) {
  $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() {
    $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(int $entity_id, int $comment_cid, int $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 }

Подробнее.

Как узнать, есть ли в моей версии 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);
}
Как программно проверить, что включён определённый модуль?

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

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

Пример форматтера выводящий 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) {
    $element = [];

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

    return $element;
  }

}

Подробнее

Работа с сущностями и полями (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_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;
}
Как программно создать ноду?

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

Подробнее

Как получить информацию о поле? Какой аналог 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
}
Как получить машинное имя поля заголовка сущности?

$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())) {
  ...
}
Как вычислить число дней между датами в поле типа Date range?

$date_start = $node->get('field_daterange')->start_date; /** @var \Drupal\Core\Datetime\DrupalDateTime $date_start */
$date_end = $node->get('field_daterange')->end_date;  /** @var \Drupal\Core\Datetime\DrupalDateTime $date_end */
$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_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 существующего файла?

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

$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();
Как программно удалить ноду? Как программно удалить сущность?

$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');

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

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

// Непосредственные родители так же доступны через поле термина "parent"
$direct_parents_items = $term->get('parent'); /** @var EntityReferenceFieldItemListInterface $direct_parents_items */

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

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

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

$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();
Как отсортировать конфиг-сущности по весу?

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

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

Темизация (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 }}

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

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

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

MODULENAME.module:

/**
 * Implements hook_theme().
 */
function MODULENAME_theme() {
  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 машинное имя нужного блока, нижние подчёркивания заменяются на тире). Сбрасываем кэш. Подробнее, раздел "Blocks".

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

Скопировать файл 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) {
  $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

Подробнее.

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

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

# THEMENAME.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,
      ],
    ],
  ];
}

templates/my-template.html.twig:

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

Подробнее.

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

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

// THEMENAME.theme

function THEMENAME_preprocess_views_view(array &$variables) {
  $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:

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?

// THEMENAME.theme

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(ViewEvent $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;
}
Как превратить массив с 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")?

Работа с 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.

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

{{ 'English'|slice(0, 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 (нужен модуль Twig Tweak):

{{ content.field_example|children }}

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

{{ 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 поля ноды?

{{ file_url(node.field_image.entity.uri.value) }}

или так:

{{ file_url(node.field_image.entity.getFileUri()) }}
Как проверить, что в массиве есть элемент с определённым ключом? Какой аналог 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 %}
Как в шаблоне проверить права текущего пользователя?

Во всех шаблонах есть переменная 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 arr = ['foo', 'bar'] %}
Добавляем значение 'baz':
{% set arr = arr|merge(['baz']) %} 
Как присвоить значение элементу массива?

{% set arr = {'foo': 'bar', 'baz': 'bat'} %}
Меняем значение arr.baz на 'new':
{% set arr = arr|merge({'baz': 'new'}) %} 
Как в 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>

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

Подробнее.

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

// MODULENAME.module

/**
 * Implements hook_form_FORM_ID_alter(): views_exposed_form.
 */
function MODULENAME_form_views_exposed_form_alter(array &$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(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) {
    ...
  }

  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');
}
Как в submitForm() получить значение элемента при #tree=>true?

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

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();
      }
    }
  }

}

Подробнее.

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

public function buildForm(array $form, FormStateInterface $form_state) {
  $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) {
  $checked_checkboxes = \Drupal\Core\Render\Element\Checkboxes::getCheckedCheckboxes($form_state->getValue('example_checkboxes'));
  // Если были отмечены Foo и Baz, то в $checked_checkboxes будет ['foo', 'baz']
}
Как вывести элемент формы без обёртки с классом form-item?

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

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

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

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

public function submitForm(array &$form, FormStateInterface $form_state) {
  /** @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);
  }
}

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

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

$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'

Подробнее.

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

$query->isNull('field_name');
$query->isNotNull('field_name');
Как подсчитать число записей с помощью 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'")

Как добавить подзапрос с 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();

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

$file_url = file_create_url('public://example.jpg'); // Вернёт http://example.com/sites/default/files/example.jpg
$file_local_url = file_url_transform_relative($file_url); // Вернёт /sites/default/files/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]
Как получить 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 = 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?

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

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

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

function THEMENAME_preprocess_views_view_field__content_recent__title(&$vars) {
  $vars['output'] = 'new field output';
}
Как для Views добавить css-класс, содержащий число результатов на текущей странице?

Пример для представления с id 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 = \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();

Способ 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'} }}
Какое название роута у страницы созданной с помощью Views?

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

view.VIEWS_NAME.VIEWS_DISPLAY_NAME

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

view.content.page

Работа с javascript

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

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

Как из 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

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

$response->addCommand(new InvokeCommand('.my-element', 'trigger', ['click']));
Как подключить js файл в head?

# THEMENAME.libraries.yml

my-library:
  js:
    js/my-library.js: {}
  header: true # <--
Как показать диалог с текстом?

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 должна быть уже подключена.

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

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

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

$current_language = \Drupal::languageManager()->getCurrentLanguage(); // Объект с \Drupal\Core\Language\LanguageInterface
$current_langcode = $current_language->getId(); // Строка "ru" или "en" или другой код языка.
Как в twig перевести фразу? Какой аналог t()?

{{ 'Home'|t }}
{{ 'Order'|t({}, {'context': 'Commerce'}) }}
{% trans %}Product{% endtrans %}
Как удалить префикс дефолтного языка из адреса?

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

Написанное актуально для
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

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

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