本文目录导读:

在PHP项目中,模板方法(Template Method) 和钩子(Hooks) 是两种常见的扩展机制,尤其在CMS(如WordPress、Drupal)和MVC框架中广泛应用,下面我将详细解析它们的原理、实现方式及典型场景。
模板方法模式(Template Method Pattern)
核心概念
定义一个操作的算法骨架,将某些步骤延迟到子类中实现,父类控制流程,子类实现细节。
典型结构
abstract class MakeDrink {
// 模板方法 - 不可重写
final public function prepareRecipe() {
$this->boilWater();
$this->brew(); // 抽象方法,子类实现
$this->pourInCup();
$this->addCondiments(); // 钩子方法(可选)
}
protected function boilWater() { // 通用步骤
echo "烧开水\n";
}
protected function pourInCup() {
echo "倒入杯中\n";
}
// 抽象方法 - 子类必须实现
abstract protected function brew();
// 钩子方法 - 默认空实现,子类可选覆盖
protected function addCondiments() {}
}
class Tea extends MakeDrink {
protected function brew() {
echo "冲泡茶叶\n";
}
// 不重写addCondiments - 纯茶
}
class Coffee extends MakeDrink {
protected function brew() {
echo "研磨咖啡豆\n";
}
protected function addCondiments() { // 覆盖钩子
echo "加糖加奶\n";
}
}
适用场景
- 多个子类有相同步骤但部分实现不同
- 希望控制核心流程,同时提供扩展点
- 常见于框架的控制器基类(如Laravel的Controller)
钩子系统(Hooks)
核心概念
在代码的特定位置预留回调接口,允许外部代码插入自定义逻辑,分为动作钩子和过滤钩子。
典型实现
1 简单钩子管理器
class HookManager {
protected static $hooks = [];
// 注册钩子
public static function add($hookName, $callback, $priority = 10) {
self::$hooks[$hookName][$priority][] = $callback;
ksort(self::$hooks[$hookName]); // 按优先级排序
}
// 执行动作钩子
public static function action($hookName, ...$args) {
if (isset(self::$hooks[$hookName])) {
foreach (self::$hooks[$hookName] as $hooks) {
foreach ($hooks as $callback) {
call_user_func_array($callback, $args);
}
}
}
}
// 执行过滤钩子
public static function filter($hookName, $value, ...$args) {
if (isset(self::$hooks[$hookName])) {
foreach (self::$hooks[$hookName] as $hooks) {
foreach ($hooks as $callback) {
$value = call_user_func_array($callback, array_merge([$value], $args));
}
}
}
return $value;
}
}
2 使用示例
// 插件端 - 注册钩子
HookManager::add('before_save', function($data) {
return '【加密】' . $data;
}, 5);
HookManager::add('before_save', function($data) {
return $data . '【校验】';
}, 10);
// 主程序端 - 触发钩子
$data = '用户提交数据';
$data = HookManager::filter('before_save', $data);
echo $data; // 输出: 【加密】用户提交数据【校验】
对比表格
| 特性 | 模板方法模式 | 钩子系统 |
|---|---|---|
| 控制权 | 父类控制流程 | 外部代码任意扩展 |
| 调用方式 | 继承 + 方法重写 | 注册 + 回调触发 |
| 扩展性 | 编译时决定(类继承) | 运行时动态扩展 |
| 典型应用 | 框架核心流程(如ORM查询) | CMS插件系统(如WordPress) |
| 风险 | 子类污染父类逻辑 | 钩子顺序导致的副作用 |
现实项目中的组合应用
很多成熟框架会混合使用两种模式:
案例:Laravel Eloquent模型事件
// 模板方法 - Model基类控制CRUD流程
class Model {
public function save(array $options = []) {
// 1. 触发creating/updating钩子
$this->fireModelEvent('creating', false);
// 2. 核心保存逻辑(模板方法)
$result = $this->performSave();
// 3. 触发created/updated钩子
$this->fireModelEvent('created', false);
return $result;
}
}
// 子类重写模板方法
class User extends Model {
public function performSave() {
// 自定义保存逻辑
}
}
// 外部通过钩子扩展
User::creating(function($user) {
$user->password = bcrypt($user->password);
});
案例:WordPress插件系统
// 核心系统(模板方法)
function wp_insert_post($data) {
// 钩子:过滤输入
$data = apply_filters('wp_insert_post_data', $data);
// 核心逻辑(模板)
$post_id = $wpdb->insert(...);
// 钩子:通知插件
do_action('wp_insert_post', $post_id, $data);
}
// 插件通过钩子扩展
add_filter('wp_insert_post_data', 'my_custom_validation');
add_action('wp_insert_post', 'my_after_save_notification');
最佳实践建议
-
模板方法:
- 使用
final修饰主方法防止子类破坏流程 - 提供默认实现的钩子方法时保持“钩子”语义清晰
- 避免在父类中出现过于复杂的依赖关系
- 使用
-
钩子系统:
- 为钩子命名时遵循
对象_动作约定(如user_created) - 优先使用松散耦合的钩子,避免修改核心逻辑
- 在文档中明确标注钩子的触发时机和参数
- 为钩子命名时遵循
-
混合策略:
- 用模板方法定义核心业务流程
- 在关键节点嵌入钩子,允许第三方扩展
- 将钩子管理设计为可注入的依赖(如事件调度器)
通过合理组合这两种模式,你可以构建出既有明确流程控制,又具备极强扩展性的PHP项目,模板方法保证了核心业务的稳定性,而钩子系统则为灵活扩展提供了安全通道。