事件系统
事件系统基于发布-订阅(Publish-Subscribe)模式,让模块之间以松耦合方式通信:监听方注册回调,触发方只管广播,双方互不依赖。框架内部的生命周期通知(应用初始化、路由加载、服务启动等)全部经由此系统分发,业务代码也可以定义自己的事件。
本文依据框架源码
src/Core/Event.php、src/Core/FrameworkEvent.php、src/Core/Server/ServerEventHook.php编写。
核心 API
事件管理器为 Viswoole\Core\Event,通过门面访问:
use Viswoole\Core\Facade\Event;| 方法 | 签名 | 说明 |
|---|---|---|
on | on(string|UnitEnum $event, callable|string $handle, int $limit = 0): string|array | 注册监听器;闭包返回监听器 ID,类名注册返回方法名 ID 数组 |
emit | emit(string|UnitEnum $event, array $arguments = []): void | 触发事件,按注册顺序调用所有监听器 |
off | off(string|UnitEnum $event, ?string $id = null): void | 移除监听器;$id 为 null 时移除该事件的全部监听器 |
offAll | offAll(): void | 清空所有监听器 |
getEvents | getEvents(): array | 获取所有已注册监听的事件名列表 |
注册监听器 on
use Viswoole\Core\Facade\Event;
// 闭包监听:返回监听器 ID,可用于精确移除
$id = Event::on('user_login', function (array $user) {
Log::info("用户 {$user['id']} 已登录");
});
// 限制触发次数:仅响应前 3 次,之后自动移除(0 表示不限制)
Event::on('cache_miss', function (string $key) {
Log::warning("缓存未命中: {$key}");
}, 3);
Event::emit('user_login', [['id' => 1]]); // 参数以数组形式依次传入监听器
Event::off('user_login', $id); // 移除单个监听器
Event::off('user_login'); // 移除该事件的全部监听器监听器由容器 invoke() 执行,因此监听器方法同样支持依赖注入参数。
事件名规则
- 大小写不敏感:事件名注册、触发、移除时统一转为小写,
Event::on('UserLogin', ...)与Event::emit('userlogin', ...)指向同一监听组; on()的事件名不能包含.:包含时抛InvalidArgumentException(.被保留给定向触发语法,见下文);- 支持枚举事件名:任意枚举均可作为事件名——字符串枚举使用枚举值,数值枚举与纯枚举使用枚举名;但枚举类不能作为监听器注册。
框架内置事件名统一由 FrameworkEvent 枚举定义(见下文生命周期事件)。
类名监听器与 event.id 定向触发
on() 传入类名时,框架反射扫描该类的全部公共方法(跳过抽象方法、构造与析构方法),以「方法名」为监听器 ID 批量注册:
class UserEvents
{
public static function login(array $user): void { /* ... */ }
public function register(array $user): void { /* ... */ }
}
// 注册整个类:返回 ['login', 'register']
Event::on('user', UserEvents::class);
// 定向触发:'user.login' 只调用 'user' 事件下 ID 为 'login' 的监听器
Event::emit('user.login', [['id' => 1]]); // → UserEvents::login()
Event::emit('user.register', [['id' => 1]]); // → UserEvents::register()
// 移除 'user' 事件下的 'login' 监听器
Event::off('user', 'login');定向触发语法 emit('事件名.监听器ID'):
- 只触发该事件下指定 ID 的监听器,其余监听器不受影响;
- 监听器 ID 是注册时生成的:类名监听器为小写方法名,闭包监听器为自动生成的哈希;
- 对闭包监听器同样适用:
Event::emit('user_login.某个id')可定向触发某一支。
次数限制
on() 的第三个参数 $limit 限制监听器最大触发次数:达到上限后框架自动将其移除;传 0 表示不限制;负数在注册时即抛异常。适合「只关心前 N 次」的场景(如启动期的预热告警)。
框架生命周期事件
框架内置事件聚合在枚举 Viswoole\Core\FrameworkEvent 中,作为事件名的单一事实来源(支持 IDE 补全,避免硬编码字符串)。监听时可直接传枚举:
use Viswoole\Core\Facade\Event;
use Viswoole\Core\FrameworkEvent;
Event::on(FrameworkEvent::ServerStarted, function (\Viswoole\Core\Server $server) {
// 服务启动完成
});
// 字符串形式等价(事件名归一化为小写):Event::on('serverstarted', ...)全部内置事件(以源码枚举为准):
| 事件枚举 | 事件名 | 触发时机 | 回调参数 |
|---|---|---|---|
AppInitialized | appinitialized | App 构造完成、依赖绑定与服务注册执行完毕后 | 无 |
AppDestroying | appdestroying | App 对象析构时,供服务清理资源 | 无 |
RouterInitializing | routerinitializing | 路由初始化开始前、加载配置路由与注解路由之前 | 无 |
RouterInitialized | routerinitialized | 配置路由、注解路由加载并解析完成后 | 无 |
ServerCreating | servercreating | 加载服务配置之前,此时 Swoole Server 尚未创建 | Server $server |
ServerCreated | servercreated | Swoole Server 创建完成并绑定到容器之后、服务启动之前 | Server $server |
ServerStarting | serverstarting | 调用 start() 之后、Swoole 事件循环启动之前 | Server $server |
ServerStarted | serverstarted | Swoole onStart 回调中,主进程事件循环已启动 | Server $server |
ServerShuttingDown | servershuttingdown | Swoole onBeforeShutdown 回调中,供执行清理工作 | Server $server |
监听注册时机
AppInitialized 在 App 构造结束时触发,此时配置已加载。想在框架启动的最早期介入,可使用 config/listens.php(下文)或 Swoole 事件挂钩。
监听注册方式
方式一:config/listens.php
config/listens.php 是事件监听注册入口。它作为配置文件随配置目录一起加载(PHP 配置文件会被 include 执行),在文件顶层直接调用 Event::on() 即可:
// config/listens.php
use Viswoole\Core\Facade\Event;
Event::on('AppInitialized', function () {
echo_log('应用初始化完成');
}, 1); // 第三个参数限制仅触发一次适合注册框架生命周期事件的监听。
方式二:服务提供者
在服务提供者的 boot() 中注册,适合随业务模块分发的事件(boot 阶段所有服务已注册完毕):
use Viswoole\Core\Service\Provider;
final class OrderEventProvider extends Provider
{
public function register(): void {}
public function boot(): void
{
$this->app->event->on('order', \App\Listener\OrderListener::class);
}
}Swoole 事件挂钩:ServerEventHook
框架事件与 Swoole 事件是两层系统。Swoole 服务端事件(workerStart、workerStop、onTask 等)通过 Viswoole\Core\Server\ServerEventHook 注册,框架将其统一转发给 Swoole Server:
use Viswoole\Core\Server\ServerEventHook;
// 注册单个事件(同一事件可注册多个回调,按注册顺序执行)
ServerEventHook::addEvent('workerStart', function (\Swoole\Server $server, int $workerId) {
// 每个 Worker 进程启动时执行一次
});
// 批量注册
ServerEventHook::addEvents([
'workerStart' => function (\Swoole\Server $server, int $workerId) { /* ... */ },
'workerStop' => function (\Swoole\Server $server, int $workerId) { /* ... */ },
]);| 方法 | 签名 | 说明 |
|---|---|---|
addEvent | addEvent(string $event, callable $callback): void | 注册单个 Swoole 事件处理器,事件名不区分大小写 |
addEvents | addEvents(array $events): void | 批量注册,键为事件名、值为回调 |
getEventHooks | getEventHooks(): array | 获取全部事件回调映射,供 Server 注册时使用 |
addEvent 必须在服务启动前调用
ServerEventHook 的事件表在 Swoole Server 创建并绑定回调时被消费。所有挂钩注册应发生在 ServerStarting 事件之前(如 config/listens.php 或服务提供者中),启动后注册不会再生效。workerStart 是「每个 Worker 进程执行一次」逻辑(如连接池填充)的正确挂钩位置,主进程的 start 事件中创建的资源无法跨进程共享。
框架自身已在 ServerEventHook 中内置了 start、shutdown、beforeShutdown 三个事件的处理(输出启动信息、监听 SIGINT 安全关闭、触发 ServerShuttingDown 事件),自定义回调会与之共存。
