動作觸發器與事件監聽器
重點摘要
標題為「重點摘要」的區段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 在 resolve(解析)監聽器時,會走訪事件的父類別與介面。將監聽器綁定到
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 等術語。請參閱已發布的詞彙表,了解每個術語的規範定義。