跳到內容

動作觸發器與事件監聽器

NextPDF 透過 NextPDF\Event 中相容於 PSR-14 的系統觸發生命週期事件。你可以用指定的優先順序註冊監聽器,回應文件建立、新增頁面、字型載入、簽署、加密、寫出或輸出等動作,並在需要時中斷整條鏈。

Terminal window
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() 檢查,然後立即回傳。

事件命名空間觸發時機穩定性
DocumentCreatedEventNextPDF\Event\Document文件建構完成實驗性
PageAddedEventNextPDF\Event\Document頁面初始化完成實驗性
ContentRenderedEventNextPDF\Event\Content內容已渲染到頁面實驗性
FontLoadedEventNextPDF\Event\Content某個字型家族與樣式首次載入實驗性
SignatureAppliedEventNextPDF\Event\Security簽章位元組已嵌入實驗性
EncryptionAppliedEventNextPDF\Event\Security加密設定已完成實驗性
PdfSerializedEventNextPDF\Event\Writer序列化完成實驗性
DocumentOutputEventNextPDF\Event\Document輸出交付前實驗性

dispatcher、provider、標記介面與基底類別都是 stable(自 3.0.0 起)。事件負載則是 experimental;其建構子引數與 readonly 屬性可能在 minor 版本中變動。patch 版本只會新增內容。綁定負載屬性名稱時,請把這點納入考量。

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 類別。 EventDispatcherListenerProvider 都是 final。請用組合,不要用繼承。
  • 空的事件類別會擲出例外。 addListener('', ...) 會擲出 InvalidConfigException。請一律傳入 class-string 常數。
  • 萬用字元的成本。 綁定在 AbstractEvent 上的監聽器會對每一個事件觸發。請給 catch-all 監聽器較低的優先順序,並讓它們保持輕量。
  • 輸出變更。 DocumentOutputEvent 帶有 PDF 位元組。引擎會在 dispatch 之後把它們讀回。變更這些位元組能取得廣泛的控制權,也帶有真正的風險。錯誤的位元組偏移量會讓 PDF 損毀,並可能破壞簽章。除非你掌控最終結果,並能確保決定性與簽章正確,否則請以觀察為主。
  • 沒有 dispatcher,就沒有事件。 沒有透過 EventAwareDocumentTrait 設定 dispatcher 的文件不會觸發任何事件。這是刻意設計的零成本路徑,並不是設定錯誤。

快速路徑是在父類別鏈上執行一次 hasListeners() 檢查。沒有監聽器時,dispatch 幾乎沒有成本。provider 會針對每個事件類別快取已排序的監聽器清單;只有發生變更時,才會清除那份快取。請讓監聽器保持非阻塞。它們會在渲染路徑上以行內方式執行。

SignatureAppliedEventEncryptionAppliedEvent 是稽核的錨點。請註冊高優先順序的監聽器,把簽署與加密記錄到防竄改的儲存區。除非你刻意要讓後續監聽器靜默,否則不要在安全性事件上中斷整條鏈。中斷它可能會悄悄停用後續執行的稽核掛鉤。

本頁除 PSR-14 相容性之外,不做任何規範性主張;而該相容性僅屬 duck-type,不需要 PSR-14 套件。

NextPDF Enterprise 隨附經稽核的監聽器,用於簽章與加密事件,並寫入防竄改的稽核記錄。監聽器契約就是公開的事件 API,因此你自己的監聽器能與 Enterprise 的監聽器共存於同一個 provider 上。

詞彙表定義了本頁使用的 event listenerevent dispatcherlistener providerstoppable event 等術語。請參閱已發布的詞彙表,了解每個術語的規範定義。