事件系统
事件系统(Event System)是现代 PHP 框架中实现松耦合通信的核心机制。它基于观察者模式(Observer Pattern),允许对象在特定动作发生时通知其他对象,而无需硬编码依赖关系。事件系统广泛应用于日志记录、通知推送、审计追踪、缓存清理等场景。本节将深入讲解事件系统的概念、PSR-14 标准和各种实现方式。
基础概念
什么是事件系统
事件系统由三个核心组件构成:
事件(Event) → 发生了什么
监听器(Listener) → 对事件做出反应
分发器(Dispatcher)→ 将事件分发给对应的监听器事件系统的工作流程
1. 某个服务触发一个事件
$dispatcher->dispatch(new UserRegisteredEvent($user));
2. 分发器查找该事件的所有监听器
→ UserRegisteredListener
→ WelcomeEmailListener
→ ActivityLogListener
3. 按优先级顺序调用每个监听器
→ UserRegisteredListener::handle()
→ WelcomeEmailListener::handle()
→ ActivityLogListener::handle()
4. 监听器处理事件(可修改事件数据)
5. 返回(可选:可终止传播)详细说明
PSR-14 接口
php
<?php
namespace Psr\EventDispatcher;
interface EventDispatcherInterface
{
/**
* 分发事件,返回可能被修改后的事件
*/
public function dispatch(object $event): object;
}
interface ListenerProviderInterface
{
/**
* 获取事件对应的所有监听器
*/
public function getListenersForEvent(object $event): iterable;
}
interface StoppableEventInterface
{
/**
* 判断事件传播是否已停止
*/
public function isPropagationStopped(): bool;
/**
* 停止事件传播
*/
public function stopPropagation(): void;
}事件定义
php
<?php
declare(strict_types=1);
namespace App\Events;
use App\Models\User;
class UserRegisteredEvent
{
public function __construct(
public readonly User $user,
public readonly string $ip,
) {}
public function getTimestamp(): int
{
return time();
}
}
class OrderPlacedEvent
{
public function __construct(
public readonly int $orderId,
public readonly float $amount,
public readonly int $userId,
) {}
}监听器定义
php
<?php
declare(strict_types=1);
namespace App\Listeners;
use App\Events\UserRegisteredEvent;
use App\Services\EmailService;
use Psr\Log\LoggerInterface;
class WelcomeEmailListener
{
public function __construct(
private readonly EmailService $emailService,
private readonly LoggerInterface $logger,
) {}
public function __invoke(UserRegisteredEvent $event): void
{
$user = $event->user;
try {
$this->emailService->sendWelcome($user->email, $user->name);
$this->logger->info('Welcome email sent', [
'user_id' => $user->id,
]);
} catch (\Throwable $e) {
$this->logger->error('Failed to send welcome email', [
'user_id' => $user->id,
'error' => $e->getMessage(),
]);
}
}
}
class ActivityLogListener
{
public function __construct(
private readonly LoggerInterface $logger
) {}
public function __invoke(UserRegisteredEvent $event): void
{
$this->logger->info('User registered', [
'user_id' => $event->user->id,
'name' => $event->user->name,
'email' => $event->user->email,
'ip' => $event->ip,
'timestamp' => $event->getTimestamp(),
]);
}
}事件分发器实现
php
<?php
declare(strict_types=1);
namespace App\Event;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;
use Psr\EventDispatcher\StoppableEventInterface;
class SimpleEventDispatcher implements EventDispatcherInterface
{
public function __construct(
private readonly ListenerProviderInterface $listenerProvider
) {}
public function dispatch(object $event): object
{
foreach ($this->listenerProvider->getListenersForEvent($event) as $listener) {
if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
return $event;
}
$listener($event);
}
return $event;
}
}
class ListenerProvider implements ListenerProviderInterface
{
/** @var array<class-string, array<int, callable>> */
private array $listeners = [];
/**
* 注册监听器
*/
public function addListener(string $eventName, callable $listener, int $priority = 0): void
{
if (!isset($this->listeners[$eventName])) {
$this->listeners[$eventName] = [];
}
$this->listeners[$eventName][] = ['listener' => $listener, 'priority' => $priority];
// 按优先级排序(数字越大优先级越高)
usort($this->listeners[$eventName], fn($a, $b) => $b['priority'] <=> $a['priority']);
}
public function getListenersForEvent(object $event): iterable
{
$eventName = $event::class;
// 支持事件继承
$listeners = [];
foreach ($this->listeners as $registeredEvent => $eventListeners) {
if ($event instanceof $registeredEvent || $eventName === $registeredEvent) {
foreach ($eventListeners as $entry) {
$listeners[] = $entry['listener'];
}
}
}
return $listeners;
}
}可停止传播的事件
php
<?php
declare(strict_types=1);
namespace App\Events;
use Psr\EventDispatcher\StoppableEventInterface;
class RequestEvent implements StoppableEventInterface
{
private bool $propagationStopped = false;
public function __construct(
private readonly object $request,
private ?object $response = null,
) {}
public function getRequest(): object
{
return $this->request;
}
public function getResponse(): ?object
{
return $this->response;
}
public function setResponse(object $response): void
{
$this->response = $response;
$this->stopPropagation();
}
public function isPropagationStopped(): bool
{
return $this->propagationStopped;
}
public function stopPropagation(): void
{
$this->propagationStopped = true;
}
}实战示例
场景一:订单处理事件
php
<?php
declare(strict_types=1);
namespace App\Events;
class OrderPlacedEvent
{
public function __construct(
public readonly int $orderId,
public readonly int $userId,
public readonly float $totalAmount,
public readonly array $items,
) {}
}
// 监听器:发送确认邮件
class SendOrderConfirmation implements \Psr\EventDispatcher\ListenerProviderInterface
{
public function getListenersForEvent(object $event): iterable
{
if ($event instanceof OrderPlacedEvent) {
return [
fn() => $this->sendConfirmation($event),
];
}
return [];
}
private function sendConfirmation(OrderPlacedEvent $event): void
{
echo "Sending confirmation for order #{$event->orderId}\n";
}
}
// 监听器:更新库存
class UpdateInventoryListener
{
public function __invoke(OrderPlacedEvent $event): void
{
foreach ($event->items as $item) {
echo "Decreasing inventory for product {$item['product_id']}\n";
}
}
}
// 监听器:记录日志
class LogOrderListener
{
public function __construct(private readonly \Psr\Log\LoggerInterface $logger) {}
public function __invoke(OrderPlacedEvent $event): void
{
$this->logger->info('Order placed', [
'order_id' => $event->orderId,
'user_id' => $event->userId,
'total' => $event->totalAmount,
]);
}
}场景二:注册和使用事件
php
<?php
declare(strict_types=1);
use App\Event\ListenerProvider;
use App\Event\SimpleEventDispatcher;
// 创建监听器提供者
$provider = new ListenerProvider();
// 注册监听器(按优先级排序)
$provider->addListener(
OrderPlacedEvent::class,
new LogOrderListener($logger),
priority: 100 // 高优先级,先执行
);
$provider->addListener(
OrderPlacedEvent::class,
new UpdateInventoryListener(),
priority: 50
);
$provider->addListener(
OrderPlacedEvent::class,
fn(OrderPlacedEvent $e) => /* 发送邮件 */ null,
priority: 10 // 低优先级,后执行
);
// 创建分发器
$dispatcher = new SimpleEventDispatcher($provider);
// 触发事件
$event = new OrderPlacedEvent(
orderId: 1001,
userId: 42,
totalAmount: 299.99,
items: [['product_id' => 1, 'quantity' => 2]]
);
$dispatcher->dispatch($event);注意事项
1. 同步 vs 异步
PHP 的事件系统通常是同步的(按顺序执行所有监听器)。如果监听器执行时间较长,会影响请求响应时间。对于耗时操作(如发送邮件),应使用队列异步处理。
php
<?php
// 同步监听器(默认,立即执行)
class SendWelcomeEmail
{
public function __invoke(UserRegisteredEvent $event): void
{
Mail::to($event->user->email)->send(new WelcomeMail());
}
}
// 异步监听器(通过队列处理,Laravel 方式)
class SendWelcomeEmail implements ShouldQueue
{
public function __construct(
public readonly User $user,
) {}
public function handle(): void
{
Mail::to($this->user->email)->send(new WelcomeMail());
}
}2. 事件命名
php
<?php
// ✅ 推荐:使用过去分词表示已发生的事件
class UserRegisteredEvent {} // 用户已注册
class OrderPlacedEvent {} // 订单已下
class PaymentSucceededEvent {} // 支付已成功
class PasswordChangedEvent {} // 密码已修改
// ❌ 避免:使用不定式
class UserRegister {} // 注册中?
class PlaceOrder {} // 下单中?3. 事件 vs 直接调用
php
<?php
// ❌ 直接调用(紧耦合)
class UserController
{
public function register(array $data): User
{
$user = User::create($data);
$this->emailService->sendWelcome($user);
$this->logService->log('User registered', $user);
$this->profileService->createDefault($user);
return $user;
}
}
// ✅ 使用事件(松耦合)
class UserController
{
public function __construct(
private readonly EventDispatcherInterface $dispatcher
) {}
public function register(array $data): User
{
$user = User::create($data);
$this->dispatcher->dispatch(new UserRegisteredEvent($user));
return $user;
}
}4. 领域事件 vs 框架事件
php
<?php
// 领域事件:与业务逻辑相关,可被任何监听器处理
class OrderPlacedEvent
{
public function __construct(
public readonly Order $order,
) {}
}
// 框架事件:由框架触发,与框架生命周期相关
class RequestHandledEvent
{
public function __construct(
public readonly ServerRequestInterface $request,
public readonly ResponseInterface $response,
public readonly float $duration,
) {}
}最佳实践
1. 事件应该是不可变的
php
<?php
declare(strict_types=1);
// ✅ 不可变事件
class UserRegisteredEvent
{
public function __construct(
public readonly User $user,
) {}
}
// ❌ 可变事件(可能导致副作用)
class BadEvent
{
public User $user;
public function __construct(User $user)
{
$this->user = $user;
}
}2. 监听器应该是幂等的
监听器应该可以安全地多次执行而不产生副作用。
下一节
继续学习:队列与任务调度 — 了解异步处理和定时任务的实现。