# Hook 机制
# Hook 说明
BeikeShop 采用类似 WordPress 的hook机制, 包括filter、action以及blade。 一般hook会搭配插件Plugin来使用。
在访问 BeikeShop 任意页面的生命周期内,系统提供了一个 hook_filter 和 hook_action 函数, 进行一些额外的操作。
hook_action 用来执行额外的函数,而 hook_filter 则是用来对输出的内容进行处理。 每一个特定的 hook 都有预定义好的名字,如产品详情页的产品数据的hook就叫product.show.data。
BeikeShop 在系统初始化的时候,插件可以将自己的处理函数用 add_hook_filter 或 add_hook_action 注册到特定名称的 hook 上, 等到页面执行到 hook_filter
或者 hook_action
时,就会调用相应的处理函数。
BeikeShop 在很多 hook 位置上都设定了默认的处理函数,如果您发现某处地方需要修改但是没有合适的地方可以注入或修改,可以联系我们或者提交PR。
另外, blade hook 是指可以在插件中修改blade模板, 当然前提是在对应的blade模板中已经定义好了 @hook 或者 @hookwrapper
# Hook 分类
# 1. 数据 Hook: add_hook_filter
- 系统中对应的埋点方法: hook_filter, 可以不关注此方法。
- 该 hook 主要用于修改系统中特定方法返回的数据, 且修改后的数据会返回给系统使用, 比如前台产品详情页的数据, 后台顶部导航菜单。 用法实例:
// 首页头部新增一个链接菜单
add_hook_filter('menu.content', function ($data) {
$data[] = [
'name' => '新增Google菜单',
'link' => 'https://www.google.com',
];
return $data;
}, 0);
# 2. 流程 Hook: add_hook_action
- 系统中对应的埋点方法: hook_action, 可以不关注此方法。
- 该 hook 主要用于修改流程, 比如订单下单成功后推送到其他 ERP 系统。
add_hook_action('checkout.order.confirm.after', function ($data) {
dump($data);
// 这里可以推送到你的 ERP 系统
}, 0);
# 3. 模板 Hook: add_hook_blade
- 系统中blade模板嵌入埋点方法:
@hook或者@hookwrapper/@endhookwrapper, 可以不关注这两个标签。 - 该 hook 用于修改 blade 模板, 比如在前台顶部电话前面加一句话如下所示:
- 在系统中
/themes/default/layout/header.blade.php中已经预埋hookwrapper:
<div class="right nav">
@hookwrapper('header.top.telephone')
<span class="px-2"><i class="bi bi-telephone-forward me-2"></i> {{ system_setting('base.telephone') }}</span>
@endhookwrapper
</div>
- 在你自己的插件中添加以下代码:
add_hook_blade('header.top.telephone', function ($callback, $output, $data) {
return '电话前' . $output;
});
- 效果如下:

# Hook 命名规则
TIP
模块一般对应类名, 操作是类的方法
# 前台 Filter
- Controller: hook_filter(模块.操作.data)
- Repository: hook_filter(repo.模块.操作.data)
- Service: hook_filter(service.模块.操作.data)
# 前台 Action
- Controller: hook_action(模块.操作.before|after)
- Repository: hook_action(repo.模块.操作.before|after)
- Service: hook_action(service.模块.操作.before|after)
# 前台 Blade
- @hook
- @hook('模块.页面.页面标志.before|after|left|right'), 比如 @hook('product.detail.buy.after')
- @hookwrapper
- @hookwrapper('模块.页面.页面标志'), 比如 @hookwrapper('header.top.currency')
# 后台 Filter|Action|Blade
统一在前台规则前面添加 admin.
# Hook 使用
在插件根目录创建 Bootstrap 类(文件名为Bootstrap.php), 代码如下:
namespace Plugin\你的插件目录名称;
class Bootstrap
{
public function boot()
{
// 这里使用 add_hook_filter、add_hook_action 或者 add_hook_blade 方法修改系统数据、流程或者页面
// 比如上面的三种hook实例之一
add_hook_blade('header.top.telephone', function ($callback, $output, $data) {
return '电话前' . $output . '电话后';
});
}
}
# 实例讲解
比如, 我们想在产品的详情页产品名字加个前缀 - 疯狂热销, 步骤如下:
# 1. 新建插件
创建文件夹ProductPrefix, 并创建 config.json 和 Bootstrap.php 两个文件

插件基本信息 config.json 内容如下,也可以按照你自己的想法修改
【注意:code 不能与线上插件有相同,否则上传插件配置时会报错提示:This plugin is not authorized. Please purchase it in the plugin market.】
{
"code": "product_prefix",
"name": "产品名前缀",
"description": "修改产品详情页产品名称前缀",
"type": "feature",
"version": "v1.0.0",
"icon": "",
"author": {
"name": "成都光大网络科技有限公司",
"email": "[email protected]"
}
}
其中,type表示插件类型,目前系统预定义的类型有8种,分别是:
[
'payment', // 支付方式
'shipping', // 配送方式
'theme', // 主题模板
'analysis', // 数据分析
'service', // 客户服务
'marketing', // 营销推广
'language', // 语言翻译
'translator', // 翻译工具
'feature', // 其他功能
]
启动文件 Bootstrap.php 内容如下
<?php
/**
* bootstrap.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <[email protected]>
* @created 2022-07-20 15:35:59
* @modified 2022-07-20 15:35:59
*/
namespace Plugin\ProductPrefix;
class Bootstrap
{
public function boot()
{
// 通过数据 hook 修改产品详情页产品名称
add_hook_filter('product.show.data', function ($product) {
$product['product']['name'] = '[疯狂热销]'. $product['product']['name'];
return $product;
});
// 修改产品详情页blade模板, 在立即购买后添加一个按钮
add_hook_blade('product.detail.buy.after', function ($callback, $output, $data) {
$view = '<button class="btn btn-dark ms-3 fw-bold"><i class="bi bi-bag-fill me-1"></i>新增按钮</button>';
return $output . $view;
});
}
}
# 2. 进入后台安装并启用插件
依次点击系统设置 - 插件列表 - 安装 - 启用

# 3. 查看产品详情
可以看到,产品名称前面多了 [疯狂热销] 前缀, Buy Now 按钮之后也添加了一个 新增按钮。

这样,我们就在不改变核心代码的情况下修改了产品详情页产品名称以及添加了一个按钮。
# 动态 Fillable
在 License 2.0.0.8 和 免费版 1.6.0.8 中,BeikeShop 的 beike/Models/Base.php 增加了一项新特性:支持通过 Hook 动态扩展模型的 $fillable 属性,无需修改核心代码即可让插件实现字段写入。
# 核心实现逻辑如下:
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$hookKey = 'model.fillable:' . static::class;
$extraFillable = hook_filter($hookKey, []);
if (!is_array($extraFillable)) {
$extraFillable = [];
}
$this->fillable = array_values(array_unique(array_merge($this->fillable, $extraFillable)));
}
# 插件如何扩展某个模型的 Fillable?
当你的插件新增字段,并希望通过 Model::create() / update() 等方法写入该字段时,只需要添加一个 hook_filter 即可,例如为购物车商品模型增加 option 字段:
# Hook 命名规则
| Hook Key 结构 | 示例 |
|---|---|
model.fillable:{模型完整类名} | model.fillable:Beike\Models\CartProduct |
Hook 应返回一个数组,数组中每个元素代表一个动态扩展的关联配置。
add_hook_filter('model.fillable:Beike\Models\CartProduct', function () {
return ['option'];
});
# 动态扩展模型关系(Relation)
在 License 2.0.0.9 和 免费版 1.6.0.9 中,BeikeShop 在 beike/Models/Base.php 中新增了对模型关联(Laravel Eloquent Relations)的动态扩展机制。
通过 Hook,插件开发者可以在不修改核心代码的情况下,为任意模型增加自定义的 hasOne、hasMany、belongsTo 等关联关系。
# 核心实现逻辑
protected static function booted(): void
{
parent::boot();
static::addGlobalScope(new SelectScope());
/**
* 动态扩展模型关系(Relation)
* Hook Key:model.relations:{模型类名}
* 插件可通过 return ['relationName' => function ($model) { return $model->xxx(...); }];
*/
$hookKey = 'model.relations:' . static::class;
$relations = hook_filter($hookKey, []);
if (is_array($relations)) {
foreach ($relations as $name => $callback) {
if (is_string($name) && is_callable($callback)) {
static::resolveRelationUsing($name, function ($model) use ($callback) {
return $callback($model);
});
}
}
}
}
# 插件如何扩展模型的关联关系?
当插件需要为系统中的模型动态增加一个关联(Relation),例如新增一个 hasOne、hasMany 或 belongsTo 的关系,而不希望修改核心模型代码时,可以通过 Hook 实现动态扩展。
# Hook 命名规则
| Hook Key 结构 | 示例 |
|---|---|
model.relations:{模型完整类名} | model.relations:Beike\Models\Order |
Hook 应返回一个数组,数组中每个元素代表一个动态扩展的关联配置。
# 插件注册示例
add_hook_filter('model.relations:Beike\Models\Order', function ($relations) {
$relations['options'] = function ($model) {
return $model->hasMany(GdOrderOption::class, 'order_id', 'id');
};
return $relations;
});
# 使用方式示例
$order = Order::with('options')->find(1);
// 访问插件扩展的关联关系
$order->options;