动作触发器与事件监听器
重点摘要
标题为“重点摘要”的章节NextPDF 在 NextPDF\Event 中通过兼容 PSR-14 的系统触发生命周期事件。 你可以按指定优先级注册监听器,响应文档创建、添加页面、字体加载、签署、加密、写入或输出等动作,并在需要时中断整条链。
composer require nextpdf/core:^3概念总览
标题为“概念总览”的章节事件系统包含两个公开部分。 ListenerProvider 将事件类映射到一组已排序的监听器 callable 列表。 EventDispatcher 遍历该列表,并按优先级调用每个监听器。 这两个类都是 final,因此请通过组合而不是继承来扩展它们。
这两个类都通过 duck typing 符合 PSR-14。 EventDispatcher::dispatch() 符合 PSR-14 的 dispatch() 签名,并会在所有监听器执行完毕后返回该事件。 ListenerProvider::getListenersForEvent() 符合 PSR-14 的 provider 签名。 NextPDF 不需要 PSR-14 包;但如果你的项目已安装它,这些接口仍然可以对齐。
有两个行为对扩展作者很重要:
- 通配符监听。 解析监听器时,provider 会遍历事件的父类及其接口。 将监听器绑定到
AbstractEvent基类,即可监看每一个生命周期事件。 绑定到某个接口,即可拦截一整族事件。 - 优先级与传播。 优先级较高者先执行。 优先级相同时,保持注册顺序。 每个继承
AbstractEvent的事件都是可停止的。 监听器可以调用stopPropagation()。 dispatcher 随后会跳过其余监听器。
dispatcher 有一条零成本快速路径。 当某个事件类及其所有父类都没有绑定监听器时,dispatch() 只做一次 hasListeners() 检查后就立即返回。
生命周期事件
标题为“生命周期事件”的章节| 事件 | 命名空间 | 触发时机 | 稳定性 |
|---|---|---|---|
DocumentCreatedEvent | NextPDF\Event\Document | 文档构造完成 | 实验性 |
PageAddedEvent | NextPDF\Event\Document | 页面初始化完成 | 实验性 |
ContentRenderedEvent | NextPDF\Event\Content | 内容已渲染到页面 | 实验性 |
FontLoadedEvent | NextPDF\Event\Content | 某个字体家族与样式首次加载 | 实验性 |
SignatureAppliedEvent | NextPDF\Event\Security | 签章字节已嵌入 | 实验性 |
EncryptionAppliedEvent | NextPDF\Event\Security | 加密设置完成 | 实验性 |
PdfSerializedEvent | NextPDF\Event\Writer | 序列化完成 | 实验性 |
DocumentOutputEvent | NextPDF\Event\Document | 输出交付之前 | 实验性 |
dispatcher、provider、标记接口与基类都是 stable(自 3.0.0 起)。 事件负载则是 experimental。 它们的构造函数参数与 readonly 属性可能在 minor 版本中变动。 patch 版本只会新增。 绑定负载属性名称时,请记住这一点。
API 接口
标题为“API 接口”的章节NextPDF\Event\ListenerProvider(稳定、final):
| 方法 | 返回 | 用途 |
|---|---|---|
addListener(string $eventClass, callable $listener, int $priority = 0) | void | 注册监听器;优先级较高者先执行。 若传入空类,会抛出 InvalidConfigException。 |
getListenersForEvent(EventInterface $event) | list<callable> | 解析监听器,包括父类与接口上的注册。 |
hasListeners(string $eventClass) | bool | 跨整个类层级的零开销检查。 |
getListenerCount(string $eventClass) | int | 只计算直接注册的数量。 |
clearListeners() | void | 重设 provider。 |
NextPDF\Event\EventDispatcher(稳定、final):
| 方法 | 返回 | 用途 |
|---|---|---|
dispatch(EventInterface $event) | EventInterface | 按优先级调用监听器;遵守传播停止;返回该事件。 |
getListenerProvider() | ListenerProvider | 访问 provider,即可在运行时新增监听器。 |
会触发事件的文档会使用 NextPDF\Event\EventAwareDocumentTrait。 它的 setEventDispatcher() 方法会把一个 dispatcher 接入单个文档。 若没有 dispatcher,每个 dispatch 辅助方法都不会执行任何操作。
代码示例 — 快速上手
标题为“代码示例 — 快速上手”的章节<?php
declare(strict_types=1);
use NextPDF\Event\EventDispatcher;use NextPDF\Event\ListenerProvider;use NextPDF\Event\Security\SignatureAppliedEvent;
$listeners = new ListenerProvider();$listeners->addListener( SignatureAppliedEvent::class, static function (SignatureAppliedEvent $event): void { \error_log("Signed at level {$event->signatureLevel} by {$event->signerName}"); }, priority: 100,);
$dispatcher = new EventDispatcher($listeners);代码示例 — 生产环境
标题为“代码示例 — 生产环境”的章节这是一个生产环境的审计监听器。 它使用较高优先级以便最先执行,以结构化形式记录,并在基类上添加一个 catch-all 监听器,以保证覆盖完整。
<?php
declare(strict_types=1);
use NextPDF\Event\AbstractEvent;use NextPDF\Event\EventDispatcher;use NextPDF\Event\ListenerProvider;use NextPDF\Event\Security\EncryptionAppliedEvent;use NextPDF\Event\Security\SignatureAppliedEvent;use Psr\Log\LoggerInterface;
final class SecurityAuditSubscriber{ public function __construct(private readonly LoggerInterface $logger) {}
public function register(ListenerProvider $listeners): EventDispatcher { $listeners->addListener( SignatureAppliedEvent::class, function (SignatureAppliedEvent $event): void { $this->logger->info('signature.applied', [ 'level' => $event->signatureLevel, 'signer' => $event->signerName, ]); }, priority: 1000, );
$listeners->addListener( EncryptionAppliedEvent::class, function (EncryptionAppliedEvent $event): void { $this->logger->info('encryption.applied', [ 'algorithm' => $event->algorithm, ]); }, priority: 1000, );
// Catch-all: observe every lifecycle event for trace completeness. $listeners->addListener( AbstractEvent::class, fn (AbstractEvent $event): mixed => $this->logger->debug('lifecycle', ['event' => $event->getEventName()]), priority: -1000, );
return new EventDispatcher($listeners); }}边界情况与陷阱
标题为“边界情况与陷阱”的章节- Final 类。
EventDispatcher与ListenerProvider都是final。 请使用组合,不要使用继承。 - 空事件类会抛出异常。
addListener('', ...)会抛出InvalidConfigException。 请始终传入 class-string 常量。 - 通配符的成本。 绑定在
AbstractEvent上的监听器会对每一个事件触发。 请为 catch-all 监听器设置较低优先级,并让它们保持轻量。 - 输出变更。
DocumentOutputEvent携带 PDF 字节。 引擎会在 dispatch 之后读回这些字节。 变更这些字节可以获得广泛控制权,但也有实际风险。 错误的字节偏移量会让 PDF 损坏,并可能破坏签章。 除非你为了确定性与签章而拥有最终结果,否则请以观察为主。 - 没有 dispatcher,就没有事件。 没有通过
EventAwareDocumentTrait设置 dispatcher 的文档不会触发任何事件。 这是有意设计的零成本路径,并不是配置错误。
快速路径是一次沿父类链进行的 hasListeners() 检查。 没有监听器时,dispatch 几乎没有成本。 provider 会针对每个事件类缓存已排序的监听器列表。 只有在发生变更时,它才会清除该缓存。 请让监听器保持非阻塞。 它们会在渲染路径上以内联方式执行。
安全性注意事项
标题为“安全性注意事项”的章节SignatureAppliedEvent 与 EncryptionAppliedEvent 是审计锚点。 请注册高优先级监听器,将签署与加密记录到防篡改的存储区。 除非你有意让后续监听器静默,否则不要在安全事件上中断整条链。 中断它可能会悄然关闭后续执行的审计挂钩。
合规性
标题为“合规性”的章节本页除了兼容 PSR-14 之外不做任何规范性主张;该兼容性仅为 duck-type,并不需要 PSR-14 包。
商业场景
标题为“商业场景”的章节NextPDF Enterprise 随附经过审计的监听器,用于签章与加密事件,并写入防篡改的审计记录。 监听器契约就是公开的事件 API,因此你自己的监听器可以与 Enterprise 的监听器共存于同一个 provider 上。
另请参阅
标题为“另请参阅”的章节相关契约与模块
标题为“相关契约与模块”的章节- 事件模块参考,介绍 PSR-14 生命周期事件分类法与 dispatcher 的内部工作方式。
- 签署契约参考,介绍
SignatureAppliedEvent背后的契约。 - SPI 稳定性规则,说明稳定的 dispatcher 与实验性负载如何进行版本管理。
- 自定义字体,将
FontLoadedEvent与注册表契约配合使用。 - 扩展开发总览,介绍完整的公开 SPI 接口。
词汇表定义了本页使用的 event listener、event dispatcher、listener provider 与 stoppable event 等术语。 请参阅已发布的词汇表,了解每个术语的规范定义。