事件系统

概念说明

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

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

工作原理

核心API

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

事件名规范

  • 事件名 大小写不敏感,内部统一转为小写存储和匹配
  • 推荐使用 点分命名法模块.动作,如 user.loginorder.created
  • 支持 “事件名.方法名” 形式,用于触发类监听器的特定公共方法

类名监听器

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

监听器执行流程

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();

全局辅助函数

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', ...);

// ❌ 不推荐:过于简短或格式混乱
Event::on('userCreate', ...);
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()->get(Logger::class)->info("用户 {$userId} 登录");
        });
    }
}