什么是事件系统
ThinkPHP的事件系统是一种观察者模式的实现,它允许在特定时间点触发事件,并执行预先注册的监听器。事件系统提供了一种松耦合的方式来扩展应用功能,让你可以在不修改核心代码的情况下动态添加功能。
✅ 关键点:事件系统不是框架的核心功能,而是扩展机制,它让代码更灵活、更易维护。
事件系统的核心组件
1. 事件(Event)
-
标识符:代表特定时机或状态(如
HttpRun、UserLogin) -
命名规范:不强制要求,但建议使用有意义的英文单词(如
UserLogin、RequestEnd) -
系统内置事件(ThinkPHP 6.0+):
AppInit: 应用初始化时触发 HttpRun: HTTP应用开始运行时触发 HttpEnd: HTTP应用结束时触发 RouteLoaded: 路由加载完成时触发
2. 监听器(Listener)
-
执行逻辑:事件触发时执行的具体代码
-
三种类型:
类型 示例 说明 匿名函数 function() { ... }简单逻辑,适合临时使用 类方法 [SomeClass::class, 'methodName']适合有状态的逻辑 事件订阅者 UserSubscribe推荐方式,符合OOP原则
⚠️ 重要提示:监听器方法必须遵循
on+事件标识命名规范(如onUserLogin),这是ThinkPHP的约定!
3. 事件管理器(Event Manager)
- 负责管理事件与监听器的注册、触发和执行
- 通过
app->event实例访问
事件系统的核心使用方式(小白必看)
✅ 方式一:直接注册监听器(推荐用于简单场景)
正确写法(在服务提供者app/Provider/EventProvider.php中):
// 正确:使用匿名函数
$this->app->event->listen('HttpRun', function() {
// 在HTTP请求开始时执行
Log::info('HTTP请求开始');
});
// 正确:类方法(需遵循on+事件标识命名)
$this->app->event->listen('UserLogin', [UserListener::class, 'onUserLogin']);
❌ 错误写法:
$this->app->event->listen('UserLogin', [UserListener::class, 'userLogin']);
原因:方法名必须是onUserLogin,否则ThinkPHP无法识别
✅ 方式二:配置文件注册(推荐用于项目级扩展)
关键点:事件配置文件必须放在app/event.php(不是config/event.php!)
// app/event.php
return [
'listen' => [
// 事件标识 => 监听器列表
'HttpRun' => [
'app\listener\HttpRunListener', // 类名(自动识别onHttpRun方法)
'app\listener\AnotherListener',
],
'UserLogin' => [
'app\listener\UserLoginListener',
],
],
];
💡 重要提示:配置文件路径必须是
app/event.php,ThinkPHP默认会加载此文件
✅ 方式三:事件订阅者(最佳实践,推荐用于复杂场景)
正确实现(创建app/subscribe/UserSubscribe.php):
<?php
namespace app\subscribe;
use app\listener\UserLoginListener;
use app\listener\UserLogoutListener;
class UserSubscribe
{
// 事件订阅方法(必须命名为subscribe)
public function subscribe($event)
{
// 订阅多个事件
$event->listen('UserLogin', UserLoginListener::class);
$event->listen('UserLogout', UserLogoutListener::class);
}
}
注册订阅者(在app/event.php中):
return [
'subscribe' => [
// 订阅者类路径
app\subscribe\UserSubscribe::class,
],
];
🌟 为什么推荐订阅者:
- 遵循OOP原则
- 逻辑集中管理
- 便于团队协作
- 避免在服务提供者中写大量事件注册代码
事件触发与参数传递(小白最容易忽略的点)
触发事件
php
编辑
// 触发事件(可传递参数)
event('UserLogin', ['user_id' => 123, 'ip' => '127.0.0.1']);
监听器接收参数
// app/listener/UserLoginListener.php
namespace app\listener;
class UserLoginListener
{
public function onUserLogin($data)
{
// $data = ['user_id' => 123, 'ip' => '127.0.0.1']
Log::info("用户 {$data['user_id']} 登录,IP: {$data['ip']}");
}
}
💡 关键点:
event()的第二个参数是传递给监听器的参数,监听器方法必须接收这个参数
高级用法:通配符监听与优先级控制
1. 通配符监听(ThinkPHP 6.0.9+支持)
// 监听所有model相关的事件
$this->app->event->listen('model.*', function($event, $data) {
// $event = 'model.insert'、'model.update'等
Log::debug("模型事件: {$event}, 数据: " . json_encode($data));
});
2. 控制监听器执行顺序
// 通过配置文件控制顺序(数组顺序即执行顺序)
'listen' => [
'UserLogin' => [
'app\listener\FirstListener', // 优先执行
'app\listener\SecondListener', // 后执行
],
],
实际应用场景(小白必看)
案例1:日志记录(请求开始/结束)
// app/event.php
'listen' => [
'HttpRun' => [
'app\listener\RequestLogListener',
],
'HttpEnd' => [
'app\listener\RequestLogListener',
],
],
// app/listener/RequestLogListener.php
public function onHttpRun()
{
$this->start = microtime(true);
}
public function onHttpEnd()
{
$time = microtime(true) - $this->start;
Log::info("请求耗时: {$time}秒");
}
案例2:多应用扩展(think-multi-app)
// vendor/topthink/think-multi-app/src/Service.php
public function boot()
{
$this->app->event->listen('HttpRun', function () {
$this->app->middleware->add(MultiApp::class);
});
}
✅ 为什么好:不需要修改框架核心,通过事件动态添加中间件
ThinkPHP事件系统命令(快速生成代码)
| 命令 | 作用 | 生成路径 |
|---|---|---|
php think make:event UserLogin | 创建事件类 | app/event/UserLogin.php |
php think make:listener UserLogin | 创建监听器类 | app/listener/UserLoginListener.php |
php think make:subscribe User | 创建订阅者 | app/subscribe/UserSubscribe.php |
💡 小技巧:使用
make:listener生成的监听器会自动包含onUserLogin方法,符合命名规范
最佳实践(小白必读)
✅ 1. 合理使用事件
- 推荐:日志记录、权限检查、性能监控
- 避免:复杂业务逻辑、数据库操作(考虑用服务层)
✅ 2. 性能优化
-
轻量级:监听器应保持简单(避免数据库查询、网络请求)
-
异步处理:耗时操作用消息队列(如
queue)// 正确:异步处理 $this->app->queue->push(function() { // 耗时操作 });
✅ 3. 错误处理
public function onUserLogin($data)
{
try {
// 业务逻辑
} catch (\Exception $e) {
Log::error("用户登录失败: " . $e->getMessage());
// 但不要抛出异常,避免影响主流程
}
}
✅ 4. 命名规范
- 事件标识:
UserLogin(驼峰式,不用下划线) - 监听器方法:
onUserLogin(必须以on开头)
常见问题解答
Q1:事件配置文件为什么放在app/event.php而不是config/event.php?
A:ThinkPHP的配置系统默认加载
config/目录,但事件系统是独立于配置系统的扩展机制,因此需要放在app/event.php。
Q2:如何调试事件监听器是否执行?
A:在监听器中添加
Log::debug('事件触发'),查看日志文件runtime/log/。
Q3:事件触发后,监听器会按顺序执行吗?
A:是的,在配置文件中数组的顺序即执行顺序(在代码中注册时,后注册的先执行)。
总结:小白掌握要点
| 关键点 | 说明 |
|---|---|
| 配置文件位置 | app/event.php(不是config/event.php) |
| 命名规范 | 监听器方法必须是on+事件标识(如onUserLogin) |
| 参数传递 | 通过event('事件', [参数])传递,监听器接收参数 |
| 最佳实践 | 用订阅者管理事件,保持监听器轻量 |
| 高级用法 | 通配符监听(model.*)、控制执行顺序 |
✨ 终极总结:ThinkPHP事件系统不是魔法,而是解耦的利器。通过正确使用,你可以让代码更清晰、扩展更简单。记住:事件是钩子,监听器是执行者,配置是桥梁。