如何为自定义CMS实现类似WordPress的hooks钩子功能?
Hooks 实现原理与架构模式
WordPress的Actions、Filters这类钩子机制本质是观察者模式的工程化实现,核心逻辑是核心代码提前在固定执行节点预埋触发点,不需要提前感知扩展逻辑的存在,运行到对应节点时批量执行预先注册的自定义回调即可,两类钩子的核心区别如下:
- Actions(动作钩子):无返回值,仅用于在指定执行时机运行额外逻辑,比如页面页脚渲染完成后插入统计代码
- Filters(过滤器钩子):会接收核心代码传入的待处理值,所有注册的回调按优先级依次处理后返回修改后的值,核心代码最终使用处理后的值继续执行,比如修改文章输出的正文内容
PHP 版本核心钩子类实现示例
class HookManager { // 存储所有注册的动作钩子回调 private array $actions = []; // 存储所有注册的过滤器钩子回调 private array $filters = []; /** * 注册动作钩子 * @param string $hookName 钩子唯一标识 * @param callable $callback 自定义回调函数 * @param int $priority 执行优先级,数值越小越先执行 */ public function addAction(string $hookName, callable $callback, int $priority = 10): void { $this->actions[$hookName][$priority][] = $callback; // 按优先级排序当前钩子下的所有回调 ksort($this->actions[$hookName]); } /** * 触发指定动作钩子 * @param string $hookName 钩子唯一标识 * @param mixed ...$args 传给回调的自定义参数 */ public function doAction(string $hookName, mixed ...$args): void { if (!isset($this->actions[$hookName])) return; foreach ($this->actions[$hookName] as $priorityCallbacks) { foreach ($priorityCallbacks as $callback) { call_user_func_array($callback, $args); } } } /** * 注册过滤器钩子 * @param string $hookName 钩子唯一标识 * @param callable $callback 自定义回调函数 * @param int $priority 执行优先级,数值越小越先执行 */ public function addFilter(string $hookName, callable $callback, int $priority = 10): void { $this->filters[$hookName][$priority][] = $callback; ksort($this->filters[$hookName]); } /** * 应用指定过滤器钩子 * @param string $hookName 钩子唯一标识 * @param mixed $value 待处理的原始值 * @param mixed ...$args 传给回调的额外参数 * @return mixed 处理完成的最终值 */ public function applyFilters(string $hookName, mixed $value, mixed ...$args): mixed { if (!isset($this->filters[$hookName])) return $value; foreach ($this->filters[$hookName] as $priorityCallbacks) { foreach ($priorityCallbacks as $callback) { $params = array_merge([$value], $args); $value = call_user_func_array($callback, $params); } } return $value; } }
核心代码预埋钩子示例
// 项目中一般将HookManager做成全局单例调用 $hookManager = new HookManager(); // 文章渲染核心逻辑 $originContent = "这是文章原始内容"; // 渲染前预埋过滤器,允许扩展修改文章内容 $finalContent = $hookManager->applyFilters('the_content', $originContent, $postId); echo $finalContent; // 页面底部渲染完成后预埋动作钩子 $hookManager->doAction('footer_end');
扩展端自定义逻辑示例
// 注册过滤器,给所有文章末尾追加版权信息 $hookManager->addFilter('the_content', function($content) { return $content . "<p style='margin-top:20px;color:#999'>© 2024 版权所有</p>"; }); // 注册动作,在页脚插入自定义脚本 $hookManager->addAction('footer_end', function() { echo "<script>console.log('自定义统计逻辑执行')</script>"; });
JS 版本核心钩子类实现示例
class HookManager { constructor() { this.actions = {}; this.filters = {}; } addAction(hookName, callback, priority = 10) { if (!this.actions[hookName]) this.actions[hookName] = {}; if (!this.actions[hookName][priority]) this.actions[hookName][priority] = []; this.actions[hookName][priority].push(callback); // 按优先级排序回调 this.actions[hookName] = Object.fromEntries( Object.entries(this.actions[hookName]).sort(([a], [b]) => Number(a) - Number(b)) ); } doAction(hookName, ...args) { if (!this.actions[hookName]) return; Object.values(this.actions[hookName]).forEach(callbacks => { callbacks.forEach(callback => callback(...args)); }); } addFilter(hookName, callback, priority = 10) { if (!this.filters[hookName]) this.filters[hookName] = {}; if (!this.filters[hookName][priority]) this.filters[hookName][priority] = []; this.filters[hookName][priority].push(callback); this.filters[hookName] = Object.fromEntries( Object.entries(this.filters[hookName]).sort(([a], [b]) => Number(a) - Number(b)) ); } applyFilters(hookName, value, ...args) { if (!this.filters[hookName]) return value; Object.values(this.filters[hookName]).forEach(callbacks => { callbacks.forEach(callback => { value = callback(value, ...args); }); }); return value; } }
JS 使用示例
const hookManager = new HookManager(); // 扩展注册逻辑:修改页面标题后缀 hookManager.addFilter('page_title', (title) => { return `${title} - 我的站点`; }); // 核心代码调用 let title = '文章详情页'; title = hookManager.applyFilters('page_title', title); document.title = title;
这套架构完全实现了核心代码和扩展代码的解耦,不需要修改核心逻辑,只需要在合适的节点预埋钩子,后续所有自定义功能都可以通过注册回调的方式实现,实际生产环境可以额外补充移除钩子、校验回调合法性、获取已注册钩子列表等辅助方法即可。
内容的提问来源于stack exchange,提问作者zzemotaDi
相关产品推荐
相关产品推荐

