Event: PSR-14 ライフサイクルイベントの分類体系
Event モジュールは、PDF 生成の各段階で型付けされたライフサイクルイベントをディスパッチします。リスナーは、エンジン内部を変更せずにドキュメントを監視または変換できます。ディスパッチャーは PSR-14 モデルに従うため、既存の PSR-14 ツールとの互換性が保たれます。
インストール
「インストール」という見出しのセクションcomposer require nextpdf/core:^3Event モジュールは core パッケージに同梱されています。追加の依存関係はありません。EventInterface 型と StoppableEventInterface 型は、psr/event-dispatcher パッケージを必要とせずに PSR-14 のコントラクトを反映します。
概念の概要
「概念の概要」という見出しのセクションこのモジュールは、ディスパッチャー、リスナープロバイダー、固定されたライフサイクルイベントクラスのセットという 3 つの部分で構成されています。
EventDispatcher は EventInterface インスタンスを受け取り、一致するリスナーを ListenerProvider に問い合わせ、各リスナーを優先度順に呼び出します。dispatch() メソッドは同じイベントオブジェクトを返します。これにより、リスナーはエンジンがイベントに格納した状態を読み取ることができます。また、エンジンが後で読み取る状態を書き戻すこともできます。これが PSR-14 ディスパッチャーの形式です。
ListenerProvider は、イベントクラスを優先度順の callable のリストにマッピングします。登録はインスタンス単位でスコープされ、静的な状態はありません。ワーカープロセスは、それぞれ独自のプロバイダーインスタンスを保持できます。プロバイダーは、イベントクラスのツリーとそのインターフェイスもたどります。そのため、AbstractEvent に対するリスナーは、すべてのライフサイクルイベントを受け取ります。インターフェイスに対するリスナーは、それを実装するすべてのイベントを受け取ります。
StoppableEventInterface は伝播制御を追加します。リスナーは stopPropagation() を呼び出すことができます。呼び出されると、ディスパッチャーはそのサイクルにおける以降のリスナー呼び出しを停止します。停止可能なイベントは特別なケースの 1 つであり、リスナーチェーンを停止するための独自の手段を備えています。PSR-14 は、停止可能なイベントについて同じモデルを説明しています(PSR-14 psr_14_event#x4)。AbstractEvent は、StoppableEventTrait を通じてこのインターフェイスを実装します。そのため、すべてのライフサイクルイベントはデフォルトで停止可能です。
ディスパッチャーは、オーバーヘッドのない高速パスを備えています。イベントクラスまたはその先祖クラスにリスナーがあるかどうかを確認し、リスナーが存在しない場合、hasListeners() は false を返し、dispatch() はただちに戻ります。リスナーのないドキュメントでは、ライフサイクルポイントごとにブール値のチェックを 1 回行うだけです。
EventAwareDocumentTrait は統合の接点です。オプションの EventDispatcher を保持し、protected なディスパッチヘルパーを提供します。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 | interface | getEventName(): non-empty-string |
NextPDF\Event\StoppableEventInterface | interface | isPropagationStopped(): bool |
NextPDF\Event\StoppableEventTrait | trait | isPropagationStopped(), stopPropagation() |
NextPDF\Event\AbstractEvent | abstract class | 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);Document クラスは、ディスパッチャーが setEventDispatcher() を通じてアタッチされると、内部でディスパッチヘルパーを呼び出します。
コードサンプル — プロダクション
「コードサンプル — プロダクション」という見出しのセクションディスパッチャーをドキュメントに組み込み、リスナーの優先度を使用し、ゲートリスナーで伝播を停止します。
<?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を継承しているため、これに対するリスナーはすべてのライフサイクルイベントを受け取ります。1 つのイベントのみを対象にしたい場合は、リスナーを具象クラスにスコープします。 - リスナーの優先度は、高いものから順にソートされます。優先度が等しい場合は、挿入順が保持されます(安定ソート)。
stopPropagation()は、現在のディスパッチサイクルのみを停止します。次にディスパッチされるイベントでは、新しいサイクルが開始されます。- 新しい親またはインターフェイスを登録すると複数のイベントクラスの resolve(解決)が変わる可能性があるため、ソート済みリスナーキャッシュは
addListener()呼び出しのたびに無効化されます。 $document(ドキュメントスコープのイベント上)はobjectとして型付けされており、Documentクラスではありません。これは、Event モジュールがCoreへの強い依存を持たないようにするためです。DocumentOutputEvent::setPdfData()は、空でない文字列を期待します。ペイロードを空の文字列に置き換えると、無効なドキュメントが生成されます。- ディスパッチヘルパー(
EventAwareDocumentTrait上)は、ディスパッチャーが設定されるまで no-op であるため、リスナーのない実行では測定可能なコストは追加されません。
パフォーマンス
「パフォーマンス」という見出しのセクションリスナーのないディスパッチは、リーフのイベントクラスでは O(1) です。すなわち、hasListeners() によるブール値チェックを 1 回行い、その後ただちに戻ります。リスナーがある場合、getListenersForEvent() はイベントの先祖を 1 回たどり、収集したエントリをソートして、次の変更までイベントクラスごとにソート済みリストをキャッシュします。そのため、同じクラスの繰り返しのディスパッチは、一致した k 個のリスナーに対して O(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 · 停止可能イベント · リスナープロバイダー