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上的派送輔助方法都是無作用,因此沒有監聽器的執行不會增加可量測的成本。
對於葉節點事件類別,沒有監聽器的派送是 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