logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动
16/10/2025, by Ivan

在之前的文章中,我们已经接触过 钩子(hooks)。在本文中,我们将更深入地了解那些用于操作实体(Entity)的钩子。

你可以先阅读这篇文章,了解钩子的概念及其用途:

http://drupalbook.org/drupal/92-what-hook-drupal-8

我们将使用钩子在与实体相关的事件(添加、删除、更新等)触发时执行自定义代码。

所有 Drupal 的钩子都可以在此页面上查看:

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(),因为在保存之前实体对象会被自动修改。在上面的示例中,我们在文章标题后追加了当前保存日期。如果第二天再次更新文章,将会再次追加新的日期。如果在保存之前不删除旧日期,标题会越来越长。此钩子最常用于在保存时验证字段值,或在内容更改时发送邮件通知。

注意,我们首先检查实体类型,因为 hook_entity_presave() 对所有实体类型(内容、区块、评论、术语等)都会执行。同样,我们还应检查实体的 bundle。

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_presave(),那么生成的文章标题中也会追加日期。

需要注意的是,hook_entity_insert()hook_entity_presave() 不同。前者仅在实体被添加时触发一次,并且不会修改字段值。如果将第一个钩子的代码放到第二个中,是不会生效的:

/**
 * 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() 每次实体更新时都会触发。需要注意的是,不要在此钩子中修改实体字段数据。与 hook_entity_insert() 类似,它应仅用于记录日志、发送通知或执行其他操作。

同样,不要在其中调用 $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

<?php

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() 更适用于复杂的访问控制,例如按时间、积分、等级等条件动态控制访问权限。

然而,滥用此钩子可能导致代码可读性下降。如果项目中存在多个实现此钩子的模块,其他开发者可能难以追踪为什么某个行为发生(例如标题被修改或访问被限制)。

我们这里只讲解了几个常见钩子,但希望你已大致理解如何使用它们。随着你在 Drupal 中开发的深入,你会越来越多地使用钩子来编写自定义逻辑。当你发现某个钩子正好能实现你的业务需求时,就可以放心地在自定义模块中实现它。