关于 drupal-hooks
面向 Drupal 模块开发者的 Drupal 11 Hook 开发参考,优先介绍 `src/Hook/` 中可测试的面向对象 Hook,也说明过程式 Hook 及事件订阅器的适用场景。
主要功能
- 01通过属性定义面向对象 Hook
- 02使用构造函数依赖注入
- 03利用 Drupal 11.1+ 自动注册
- 04为 `src/Hook/` 外的 Hook 手动注册
- 05比较 Hook 与事件订阅器
适用场景
- 01实现新的 Drupal 模块 Hook
- 02将过程式 Hook 重构为类
- 03选择 Hook 或事件订阅器
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ edutrul/ drupal-ai --skill drupal-hooks安装后在 Claude Code 中输入/drupal-hooks确认已加载。
附带文件README.md
详细介绍
Drupal Hooks(Drupal 11)
OOP Hooks(首选)
| 位置 | src/Hook/MyModuleHooks.php |
| 命名空间 | Drupal\my_module\Hook |
| 自动注册 | Drupal 11.1+(无需 services.yml) |
| DI 支持 | 构造函数注入 |
| 可测试 | 是 — 可直接实例化并注入模拟对象 |
示例(最佳实践)
<?php
namespace Drupal\my_module\Hook;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\node\NodeInterface;
final class MyModuleHooks {
use StringTranslationTrait; // Provides $this->t().
public function __construct(
private readonly AccountProxyInterface $currentUser,
) {}
/**
* Implements hook_form_node_article_form_alter().
*
* Prefer targeted form hooks over generic form_alter — they only fire for
* the specific form ID and avoid unnecessary processing.
*/
#[Hook('form_node_article_form_alter')]
public function formNodeArticleFormAlter(array &$form, FormStateInterface $form_state): void {
$form['title']['#title'] = $this->t('Article title');
}
/**
* Implements hook_form_alter().
*
* Use only when acting on multiple forms or the form ID is unknown at
* development time. Check $form_id explicitly.
*/
#[Hook('form_alter')]
public function formAlter(array &$form, FormStateInterface $form_state, string $form_id): void {
if ($form_id === 'node_page_form') {
$form['title']['#description'] = $this->t('Enter a descriptive page title.');
}
}
#[Hook('node_presave')]
public function nodePresave(NodeInterface $node): void {
if ($node->getType() === 'article') {
// Example: stamp the current user's ID on save.
$node->set('uid', $this->currentUser->id());
}
}
#[Hook('theme')]
public function theme(): array {
return [
'my_template' => [
'variables' => ['content' => NULL],
],
];
}
}
注意: 如果钩子未执行,请检查命名空间和位置。
src/Hook/之外的 OOP 钩子需要手动注册服务。
services.yml
| 场景 | 是否需要? |
|---|---|
类位于 src/Hook/,命名空间为 Drupal\my_module\Hook | 否 — 自动注册(Drupal 11.1+) |
类位于 src/Hook/ 之外(例如自定义服务) | 是 — 手动注册 |
需要注册时,autowire 会根据类型提示解析构造函数依赖项:
namespace Drupal\my_module\Service;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Session\AccountProxyInterface;
final class MyCustomService {
public function __construct(
private readonly AccountProxyInterface $currentUser,
) {}
#[Hook('form_alter')]
public function formAlter(...): void {
// ...
}
}
services:
Drupal\my_module\Service\MyCustomService:
autowire: true
程序式钩子(.module)
通过 my_module_hook_name() 命名方式自动发现。仍然有效;新代码优先使用 OOP 钩子。
钩子与事件订阅器
| 使用钩子的场景 | 使用事件订阅器的场景 |
|---|---|
form_alter、实体钩子、主题钩子 | 响应已分发的 Symfony 事件 |
| 扩展 Drupal 核心/贡献模块的行为 | PSR-14 / 解耦系统 |
| 使用现有 Drupal API | 构建松耦合逻辑 |
src/Hook/中的 OOP 钩子完全可测试 — 可直接实例化类并注入模拟依赖项。
规则(重要)
- 对于新的实现,始终优先在
src/Hook/中使用 OOP 钩子 - 除非明确要求,否则绝不要在
.module中添加新钩子 - 如果存在程序式钩子,建议将其重构为 OOP
评论
支持 Markdown