Event(事件):PSR-14 生命周期事件分类
Event 模块会在 PDF 生成的每个阶段分发类型明确的生命周期事件。监听器(listener)可以在不修改引擎内部实现的情况下观察或转换文件。分发器遵循 PSR-14 模型,因此现有的 PSR-14 工具仍可兼容运行。
composer require nextpdf/core:^3Event 模块随核心包一同提供。它没有额外依赖:EventInterface 与 StoppableEventInterface 这两个类型对应 PSR-14 的合约,无须引入 psr/event-dispatcher 包。
概念总览
标题为“概念总览”的章节这个模块由三个部分组成:一个分发器、一个监听器提供器(listener provider),以及一组固定的生命周期事件类。
EventDispatcher 接收一个 EventInterface 实例。它会向 ListenerProvider 查询匹配的监听器,然后按优先级逐一调用每个监听器。dispatch() 方法会返回同一个事件对象。因此,监听器可以读取引擎放入事件中的状态,也可以回写状态,供引擎稍后读取。这就是 PSR-14 分发器的形态。
ListenerProvider 会将事件类映射到一份按优先级排列的可调用条目列表。注册范围仅限于实例,不包含任何静态状态。一个 worker 进程可以持有自己的提供器实例。提供器也会遍历事件类的继承树及其接口。因此,注册在 AbstractEvent 上的监听器会看到每个生命周期事件;注册在某个接口上的监听器,会看到所有实现该接口的事件。
StoppableEventInterface 加入了传播控制能力。监听器可以调用 stopPropagation(),随后分发器会停止在这一轮中调用后续监听器。可停止事件是一种特殊情况,它自带终止监听器链的方式。PSR-14 对可停止事件描述了相同模型(PSR-14 psr_14_event#x4)。AbstractEvent 通过 StoppableEventTrait 实现该接口。因此,每个生命周期事件默认都是可停止的。
分发器有一条零额外开销的快速路径。它会检查事件类或任何祖先类上是否注册了监听器。当完全没有监听器时,hasListeners() 会返回 false,而 dispatch() 会立即返回。没有监听器的文件在每个生命周期点只需一次布尔检查的成本。
EventAwareDocumentTrait 是集成接点。它持有一个可选的 EventDispatcher,并公开受保护的分发辅助方法。Document 类会在每个生命周期点调用这些辅助方法。未设置分发器时,每个辅助方法都是空操作(no-op)。
生命周期事件分类:
| 事件 | 命名空间 | 触发时机 |
|---|---|---|
DocumentCreatedEvent | Event\Document | 在文件完整构建完成后 |
PageAddedEvent | Event\Document | 在页面初始化后 |
ContentRenderedEvent | Event\Content | 在 HTML 或文本内容绘制到某一页之后 |
FontLoadedEvent | Event\Content | 当字体完成解析并载入注册表时 |
EncryptionAppliedEvent | Event\Security | 在加密参数设置完成后 |
SignatureAppliedEvent | Event\Security | 在签名嵌入后 |
PdfSerializedEvent | Event\Writer | 在序列化之后、输出交付之前 |
DocumentOutputEvent | Event\Document | 在 PDF 字节送达目标位置之前 |
API 接口
标题为“API 接口”的章节| 符号 | 种类 | 主要成员 |
|---|---|---|
NextPDF\Event\EventInterface | 接口 | getEventName(): non-empty-string |
NextPDF\Event\StoppableEventInterface | 接口 | isPropagationStopped(): bool |
NextPDF\Event\StoppableEventTrait | trait | isPropagationStopped(), stopPropagation() |
NextPDF\Event\AbstractEvent | 抽象类 | getEventName();实现 StoppableEventInterface |
NextPDF\Event\EventDispatcher | final class | dispatch(EventInterface): EventInterface, getListenerProvider() |
NextPDF\Event\ListenerProvider | final class | addListener(), getListenersForEvent(), hasListeners(), getListenerCount(), clearListeners() |
NextPDF\Event\EventAwareDocumentTrait | trait | setEventDispatcher(), getEventDispatcher() |
NextPDF\Event\Document\DocumentCreatedEvent | final class | $document, $config |
NextPDF\Event\Document\PageAddedEvent | final class | $document, $pageIndex, $pageSize, $orientation |
NextPDF\Event\Document\DocumentOutputEvent | final class | getPdfData(), setPdfData(), getByteSize(), $filename, $destination |
NextPDF\Event\Content\ContentRenderedEvent | final class | $document, $pageIndex, $contentType, $content |
NextPDF\Event\Content\FontLoadedEvent | final class | $family, $style, $fontType, $filePath |
NextPDF\Event\Security\EncryptionAppliedEvent | final class | $document, $algorithm, $allowPrint, $allowCopy, $allowModify |
NextPDF\Event\Security\SignatureAppliedEvent | final class | $document, $signatureLevel, $signerName, $reason, $location |
NextPDF\Event\Writer\PdfSerializedEvent | final class | $byteSize, $objectCount, $pageCount, $pdfVersion, $isLinearized, $isEncrypted |
addListener() 会在事件类字符串为空时抛出 NextPDF\Exception\InvalidConfigException。
代码示例 — 快速上手
标题为“代码示例 — 快速上手”的章节注册一个监听器并分发一个生命周期事件。
<?php
declare(strict_types=1);
use NextPDF\Event\Document\PageAddedEvent;use NextPDF\Event\EventDispatcher;use NextPDF\Event\ListenerProvider;
$provider = new ListenerProvider();$provider->addListener( PageAddedEvent::class, static function (PageAddedEvent $event): void { \printf("Page %d added (%s)\n", $event->pageIndex, $event->pageSize->name); },);
$dispatcher = new EventDispatcher($provider);当通过 setEventDispatcher() 为 Document 类挂载分发器后,它会在内部调用分发辅助方法。
代码示例 — 生产环境
标题为“代码示例 — 生产环境”的章节将分发器接入文件,使用监听器优先级,并通过一个 gate 监听器停止传播。
<?php
declare(strict_types=1);
use NextPDF\Event\Document\DocumentOutputEvent;use NextPDF\Event\Document\PageAddedEvent;use NextPDF\Event\EventDispatcher;use NextPDF\Event\ListenerProvider;
$provider = new ListenerProvider();
// Higher priority runs first. A licensing gate observes every page.$provider->addListener( PageAddedEvent::class, static function (PageAddedEvent $event): void { if ($event->pageIndex >= 100) { // Stop later page listeners for this dispatch cycle. $event->stopPropagation(); } }, priority: 100,);
// Last-chance hook: replace the PDF bytes before delivery.$provider->addListener( DocumentOutputEvent::class, static function (DocumentOutputEvent $event): void { $optimized = \gzencode($event->getPdfData(), 0); if ($optimized !== false) { $event->setPdfData($optimized); } },);
$dispatcher = new EventDispatcher($provider);// Pass $dispatcher to a Document via setEventDispatcher($dispatcher).边界情况与陷阱
标题为“边界情况与陷阱”的章节- 通配符注册会利用类继承层级。由于每个事件都继承自
AbstractEvent,注册在它上面的监听器会收到每个生命周期事件。当你只想处理单个事件时,请将监听器范围限定为具体类。 - 监听器优先级由高到低排序。优先级相同者保留插入顺序(稳定排序)。
stopPropagation()只会中止当前这一轮分发。下一个被分发的事件会开启一轮全新的循环。- 每次调用
addListener()都会使已排序的监听器缓存失效,因为新增的父类或接口注册可能改变多个事件类的 resolve(解析)结果。 $document在文件范围事件中的类型声明为object,而不是Document类,以避免 Event 模块对Core产生硬依赖。DocumentOutputEvent::setPdfData()预期接收非空字符串。用空字符串替换载荷会生成一份无效文件。- 在设置分发器之前,
EventAwareDocumentTrait上的分发辅助方法都是空操作(no-op),因此没有监听器时执行不会增加可测量的成本。
对于叶子事件类,没有监听器时的分发是 O(1):执行一次 hasListeners() 布尔检查,然后立即返回。有监听器时,getListenersForEvent() 会遍历一次事件的继承关系,对收集到的条目排序,并按事件类缓存已排序列表,直到下一次变更。因此,重复分发同一类的成本为 O(k),其中 k 为匹配的监听器数量。本参考页的默认 performance_budget 为 wall_ms: 1500、peak_mb: 64。
安全注意事项
标题为“安全注意事项”的章节监听器在生成流水线内以与调用方相同的权限执行。请将监听器代码视为受信任代码。DocumentOutputEvent 会暴露最终的 PDF 二进制内容,并允许监听器将其替换。审计或完整性监听器应在任何会转换字节的监听器之前执行(使用更高的优先级)。安全范围事件(EncryptionAppliedEvent、SignatureAppliedEvent)会报告已应用的参数,供审计记录使用。它们不会改变密码学结果。
符合性
标题为“符合性”的章节| 规范 | 条款 | 主题 |
|---|---|---|
| PSR-14(PHP-FIG) | psr_14_event#x4 | 可停止事件会中止后续监听器 |
本包的 dispatch() 签名、监听器提供器的拆分以及可停止事件模型均遵循 PSR-14。NextPDF 声明了自己的 EventInterface 与 StoppableEventInterface。本包没有 PSR-14 的运行时依赖,同时仍保持鸭子类型兼容。
另请参阅
标题为“另请参阅”的章节/modules/core/contracts/— 公开接口面/modules/core/observability/— 遥测与指标钩子/modules/core/audit/— 审计轨迹集成/modules/core/config/—Config,随DocumentCreatedEvent提供/modules/core/exception/—InvalidConfigException,由addListener()抛出
词汇表:PSR-14 · stoppable event · listener provider