PHP事件系统怎么设计

wen PHP项目 2

本文目录导读:

PHP事件系统怎么设计

  1. 核心概念
  2. 基础架构设计
  3. 使用示例
  4. 高级特性设计
  5. 容器集成
  6. 设计要点总结

设计一个PHP事件系统,核心是解耦代码逻辑——让“发生什么”和“做什么”分离,下面从设计原则到具体实现,给出一个完整的方案。

核心概念

  • 事件(Event):描述发生的事情,携带数据
  • 监听器(Listener):对事件做出反应,执行逻辑
  • 调度器(Dispatcher):触发事件,通知所有监听器
  • 订阅器(Subscriber):批量注册多个监听器

基础架构设计

事件接口定义

<?php
namespace App\Event;
interface EventInterface
{
    /**
     * 获取事件名称(可选,可用于自动映射)
     */
    public function getName(): string;
    /**
     * 停止事件传播
     */
    public function isPropagationStopped(): bool;
    /**
     * 停止传播
     */
    public function stopPropagation(): void;
}

基础事件类

<?php
namespace App\Event;
use App\Event\EventInterface;
class Event implements EventInterface
{
    protected string $name;
    protected bool $propagationStopped = false;
    protected array $data = [];
    public function __construct(string $name = '', array $data = [])
    {
        $this->name = $name ?: static::class;
        $this->data = $data;
    }
    public function getName(): string
    {
        return $this->name;
    }
    public function getData(): array
    {
        return $this->data;
    }
    public function get(string $key, mixed $default = null): mixed
    {
        return $this->data[$key] ?? $default;
    }
    public function set(string $key, mixed $value): self
    {
        $this->data[$key] = $value;
        return $this;
    }
    public function isPropagationStopped(): bool
    {
        return $this->propagationStopped;
    }
    public function stopPropagation(): void
    {
        $this->propagationStopped = true;
    }
}

事件调度器

<?php
namespace App\Event;
use App\Event\EventInterface;
use InvalidArgumentException;
class EventDispatcher
{
    /**
     * 事件 => 监听器列表
     * @var array<string, array<int, callable|array>>
     */
    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][$priority][] = $listener;
        krsort($this->listeners[$eventName]); // 高优先级在前
    }
    /**
     * 移除监听器
     */
    public function removeListener(string $eventName, callable $listener): bool
    {
        if (!isset($this->listeners[$eventName])) {
            return false;
        }
        foreach ($this->listeners[$eventName] as $priority => $listeners) {
            foreach ($listeners as $key => $registeredListener) {
                if ($registeredListener === $listener) {
                    unset($this->listeners[$eventName][$priority][$key]);
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * 触发事件
     */
    public function dispatch(EventInterface $event): EventInterface
    {
        $eventName = $event->getName();
        if (!isset($this->listeners[$eventName])) {
            return $event;
        }
        foreach ($this->listeners[$eventName] as $priority => $listeners) {
            if ($event->isPropagationStopped()) {
                break;
            }
            foreach ($listeners as $listener) {
                if ($event->isPropagationStopped()) {
                    break;
                }
                $result = call_user_func($listener, $event);
                // 如果监听器返回 false,停止传播
                if ($result === false) {
                    $event->stopPropagation();
                }
            }
        }
        return $event;
    }
    /**
     * 获取所有监听器
     */
    public function getListeners(string $eventName = null): array
    {
        if ($eventName !== null) {
            return $this->flatten($this->listeners[$eventName] ?? []);
        }
        $all = [];
        foreach ($this->listeners as $name => $listeners) {
            $all[$name] = $this->flatten($listeners);
        }
        return $all;
    }
    /**
     * 扁平化优先级数组
     */
    private function flatten(array $listeners): array
    {
        $result = [];
        foreach ($listeners as $priority => $items) {
            foreach ($items as $item) {
                $result[] = $item;
            }
        }
        return $result;
    }
}

订阅器支持

<?php
namespace App\Event;
interface EventSubscriberInterface
{
    /**
     * 返回订阅的事件列表
     * 
     * @return array<string, array|string>
     * 例:['user.created' => 'onUserCreated', 'user.updated' => ['onUserUpdated', 10]]
     */
    public static function getSubscribedEvents(): array;
}

在调度器中添加订阅器注册方法:

public function addSubscriber(EventSubscriberInterface $subscriber): void
{
    foreach ($subscriber::getSubscribedEvents() as $eventName => $params) {
        if (is_string($params)) {
            $this->addListener($eventName, [$subscriber, $params]);
        } elseif (is_array($params)) {
            foreach ($params as $listener) {
                if (is_string($listener)) {
                    $this->addListener($eventName, [$subscriber, $listener]);
                } elseif (is_array($listener) && count($listener) === 2) {
                    $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1]);
                }
            }
        }
    }
}

具体事件示例

<?php
namespace App\Event\Domain;
use App\Event\Event;
class UserCreatedEvent extends Event
{
    public const NAME = 'user.created';
    public function __construct(
        private readonly int $userId,
        private readonly array $userData = []
    ) {
        parent::__construct(self::NAME);
    }
    public function getUserId(): int
    {
        return $this->userId;
    }
    public function getUserData(): array
    {
        return $this->userData;
    }
}

事件总线(封装调度器)

<?php
namespace App\Event;
class EventBus
{
    private static ?EventBus $instance = null;
    private EventDispatcher $dispatcher;
    private function __construct()
    {
        $this->dispatcher = new EventDispatcher();
    }
    public static function getInstance(): EventBus
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    public function emit(EventInterface $event): void
    {
        $this->dispatcher->dispatch($event);
    }
    public function on(string $eventName, callable $listener, int $priority = 0): void
    {
        $this->dispatcher->addListener($eventName, $listener, $priority);
    }
    public function subscribe(EventSubscriberInterface $subscriber): void
    {
        $this->dispatcher->addSubscriber($subscriber);
    }
}

使用示例

<?php
// 注册监听器
$dispatcher = new EventDispatcher();
// 方法一:闭包监听器
$dispatcher->addListener('user.created', function (UserCreatedEvent $event) {
    // 发送欢迎邮件
    Mailer::send($event->getUserData()['email'], '欢迎注册');
}, 10); // 优先级10
// 方法二:类方法监听器
class SendSmsListener
{
    public function onUserCreated(UserCreatedEvent $event): void
    {
        // 发送短信通知
    }
}
$dispatcher->addListener('user.created', [new SendSmsListener(), 'onUserCreated']);
// 触发事件
$event = new UserCreatedEvent(123, ['email' => 'user@example.com']);
$dispatcher->dispatch($event);

高级特性设计

异步事件

<?php
trait AsyncEventTrait
{
    private bool $async = false;
    private int $delaySeconds = 0;
    public function setAsync(bool $async = true): self
    {
        $this->async = $async;
        return $this;
    }
    public function isAsync(): bool
    {
        return $this->async;
    }
    public function setDelay(int $seconds): self
    {
        $this->delaySeconds = $seconds;
        return $this;
    }
}

事件追踪

<?php
class TraceEventDispatcher extends EventDispatcher
{
    private array $history = [];
    public function dispatch(EventInterface $event): EventInterface
    {
        $this->history[] = [
            'event' => $event->getName(),
            'time'  => microtime(true),
            'memory' => memory_get_usage(),
        ];
        return parent::dispatch($event);
    }
    public function getHistory(): array
    {
        return $this->history;
    }
}

容器集成

<?php
// 与依赖注入容器结合
class ContainerAwareDispatcher extends EventDispatcher
{
    public function __construct(
        private readonly ContainerInterface $container
    ) {}
    public function dispatchNamed(string $eventName, array $params = []): EventInterface
    {
        $event = new Event($eventName, $params);
        return $this->dispatch($event);
    }
    // 从容器解析监听器
    public function registerFromContainer(string $eventName, string $serviceId, string $method = '__invoke'): void
    {
        $this->addListener($eventName, function ($event) use ($serviceId, $method) {
            $service = $this->container->get($serviceId);
            return $service->{$method}($event);
        });
    }
}

设计要点总结

规范建议

  • 命名规范{实体}.{动作},如 user.createdorder.paid
  • 参数传递:通过事件对象传递数据,不要直接传数组
  • 返回值约定:监听器返回 false 或调用 stopPropagation() 停止传播
  • 异常处理:监听器异常不影响其他监听器执行

性能考虑

// 使用索引优化
private array $listenerIndex = [];
// 事件名称哈希索引
public function addListener(string $eventName, callable $listener, int $priority = 0): void
{
    $hash = md5($eventName);
    // ... 使用哈希索引
}

测试建议

  • 单元测试:测试调度器逻辑
  • 集成测试:测试具体业务事件
  • 性能测试:大量事件触发下的性能表现

这个设计涵盖了事件系统的主要功能需求,可以根据项目实际情况选择合适的部分使用。

抱歉,评论功能暂时关闭!