挂钩和过滤器参考:完整的开发人员指南
挂钩和过滤器参考
受众: 开发人员 — 需要 PHP / Laravel 知识。 类别: 技术参考 — 集成开发人员的技术文档。
你将学到什么
-
了解PolyCMS中的Action vs Filter机制
-
查找钩子名称、参数和调度位置
-
注册主题
functions.php和模块ServiceProvider的钩子 -
使用优先级来控制执行顺序
-
为每个钩子组应用实际用例
1. Hook系统简介
PolyCMS 使用类似 WordPress 的 Hook/Filter 系统,在 Laravel 上原生构建:
-
Action (
Hook::doAction):执行副作用(记录、发送电子邮件、同步数据)。不需要返回值。 -
Filter (
Hook::applyFilters):接收一个值,修改它,并且必须返回修改后的值。
基本语法
use App\Facades\Hook;
// Register an Action
Hook::addAction('order_completed', function ($order) {
Mail::to($order->user)->send(new OrderCompletedMail($order));
}, priority: 10);
// Register a Filter
Hook::addFilter('post.default_image', function (?string $imageUrl, $post) {
if ($post?->categories->contains('slug', 'news')) {
return '/images/news-default.jpg';
}
return $imageUrl;
}, priority: 10);
2. 内容和帖子
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
post.default_image |
$imageUrl, $context |
帖子没有特色图片时的默认图片 |
post.frontend_url |
$url, $post |
自定义帖子的公共 URL |
post.content.render |
$html, $post |
返回 API 之前过滤帖子 HTML 内容 |
post.query.builder |
$query, $request |
修改帖子列表的 Eloquent 查询 |
category.frontend_url |
$url, $category |
自定义类别的公共 URL |
content.render.blocks |
$blocks |
渲染前过滤块数组 |
content.render.html |
$html, $blocks |
过滤最终输出HTML |
content.render.block.{type} |
$html, $block |
渲染或覆盖特定块类型 |
行动
| 钩 | 参数 | 描述 |
|---|---|---|
tag.saved |
$tag, $context |
创建/更新标签后触发 |
tag.deleted |
$tag, $context |
删除标签后触发 |
category.saved |
$category, $context |
创建/更新类别后触发 |
category.deleted |
$category, $context |
删除类别后触发 |
实际用例
UC1 - 特定于类别的默认缩略图: 当帖子没有特色图像时,为每个类别保留特定的默认缩略图:
Hook::addFilter('post.default_image', function (?string $url, $post) {
if ($post?->categories->contains('slug', 'tech')) return '/images/defaults/tech.jpg';
if ($post?->categories->contains('slug', 'lifestyle')) return '/images/defaults/lifestyle.jpg';
return $url; // fallback to admin setting
});
UC2 — Wiki URL 结构: 将帖子 URL 结构从 /posts/slug 降级/更改为 /docs/slug 以实现 wiki 风格的布局:
Hook::addFilter('post.frontend_url', function ($url, $post) {
return ($post->type === 'wiki') ? '/docs/' . $post->slug : $url;
});
UC3 — 自动插入内嵌广告: 在帖子的第三段后自动插入广告横幅块:
Hook::addFilter('content.render.html', function (string $html) {
$adBanner = '<div class="ad-inline">Advertisement</div>';
$parts = explode('</p>', $html, 4);
if (count($parts) > 3) {
$parts[2] .= '</p>' . $adBanner;
return implode('</p>', $parts);
}
return $html;
});
3.媒体库
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
media.upload.file |
$file, $data |
处理前修改上传文件 |
media.upload.data |
$data, $file |
修改上传元数据 |
media.create.data |
$mediaData, $file |
保存到数据库之前修改媒体记录属性 |
media.delete.should |
$shouldDelete,$media |
安全门:返回“false”以阻止媒体删除 |
media.url |
$url, $media |
重写 URL(例如,重定向到 CDN) |
行动
| 钩 | 参数 | 描述 |
|---|---|---|
media.uploaded |
$media, $file, $data |
上传成功后触发 |
media.deleting |
$media |
删除前触发 |
media.deleted |
$media |
删除后触发 |
实际用例
UC1 — CDN URL 重写: 与 Cloudflare R2 或 Amazon S3 集成,自动将本地存储路径转换为 CDN URL:
Hook::addFilter('media.url', function (string $url, $media) {
return str_replace('/storage/', 'https://cdn.mysite.com/', $url);
});
UC2 — 保护关键系统资产: 防止意外删除当前用作站点徽标的任何图像:
Hook::addFilter('media.delete.should', function (bool $allow, $media) {
$logoUrl = get_option('site_logo', null, 'general');
return ($media->url === $logoUrl) ? false : $allow;
});
UC3 — 上传时自动优化图像: 调度后台队列以从标准上传生成轻量级 WebP 图像:
Hook::addAction('media.uploaded', function ($media, $file) {
if (str_starts_with($media->mime_type, 'image/')) {
dispatch(new ConvertToWebpJob($media));
}
});
4. 电子商务 — 订单和退款
行动
| 钩 | 参数 | 描述 |
|---|---|---|
order_status_updated |
$order, $oldStatus, $newStatus |
订单状态变化时触发 |
order_completed |
$order |
订单成功完成时触发 |
order.refund.processing |
$order, $validated |
在处理退款之前触发 |
order.refund.completed |
$order, $result |
退款成功后触发 |
order.refund.succeeded |
$order, $result, $validated, $userId |
API退款成功 |
order.refund.failed |
$order, $validated, $exception, $userId |
API退款失败 |
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
order.refund.preview.result |
$preview, $order, $input |
修改退款预览数据 |
实际用例
UC1 — 新订单的电报通知:
Hook::addAction('order_status_updated', function ($order, $old, $new) {
if ($old === 'pending' && $new === 'processing') {
TelegramBot::send(" Order #{$order->code} has been confirmed!");
}
});
UC2 — 订单完成时的忠诚奖励积分:
Hook::addAction('order_completed', function ($order) {
if ($order->user_id) {
$points = (int) floor($order->total / 10); // 1 point per $10 spent
LoyaltyService::addPoints($order->user_id, $points, "Order #{$order->code}");
}
});
UC3 — 退款失败的审核日志:
Hook::addAction('order.refund.failed', function ($order, $data, $exception, $userId) {
AuditLog::create([
'action' => 'refund_failed',
'order_id' => $order->id,
'user_id' => $userId,
'reason' => $exception->getMessage(),
]);
});
5. 电子商务 — 购物车、运输、税收、库存
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
cart.totals |
$totals, $cart |
修改购物车总计 |
shipping.calculate_cost |
$cost, $method, $cart |
覆盖运费 |
shipping.available_methods |
$methods, $address, $cart |
根据地址和购物车内容过滤运输方式 |
tax.calculated |
$result, $subtotal, $address |
覆盖计算的税务结果 |
inventory.is_stockable_product |
$default, $product, $context |
检查产品是否跟踪库存水平 |
review.can_submit |
$allowed, $user, $product |
安全门:允许用户提交评论? |
行动
| 钩 | 参数 | 描述 |
|---|---|---|
cart.item.added |
$item, $cart |
将商品添加到购物车后触发 |
cart.item.updated |
$item, $oldQty, $newQty |
商品数量变化后触发 |
cart.item.removed |
$item, $cart |
删除项目后触发 |
cart.cleared |
$cart |
清空购物车后触发 |
cart.merged |
$userCart, $guestCart |
当访客购物车合并到经过身份验证的用户购物车时触发 |
review.submitted |
$review |
提交评论后触发 |
review.approved |
$review |
批准审核后触发 |
review.rejected |
$review |
拒绝审核后触发 |
实际用例
UC1 — 订单超过 100 美元免运费:
Hook::addFilter('shipping.calculate_cost', function ($cost, $method, $cart) {
$subtotal = collect($cart->items)->sum(fn($i) => $i->price * $i->quantity);
return ($subtotal >= 100) ? 0 : $cost;
});
UC2 — 如果用户未购买产品则阻止评论:
Hook::addFilter('review.can_submit', function (bool $allowed, $user, $product) {
$hasPurchased = Order::where('user_id', $user->id)
->where('status', 'completed')
->whereHas('items', fn($q) => $q->where('product_id', $product->id))
->exists();
return $hasPurchased;
});
UC3 — AddToCart 事件的 Facebook 像素跟踪:
Hook::addAction('cart.item.added', function ($item, $cart) {
session()->push('fb_pixel_events', [
'event' => 'AddToCart',
'product_id' => $item->product_id,
'value' => $item->price,
]);
});
6. 电子商务——支付网关
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
payment.gateway.config_schema |
$schema, $gateway |
扩展网关的架构配置字段 |
实际用例:向银行转账网关添加自定义字段:
Hook::addFilter('payment.gateway.config_schema', function ($schema, $gateway) {
if ($gateway->code === 'bank_transfer') {
$schema['branch_code'] = [
'type' => 'text', 'label' => 'Branch Code', 'required' => false,
];
}
return $schema;
});
7. 主题和外观
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
theme.view.data |
$data, $viewName |
为任何 Blade 模板注入/修改视图变量 |
theme.template.resolve |
$viewName, $templateTheme, $entityType, $entity |
覆盖匹配的 Blade 视图 |
theme.template.registry |
$templates, $viewType |
注册新页面模板 |
theme.options.values |
$options |
过滤加载的主题选项值 |
theme.options.css_vars |
$cssVars, $themeOptionValues |
修改自定义 CSS 属性 |
theme.breadcrumbs.post |
$breadcrumbs, $post |
过滤帖子面包屑 |
theme.breadcrumbs.product |
$breadcrumbs, $product |
过滤产品面包屑 |
theme.show_page_header |
$show, $page |
切换页眉的可见性 |
frontend.topbar.banners |
$banners |
注入顶栏促销横幅 |
themes.list |
$themes |
过滤管理中可用主题的列表 |
行动
| 钩 | 参数 | 描述 |
|---|---|---|
theme.activated |
$theme |
激活主题时触发 |
theme.main.changed |
$theme, $oldMainTheme |
更改主题时触发 |
theme.installing |
$file |
在提取主题 ZIP 之前触发 |
theme.activating |
$slug, $type, $mode |
在激活主题之前触发 |
theme.deactivating |
$slug |
在停用主题之前触发 |
theme.deleting |
$theme |
删除主题前触发 |
cms_head |
(无) | 布局 <head> 标记内的输出挂钩 |
实际用例
UC1 — 从模块注入促销横幅:
Hook::addFilter('frontend.topbar.banners', function (array $banners) {
$banners[] = [
'text' => ' Flash Sale — 30% Off Today!',
'url' => '/sale',
'bg_color' => '#ff4444',
];
return $banners;
});
UC2 — Google Analytics 头部注入:
Hook::addAction('cms_head', function () {
$ga = get_option('google_analytics_id', null, 'seo');
if ($ga) {
echo "<script async src='https://www.googletagmanager.com/gtag/js?id={$ga}'></script>";
}
});
UC3 — 加载热门帖子侧边栏变量:
Hook::addFilter('theme.view.data', function ($data, $viewName) {
if ($viewName === 'posts.index') {
$data['popular_posts'] = Post::orderBy('views', 'desc')->limit(5)->get();
}
return $data;
});
8. 设置和配置
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
settings.defaults |
$defaults, $settingsService |
扩展或覆盖基本设置定义 |
settings.media.drivers |
$drivers |
注册新的媒体存储驱动程序 |
settings.permalinks.structure |
$structure, $settingsService |
过滤系统永久链接 |
行动
| 钩 | 参数 | 描述 |
|---|---|---|
setting.updating |
$key, $value, $group, $type |
保存设置前触发 |
settings.saved |
$payload |
设置保存后触发 |
实际用例
UC1 — 通过模块注册自定义设置部分:
Hook::addFilter('settings.defaults', function ($defaults) {
$defaults['mymodule'] = [
'mymodule_api_key' => [
'key' => 'mymodule_api_key', 'value' => '', 'type' => 'text',
'label' => 'API Key', 'description' => 'Enter your API key',
],
];
return $defaults;
});
UC2 — 清除永久链接上的路由缓存更新:
Hook::addAction('settings.saved', function ($payload) {
if (($payload['group'] ?? '') === 'permalinks') {
Artisan::call('route:clear');
Cache::tags('routes')->flush();
}
});
9. 用户、角色和身份验证
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
user.resource.to_array |
$data, $user, $request |
扩展用户API资源输出 |
auth.login.pre_token |
$response, $user, $request |
拦截登录生命周期(例如,用于多重身份验证) |
行动
| 钩 | 参数 | 描述 |
|---|---|---|
user.creating / user.updating / user.deleting |
`$user | $data` |
role.creating / role.updating / role.deleting / role.cloning |
`$role | $data` |
实际用例
UC1 — 多重身份验证拦截器:
Hook::addFilter('auth.login.pre_token', function ($response, $user, $request) {
if ($user->hasRole('admin') && !$request->filled('otp_code')) {
return response()->json(['requires_2fa' => true, 'user_id' => $user->id], 403);
}
return $response; // null = continue token generation
});
UC2 — 用户注册欢迎电子邮件:
Hook::addAction('user.creating', function ($data) {
dispatch(new SendWelcomeEmail($data['email'], $data['name']));
});
10. 搜索引擎优化
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
seo.canonical_url |
$url |
覆盖规范 URL |
seo.site_favicon |
$iconUrl |
覆盖图标路径 |
实际用例 — 多语言设置的规范 URL:
Hook::addFilter('seo.canonical_url', function (string $url) {
$locale = app()->getLocale();
if ($locale !== 'en') {
return url("/{$locale}" . parse_url($url, PHP_URL_PATH));
}
return $url;
});
11. 小部件
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
widget.render.instance |
$instance |
渲染前修改小部件实例设置 |
widget.render.{widget_type} |
$widget |
修改特定小部件数据 |
widget.area.render.instances |
$instances, $area |
过滤区域内加载的小部件实例 |
widget.render.output |
$html, $instance |
过滤单个小部件的 HTML 输出 |
widget.area.render.output |
$html, $area |
过滤小部件区域的渲染输出 |
widgets.types |
$widgets |
已注册小部件类型的过滤器列表 |
行动
| 钩 | 参数 | 描述 |
|---|---|---|
widgets.register_types |
$widgetManager |
注册新的小部件类型 |
widgets.register_areas |
$widgetManager |
注册新的小部件区域 |
实际用例 — 注册“商店地图”小部件类型:
Hook::addAction('widgets.register_types', function ($manager) {
$manager->registerType('store_map', [
'label' => 'Store Map',
'description' => 'Google Maps showing store locations',
'fields' => [
'api_key' => ['type' => 'text', 'label' => 'Google Maps API Key'],
'lat' => ['type' => 'text', 'label' => 'Latitude'],
'lng' => ['type' => 'text', 'label' => 'Longitude'],
],
'view' => 'widgets.store-map',
]);
});
12. 管理导航和编辑器
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
topbar.menu.items |
$items, $request, $user |
从前端管理栏中添加/删除项目 |
topbar.menu.should_show |
$show, $user |
切换前端顶部管理栏的可见性 |
admin.editor.panels |
$panels, $type, $user |
将自定义面板添加到块编辑器侧边栏 |
行动
| 钩 | 参数 | 描述 |
|---|---|---|
admin.menu.build |
(无) | 组装侧边栏导航菜单时触发 |
topbar.menu.context |
$request, $user |
前端管理栏初始化时触发 |
实际用例 - 将自定义 SEO 状态菜单项添加到顶栏:
Hook::addFilter('topbar.menu.items', function (array $items, $request, $user) {
$items[] = [
'key' => 'seo_score',
'label' => 'SEO Score',
'icon' => 'chart-bar',
'url' => '/admin/seo/score',
'position' => 50,
];
return $items;
});
13. 模块
过滤器
| 钩 | 参数 | 描述 |
|---|---|---|
module.resource.meta |
$meta, $moduleData |
扩展模块元数据详细信息 |
modules.list |
$modulesArray |
过滤管理中列出的模块 |
product.query.builder |
$query, $request |
修改产品 Eloquent 列表查询 |
行动
| 钩 | 参数 | 描述 |
|---|---|---|
module.activating |
$moduleKey, $module |
激活模块之前触发 |
module.deactivating |
$moduleKey, $module |
在停用模块之前触发 |
module.deleting |
$moduleKey, $module |
删除模块前触发 |
module.installing |
$uploadedFile |
上传新模块 ZIP 时触发 |
实际用例 - 在模块激活时自动运行迁移:
Hook::addAction('module.activating', function ($moduleKey, $module) {
Artisan::call('migrate', [
'--path' => "modules/Polyx/{$moduleKey}/database/migrations",
'--force' => true,
]);
});
14. 系统引导
行动
| 钩 | 参数 | 描述 |
|---|---|---|
roles.register_permissions |
$permissionRegistry |
注册新系统权限 |
register_email_templates |
$emailTemplateManager |
注册核心电子邮件模板 |
layout.register_assets |
$layoutAssetManager |
注册自定义管理/前端布局资源 |
routes.frontend.register |
(无) | 注入自定义前端路由 |
实际用例 - 模块注册自定义功能/权限:
Hook::addAction('roles.register_permissions', function ($registry) {
$registry->register('manage analytics', 'Analytics', 'View and manage analytics dashboard');
$registry->register('export reports', 'Analytics', 'Export analytics reports to CSV');
});
相关文章
-
Hooks & Filters 概述 — 事件系统总体概述
-
主题开发 — 自定义主题入门
-
模块开发 — 自定义模块入门
-
REST API 身份验证 — 对 API 客户端进行身份验证