Extra Block Types (EBT) - New Layout Builder experience❗

Extra Block Types (EBT) - styled, customizable block types: Slideshows, Tabs, Cards, Accordions and many others. Built-in settings for background, DOM Box, javascript plugins. Experience the future of layout building today.

Demo EBT modules Download EBT modules

❗Extra Paragraph Types (EPT) - New Paragraphs experience

Extra Paragraph Types (EPT) - analogical paragraph based set of modules.

Demo EPT modules Download EPT modules

Scroll

12.11.3. Хуки для работы с Entity.

19/11/2019, by Ivan

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

В этой статье вы можете прочитать в общем, что такое хуки и зачем они нужны:

12.2. Что такое hook в Drupal 8?

Мы будем использовать хуки для добавления нашего кастомного кода, который будет срабатывать на определенные события связанные с сущностями: добавление, удаление, обновление.

Все хуки Drupal 8 вы можете посмотреть на этой странице:

https://api.drupal.org/api/drupal/core!core.api.php/group/hooks/8.2.x

мы разберем только часть из них, которые наиболее часто встречаются в кастомных модулях для работы с контентом.

Я добавил весь код на github в модуль drupalbook_examples, вы можете скачать модуль и добавить его к себе на сайт:

https://github.com/levmyshkin/drupalbook8

hook_entity_presave()

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/function/hook_entity_presave/8.6.x

 

/**
 * Implements hook_entity_presave().
 */
function drupalbook_examples_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
  if ($entity->getEntityTypeId() == 'node' && $entity->getType() == 'article') {
    $entity->title->value = $entity->title->value . 'by ' . date('d-m-Y');
  }
}

Hook_entity_presave() срабатывает каждый раз когда сущность сохраняется. При этом вам не нужно вызывать $entity->save() внутри хука, потому что объект сущности будет изменен перед сохранением. В данном примере мы добавляем к заголовку статьи текущую дату сохранения ноды. Если на следующий день мы обновим статью, то новая дата добавится еще раз. Если не удалять из тайтла дату перед сохранением, то тайтл будет обрастать все новыми и новыми датами после пересохранения статьи. Чаще всего мы проверяем наличие определенных значений у полей в entity при сохранении или можно отправлять email с уведомлением об изменении статьи.

Заметьте, что мы проверяем сначала нужный тип сущности, потому что код в hook_entity_presave() срабатывает для всех сущностей: контента, блоков, комментариев, терминов таксономии. И также проверяем нужный нам бандл ноды.

hook_entity_insert()

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/function/hook_entity_insert/8.6.x

/**
 * Implements hook_entity_insert().
 */
function drupalbook_examples_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
  if ($entity->getEntityTypeId() == 'node' && $entity->getType() == 'page') {
    $node = Node::create([
      'type'        => 'article',
      'title'       => 'New page created: ' . $entity->title->value,
    ]);
    $node->save();
  }
}

Hook_entity_insert() вызывается когда добавляется новая сущность. Так например при создании новой страницы на сайте, будет создана статья. А если у вас есть и предыдущих хук, то будет добавлена дата к тайтлу созданной статьи.

Надо отметить, что есть различие между хуками hook_entity_insert() и hook_entity_presave(). Hook_entity_insert() срабатывает только один раз, когда добавлена сущность и не изменяет значения полей сущности. То есть если вы вставите код из первого хука во второй хук, то этот код не будет работать также:

/**
 * Implements hook_entity_insert().
 */
function drupalbook_examples_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
  if ($entity->getEntityTypeId() == 'node' && $entity->getType() == 'article') {
    $entity->title->value = $entity->title->value . 'by ' . date('d-m-Y');
  }
}

Вы, конечно, можете добиться сохранения значения ноды:

function your_module_entity_insert(Drupal\Core\Entity\EntityInterface $entity){
  if ($entity->getType() == 'article') {
    drupal_register_shutdown_function('_your_module_post_insert', $entity);
  }
}
 
function _your_module_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
  if ($entity) {
      $entity->save();
  }
}

Но этого не стоит делать, лучше всего использовать hook_entity_presave() для изменения самой сущности при сохранение, а hook_entity_insert() для изменения других сущностей или действий.

hook_entity_update()

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/function/hook_entity_update/8.2.x

/**
 * Implements hook_entity_update().
 */
function drupalbook_examples_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
  if ($entity->getEntityTypeId() == 'node' && $entity->getType() == 'page') {
    \Drupal::messenger()->addMessage('Page has been changed: ' . $entity->title->value);
  }
}

Hook_entity_update() срабатывает каждый раз, когда обновляется сущность. Тут сразу стоит отметить, что и здесь обновлять поля объекта $entity не стоит, этот хук как и hook_entity_insert() служит для вызова определенных действий не связанных с данными полей обновленной сущности. Этот хук стоит опять же использовать для логированния, отправления сообщений или других действий.

Также не стоит выполнять $entity->save(), чтобы не вызвать повторный вызов hook_entity_update(), а если вы все-таки это сделали, то позаботьтесь об выполнение условий при которых вы не будете вызывать $entity->save() и не произойдет зацикливание.

hook_entity_delete()

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/function/hook_entity_delete/8.6.x

Еще один хук для логирования и выполнения действий после удаления сущностей.

hook_entity_access()

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/function/hook_entity_access/8.2.x

 

use Drupal\Core\Access\AccessResult;
 
...
 
/**
 * Implements hook_entity_access().
 */
function drupalbook_examples_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account) {
  if ($entity->getEntityTypeId() == 'node' && $entity->getType() == 'article' && $operation == 'view' && in_array('administrator', $account->getRoles())) {
    AccessResult::forbidden();
  }
}

В этом примере мы закрываем доступ к статьям всем пользователям, у которых нет роли administrator. Конечно, если вам достаточно стандартных прав доступа по ролям, чтобы разграничить доступ к контенту, то лучше использовать настойки прав доступа. Hook_entity_access() нужен для гибкой настройки доступа к контенту, например по расписанию, выполнению определенных условий по userpoint, karma, user levels и т.д. Причем если вы делаете обычное ограничение доступа к определенному контенту через этот хук, переписывая стандартные права доступа, то это может вызвать непонимание у другого программиста, который продолжит заниматься проектом. Отсюда мы приходит к главному минусу хуков, это неочевидность выполнения стороннего кода. Если мы допустим не знаем о существование модуля с реализованными хуками, то для нас будет загадкой, почему наш тайтл у статьи меняется при сохранение. Конечно, мы знаем что хуки существуют и поэтому каждый раз, когда вы сталкиваетесь с подобным поведением, например, при обновление ноды, то вам приходится искать по всему проекту "entity_presave" или "entity_update". Зачастую программисты следуют code guide и оставляют комментарии о хуках "Implements hook_entity_presave()", так что можно искать и по названию хука, но стоит учитывать что не все программисты следуют code guide, особенно если вам достался проект от прошлой комманды, которая завалила проект.

Мы рассмотрели только несколько хуков, но я думаю что у вас должно сформироваться определенное понимание того, где использовать хуки. Чем больше вы будете делать задач по друпалу, тем чаще вы будете сталкиваться с необходимостью писать кастомный код. Если какой-то из хуков выглядит удачным местом для добавления кода, то можете смело его реализовать в вашем кастомном модуле.