Event: PSR-14-Taxonomie für Lebenszyklus-Events
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Das Event-Modul löst in jeder Phase der PDF-Erzeugung typisierte Lebenszyklus-Events aus. Listener beobachten oder transformieren ein Dokument, ohne die internen Abläufe der Engine zu verändern. Der Dispatcher folgt dem PSR-14-Modell; dadurch bleibt bestehendes PSR-14-Tooling kompatibel.
Installation
Abschnitt betitelt „Installation“composer require nextpdf/core:^3Das Event-Modul wird mit dem Core-Paket ausgeliefert. Es bringt keine zusätzliche Abhängigkeit mit: Die Typen EventInterface und StoppableEventInterface bilden die PSR-14-Verträge nach, ohne das Paket psr/event-dispatcher vorauszusetzen.
Konzeptioneller Überblick
Abschnitt betitelt „Konzeptioneller Überblick“Das Modul besteht aus drei Teilen: einem Dispatcher, einem Listener-Provider und einem festen Satz von Lebenszyklus-Event-Klassen.
EventDispatcher nimmt eine Instanz von EventInterface entgegen, fragt beim ListenerProvider die passenden Listener ab und ruft sie anschließend in Prioritätsreihenfolge auf. Die Methode dispatch() gibt dasselbe Event-Objekt zurück. Dadurch kann ein Listener Zustand lesen, den die Engine im Event abgelegt hat, und Zustand zurückschreiben, den die Engine später ausliest. Das entspricht der Form eines PSR-14-Dispatchers.
ListenerProvider bildet eine Event-Klasse auf eine Liste von Callables in Prioritätsreihenfolge ab. Die Registrierung ist instanzgebunden; es gibt keinen statischen Zustand. Ein Worker-Prozess kann eigene Provider-Instanzen halten. Der Provider durchläuft außerdem die Klassenhierarchie des Events und seine Interfaces. So erhält ein Listener für AbstractEvent jedes Lebenszyklus-Event. Ein Listener für ein Interface erhält jedes Event, das dieses Interface implementiert.
StoppableEventInterface ergänzt eine Steuerung der Weitergabe. Ein Listener kann stopPropagation() aufrufen. Der Dispatcher ruft dann für diesen Zyklus keine weiteren Listener mehr auf. Ein stoppbares Event ist ein Sonderfall und bringt seinen eigenen Mechanismus mit, um die Listener-Kette anzuhalten. PSR-14 beschreibt dasselbe Modell für stoppbare Events (PSR-14 psr_14_event#x4). AbstractEvent implementiert das Interface über StoppableEventTrait. Damit ist jedes Lebenszyklus-Event standardmäßig stoppbar.
Der Dispatcher hat einen Schnellpfad ohne Overhead. Er prüft, ob es einen Listener für die Event-Klasse oder einen beliebigen Vorfahren gibt. Ist keiner vorhanden, liefert hasListeners() den Wert false, und dispatch() kehrt sofort zurück. Ein Dokument ohne Listener verursacht an jedem Lebenszyklus-Punkt nur eine boolesche Prüfung.
EventAwareDocumentTrait bildet die Integrationsstelle und hält einen optionalen EventDispatcher. Es stellt geschützte Dispatch-Helfer bereit. Die Klasse Document ruft diese Helfer an jedem Lebenszyklus-Punkt auf. Wenn kein Dispatcher gesetzt ist, ist jeder Helfer ein No-Op.
Die Lebenszyklus-Event-Taxonomie:
| Event | Namespace | Ausgelöst |
|---|---|---|
DocumentCreatedEvent | Event\Document | Nachdem ein Dokument vollständig konstruiert wurde |
PageAddedEvent | Event\Document | Nachdem eine Seite initialisiert wurde |
ContentRenderedEvent | Event\Content | Nachdem HTML- oder Textinhalt auf eine Seite gerendert wurde |
FontLoadedEvent | Event\Content | Wenn eine Schrift in die Registry geparst wird |
EncryptionAppliedEvent | Event\Security | Nachdem die Verschlüsselungsparameter konfiguriert wurden |
SignatureAppliedEvent | Event\Security | Nachdem eine Signatur eingebettet wurde |
PdfSerializedEvent | Event\Writer | Nach der Serialisierung, vor der Auslieferung der Ausgabe |
DocumentOutputEvent | Event\Document | Bevor die PDF-Bytes ihr Ziel erreichen |
API-Oberfläche
Abschnitt betitelt „API-Oberfläche“| Symbol | Art | Wichtige Mitglieder |
|---|---|---|
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(); implementiert 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() wirft NextPDF\Exception\InvalidConfigException, wenn der Event-Klassenstring leer ist.
Codebeispiel — Schnellstart
Abschnitt betitelt „Codebeispiel — Schnellstart“Registrieren Sie einen Listener und lösen Sie ein Lebenszyklus-Event aus.
<?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);Die Klasse Document ruft die Dispatch-Helfer intern auf, sobald ein Dispatcher über setEventDispatcher() angebunden wurde.
Codebeispiel — Produktion
Abschnitt betitelt „Codebeispiel — Produktion“Verdrahten Sie den Dispatcher mit einem Dokument, nutzen Sie Listener-Priorität und stoppen Sie die Weitergabe aus einem Gate-Listener heraus.
<?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).Sonderfälle & Stolperfallen
Abschnitt betitelt „Sonderfälle & Stolperfallen“- Die Wildcard-Registrierung nutzt die Klassenhierarchie. Ein Listener für
AbstractEventempfängt jedes Lebenszyklus-Event, weil jedes Event davon erbt. Beschränken Sie Listener auf konkrete Klassen, wenn Sie nur ein einziges Event adressieren möchten. - Die Listener-Priorität sortiert die höchste Priorität zuerst. Gleiche Prioritäten behalten die Einfügereihenfolge (stabile Sortierung).
stopPropagation()hält nur den aktuellen Dispatch-Zyklus an. Das nächste ausgelöste Event beginnt einen frischen Zyklus.- Der sortierte Listener-Cache wird bei jedem Aufruf von
addListener()ungültig, da eine neue Eltern- oder Interface-Registrierung die Auflösung für mehrere Event-Klassen ändern kann. $documentauf den dokumentbezogenen Events ist alsobjecttypisiert, nicht als KlasseDocument, um das Event-Modul von einer harten Abhängigkeit vonCorefreizuhalten.DocumentOutputEvent::setPdfData()erwartet einen nicht leeren String. Wird die Nutzlast durch einen leeren String ersetzt, entsteht ein ungültiges Dokument.- Die Dispatch-Helfer in
EventAwareDocumentTraitsind No-Ops, bis ein Dispatcher gesetzt ist, sodass Läufe ohne Listener keine messbaren Kosten verursachen.
Performance
Abschnitt betitelt „Performance“Ein Dispatch ohne Listener ist O(1) für eine Blatt-Event-Klasse: eine boolesche Prüfung mit hasListeners(), danach die sofortige Rückkehr. Mit Listenern durchläuft getListenersForEvent() die Event-Abstammung einmal, sortiert die gesammelten Einträge und cacht die sortierte Liste pro Event-Klasse bis zur nächsten Mutation. Wiederholtes Dispatching derselben Klasse ist daher O(k) über k passende Listener. Das Standard-performance_budget für diese Referenzseite ist wall_ms: 1500, peak_mb: 64.
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“Listener laufen innerhalb der Erzeugungs-Pipeline mit denselben Rechten wie der Aufrufer. Behandeln Sie Listener-Code als vertrauenswürdigen Code. DocumentOutputEvent legt die finale PDF-Binärdatei offen und lässt einen Listener sie ersetzen. Ein Audit- oder Integritäts-Listener sollte vor jedem Listener laufen, der die Bytes transformiert (verwenden Sie eine höhere Priorität). Die sicherheitsbezogenen Events (EncryptionAppliedEvent, SignatureAppliedEvent) melden die angewendeten Parameter für die Audit-Protokollierung. Sie ändern das kryptografische Ergebnis nicht.
Konformität
Abschnitt betitelt „Konformität“| Spezifikation | Klausel | Thema |
|---|---|---|
| PSR-14 (PHP-FIG) | psr_14_event#x4 | Stoppbares Event hält weitere Listener an |
Die Signatur von dispatch(), die Aufteilung in Listener-Provider und das Modell für stoppbare Events folgen PSR-14. NextPDF deklariert sein eigenes EventInterface und StoppableEventInterface. Das Paket hat keine PSR-14-Laufzeitabhängigkeit und bleibt zugleich duck-type-kompatibel.
Siehe auch
Abschnitt betitelt „Siehe auch“/modules/core/contracts/— öffentliche Interface-Oberfläche/modules/core/observability/— Telemetrie- und Metrik-Hooks/modules/core/audit/— Integration des Audit-Trails/modules/core/config/—Config, weitergereicht überDocumentCreatedEvent/modules/core/exception/—InvalidConfigExceptionausaddListener()
Glossar: PSR-14 · stoppbares Event · Listener-Provider