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

Drupal → Drupal 8 Dev FAQ

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

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

Содержание

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

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

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

Способ 2 (папка vendor будет в web root):
$ composer create-project drupal/legacy-project
Как установить Drush?
$ composer require drush/drush

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

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

Подробнее.

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

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

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

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

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

Подробнее.

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

$ vendor/bin/drush generate controller

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

Подробнее.

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

$ vendor/bin/drush generate block

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

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

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

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

$ vendor/bin/drush cache-rebuild

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

drupal_flush_all_caches();

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

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

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

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

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

Подробнее.

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

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

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

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

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

Можно обойтись без реализации хука, если указать модуль system и специальным образом сформировать массив $params:
\Drupal::service('plugin.manager.mail')->mail('system', 'example_mail_key', 'example@gmail.com', 'en', [
  'context' => [
    'subject' => 'Subject',
    'message' => \Drupal\Core\Render\Markup::create('Message'),
  ],
]);
Как получить текущее время в timestamp?
$current_timestamp = \Drupal::time()->getCurrentTime();
Как транслитерировать строку?
$transliterated_string = \Drupal::transliteration()->transliterate('Привет Мир', 'ru');
// --> Privet Mir
Как отдать пользователю текст в виде файла?
class ExampleController extends ControllerBase {
 
  public function export() {
    $response = new Response();
    $response->headers->set('Content-Type', 'text/csv; charset=utf-8');
    $response->headers->set('Content-Disposition', 'attachment; filename="example.txt"');
    $response->setContent('Hello World!');
 
    return $response;
  }
 
}
Как вывести страницу 404 (страница не найдена) или 403 (доступ запрещён)?
// 404
throw new NotFoundHttpException();
// 403
throw new AccessDeniedHttpException();
Как создать свой токен вида "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),
]);
Как из своего контроллера добавить на страницу мета-тег?
class ExampleController extends ControllerBase {
 
  public function exampleAction() {
    $meta_description = [
      '#tag' => 'meta',
      '#attributes' => [
        'name' => 'description',
        'content' => ['#plain_text' => 'Random text'],
      ],
    ];
 
    return [
      '#attached' => [
        'html_head' => [
          [$meta_description, 'description']
        ],
      ],
      ...
    ];
  }
 
}
Как создать свой permission? Какой аналог hook_permission()?
modulename.permissions.yml
administer modulename:
  title: 'Administer Modulename'

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

Подробнее.

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

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

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

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

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

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

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

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

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

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

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

modules/custom/modulename/modulename.module

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

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

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

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

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

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

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

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

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

Подробнее.

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

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

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

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

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

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

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

themename.theme

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

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

Подробнее.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Подробнее.
Как в #states задать условие "показывать если значение НЕ xxx"?
$form['second_element'] = [
  ...
  '#states' => [
    'invisible' => [
      ':input[name="first_element"]' => [
        ['value' => 'xxx'],
      ],
    ],
  ],
];
Как отключить html валидацию формы?
$form['#attributes']['novalidate'] = 'novalidate';
Как показать ошибки валидации в диалоговом окне?
class ExampleForm extends FormBase {
 
  /**
   * {@inheritDoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    ...
 
    $form['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
      '#ajax' => [
        'callback' => '::ajaxSubmit',
      ],
    ];
 
    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
 
    return $form;
  }
 
  /**
   * Ajax submit callback.
   */
  public function ajaxSubmit(array $form, FormStateInterface $form_state) {
    $response = new AjaxResponse();
 
    if ($form_state->hasAnyErrors()) {
      $response->addCommand(new OpenModalDialogCommand($this->t('Error'), ['#type' => 'status_messages']));
    }
    else {
      ...
    }
 
    return $response;
  }
 
}
Как с помощью ajax команды добавить или удалить html класс?
$response->addCommand(new InvokeCommand('.my-element', 'addClass', ['my-new-class']));
$response->addCommand(new InvokeCommand('.my-element', 'removeClass', ['my-old-class']));
Как разрешить отправлять форму не чаще одного раза в час?
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.

Работа с базой данных (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');

Entity Query API

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

Подробнее.

Как добавить условие - "многозначное поле имеет значение 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, NULL, 'IS NULL');
// Обратное условие "поле field_bar должно иметь хотя бы одно значение"
$entity_query->exists('field_bar');
// Или
$entity_query->condition($field, 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.

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

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

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

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

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

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

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

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

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

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

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

Эххх, такой бы гайд еще по коммерц 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].

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