事件系统

概念说明

Viswoole 的事件系统基于 发布-订阅(Publish-Subscribe) 模式实现,允许各个模块之间通过事件进行松耦合通信。事件系统由 Event 类提供,支持:

  • 注册监听器响应特定事件
  • 触发事件并传递数据给监听器
  • 移除不再需要的监听器
  • 类名形式的批量监听器注册

工作原理

核心API

方法说明
on(event, handle, limit)注册事件监听器,limit 限制触发次数
emit(event, arguments)触发事件,按注册顺序通知所有监听器
off(event, id)移除指定事件的指定监听器
offAll()移除所有已注册的监听器

事件名规范

  • 事件名 大小写不敏感,内部统一转为小写存储和匹配
  • on() 注册的事件名 不能包含 .(包含 . 会抛出异常)
  • 推荐使用 “模块.动作” 点分命名法 配合类名监听器:on('user', UserListener::class) 注册后,通过 emit('user.login') 定向触发类监听器的 login() 方法
  • 闭包监听器请使用不含 . 的普通事件名(如 user_login

类名监听器

当传入类名作为监听器时,框架会自动扫描该类的 公共方法(含静态方法),将每个方法注册为独立的监听器。这使得一个监听器类可以集中管理某类事件的所有相关处理逻辑。

监听器执行流程

text
emit('user.login', [$userId])


┌──────────────────┐
│ 查找 'user.login' │
│ 的所有监听器       │
└────────┬─────────┘


  ┌──────┴──────┐
  │              │
  ▼              ▼
闭包监听器     类方法监听器
handle($args)  Method($args)
  │              │
  ▼              ▼
检查 limit     检查 limit
  │              │
  ▼              ▼
执行回调       达到限制则 off

代码示例

基本用法

php
use Viswoole\Core\Facade\Event;

// 注册监听器(闭包监听器的事件名不能包含 ".")
Event::on('user_login', function ($userId) {
    Log::info("用户 {$userId} 已登录");
});

// 触发事件
Event::emit('user_login', [$userId]);

限制触发次数

某些场景下只需要监听器响应有限次数:

php
// 仅响应前 3 次,之后自动移除
Event::on('cache_miss', function ($key) {
    Log::warning("缓存未命中: {$key}");
}, 3);

类名监听器

将相关的事件处理逻辑组织到一个类中:

php
class OrderListener
{
    public function created(array $orderData): void
    {
        // 订单创建后的处理
        Log::info("订单创建: {$orderData['order_no']}");
    }

    public function paid(array $orderData): void
    {
        // 订单支付完成后的处理
        Notification::send($orderData['user_id'], '支付成功');
    }

    public function shipped(array $orderData): void
    {
        // 订单发货后的处理
        SmsService::send($orderData['phone'], '您的订单已发货');
    }
}

// 注册整个类作为监听器,自动扫描公共方法
Event::on('order', OrderListener::class);

// 通过 "事件名.方法名" 触发特定方法
Event::emit('order.created', [$orderData]);
Event::emit('order.paid', [$orderData]);
Event::emit('order.shipped', [$orderData]);

移除监听器

php
// on() 返回监听器 ID,可用于精确移除
$listenerId = Event::on('user_register', function ($data) { /* ... */ });

// 移除单个监听器
Event::off('user_register', $listenerId);

// 移除某事件的所有监听器
Event::off('user_register');

// 清空所有监听器(慎用)
Event::offAll();

注意on() 注册的事件名不能包含 .;如需移除类名监听器的某个方法监听,可结合 emit('模块.动作') 对应的 off('模块', '动作') 进行定向移除。

全局辅助函数

php
use Viswoole\Core\Facade\Event;

// 注册监听器
Event::on('app_booted', function () {
    echo '应用启动完成';
});

最佳实践

1. 事件命名规范

采用统一的命名约定,保持项目一致性:

php
// ✅ 推荐:使用下划线或驼峰命名闭包事件,语义清晰
Event::on('user_created', ...);
Event::on('order_paid', ...);
Event::on('cache_cleared', ...);

// 类名监听器注册一个不含点号的前缀事件名,通过 emit('前缀.方法') 定向触发
Event::on('order', OrderListener::class);
Event::emit('order.created', $data);  // 触发 OrderListener::created()
Event::emit('order.paid', $data);     // 触发 OrderListener::paid()

// ❌ 不推荐:在 on() 中使用点号(会抛出异常)
Event::on('user.created', ...); // ❌ 异常:事件名不能包含"."
Event::on('order_pay', ...);    // 格式混乱
Event::on('done', ...);         // 过于简短

2. 合理使用类名监听器

当一个事件涉及多个关联操作时,使用类监听器组织代码:

php
// ✅ 推荐:同一领域的多个子事件用类监听器管理
Event::on('payment', PaymentListener::class);
// PaymentListener 包含: success(), failed(), refunded() 等方法

// ❌ 不推荐:零散注册多个闭包
Event::on('payment_success', fn($d) => ...);
Event::on('payment_failed', fn($d) => ...);
Event::on('payment_refunded', fn($d) => ...);

3. 监听器保持轻量

监听器应尽快返回,耗时操作应异步处理:

php
// ✅ 推荐:快速返回,耗时任务异步投递
// 先注册任务主题(通常在服务提供者中注册一次)
Task::register('order.notify', function ($task) {
    $orderData = $task->data;
    NotificationService::send($orderData);
    StatisticsService::record($orderData);
});

Event::on('order_created', function ($orderData) {
    // 同步:记录日志(快速)
    Log::info("订单创建: {$orderData['order_no']}");

    // 异步:投递任务到 Task Worker 处理
    Task::emit('order.notify', $orderData);
});

// ❌ 不推荐:在监听器中执行耗时同步操作
Event::on('order_created', function ($orderData) {
    sleep(5); // 阻塞当前协程!
    ExternalApi::syncData($orderData);
});

4. 注意事件触发的时机

在服务提供者的 boot() 方法中注册事件监听,确保所有服务都已就绪:

php
class EventServiceProvider extends Provider
{
    public function register(): void {}

    public function boot(): void
    {
        // boot 阶段所有服务已注册完毕,可以安全地使用其他服务
        Event::on('user_login', function ($userId) {
            // 此时 Logger、Cache 等服务已经可用
            app()->make(Logger::class)->info("用户 {$userId} 登录");
        });
    }
}