Zum Inhalt springen

Action-Trigger und Event-Listener

NextPDF löst Lebenszyklus-Events über ein PSR-14-kompatibles System in NextPDF\Event aus. Registrieren Sie einen Listener mit einer Priorität. Reagieren Sie auf das Erstellen eines Dokuments, eine neue Seite, das Laden einer Schrift, das Signieren, das Verschlüsseln, das Schreiben oder die Ausgabe. Stoppen Sie die Kette bei Bedarf.

Terminal-Fenster
composer require nextpdf/core:^3

Das Event-System besteht aus zwei öffentlichen Teilen. ListenerProvider ordnet einer Event-Klasse eine sortierte Liste von Listener-Callables zu. EventDispatcher durchläuft diese Liste und ruft jeden Listener in der Reihenfolge der Priorität auf. Beide Klassen sind final; erweitern Sie sie daher über Komposition statt durch Subklassenbildung.

Beide Klassen entsprechen PSR-14 über Duck-Typing. EventDispatcher::dispatch() entspricht der PSR-14-Signatur dispatch() und gibt das Event zurück, nachdem jeder Listener ausgeführt wurde. ListenerProvider::getListenersForEvent() entspricht der PSR-14-Provider-Signatur. NextPDF setzt das PSR-14-Paket nicht voraus; wenn Ihr Projekt es jedoch verwendet, passen die Interfaces trotzdem zusammen.

Für Extension-Autoren sind zwei Verhaltensweisen wichtig:

  • Wildcard-Listening. Um Listener aufzulösen, durchläuft der Provider die Elternklassen des Events und dessen Interfaces. Binden Sie einen Listener an die Basisklasse AbstractEvent, um jedes Lebenszyklus-Event zu beobachten. Binden Sie ihn an ein Interface, um eine ganze Familie abzufangen.
  • Priorität und Weitergabe. Listener mit höherer Priorität werden zuerst ausgeführt. Bei gleicher Priorität bleibt die Registrierungsreihenfolge erhalten. Jedes Event, das AbstractEvent erweitert, ist stoppbar. Ein Listener kann stopPropagation() aufrufen. Der Dispatcher überspringt dann die verbleibenden Listener.

Der Dispatcher verfügt über einen Fast-Path ohne Zusatzkosten. Wenn für eine Event-Klasse oder eine Elternklasse kein Listener gebunden ist, kehrt dispatch() sofort nach einer einzigen hasListeners()-Prüfung zurück.

EventNamespaceAusgelöst, wennStabilität
DocumentCreatedEventNextPDF\Event\DocumentDie Konstruktion eines Dokuments ist abgeschlossenexperimental
PageAddedEventNextPDF\Event\DocumentEine Seite ist vollständig initialisiertexperimental
ContentRenderedEventNextPDF\Event\ContentInhalt wird auf eine Seite gerendertexperimental
FontLoadedEventNextPDF\Event\ContentEine Schriftfamilie und ein Schriftstil werden zum ersten Mal geladenexperimental
SignatureAppliedEventNextPDF\Event\SecuritySignatur-Bytes werden eingebettetexperimental
EncryptionAppliedEventNextPDF\Event\SecurityDie Verschlüsselung wird konfiguriertexperimental
PdfSerializedEventNextPDF\Event\WriterDie Serialisierung schließt abexperimental
DocumentOutputEventNextPDF\Event\DocumentVor der Auslieferung der Ausgabeexperimental

Der Dispatcher, der Provider, das Marker-Interface und die Basisklasse sind stable (seit 3.0.0). Die Event-Payloads sind experimental. Ihre Konstruktorargumente und readonly-Eigenschaften können sich in einem Minor-Release ändern. Ein Patch-Release fügt nur hinzu. Berücksichtigen Sie diese Stabilitätsstufe, wenn Sie an die Namen der Payload-Eigenschaften binden.

NextPDF\Event\ListenerProvider (stable, final):

MethodeRückgabeZweck
addListener(string $eventClass, callable $listener, int $priority = 0)voidRegistriert einen Listener; eine höhere Priorität wird zuerst ausgeführt. Wirft bei einer leeren Klasse InvalidConfigException.
getListenersForEvent(EventInterface $event)list<callable>Löst Listener auf, einschließlich der Eltern- und Interface-Registrierungen.
hasListeners(string $eventClass)boolPrüfung ohne Overhead über die Klassenhierarchie hinweg.
getListenerCount(string $eventClass)intZählt nur die direkten Registrierungen.
clearListeners()voidSetzt den Provider zurück.

NextPDF\Event\EventDispatcher (stable, final):

MethodeRückgabeZweck
dispatch(EventInterface $event)EventInterfaceRuft Listener in der Reihenfolge der Priorität auf; respektiert den Propagation-Stopp; gibt das Event zurück.
getListenerProvider()ListenerProviderGreift auf den Provider zu, um zur Laufzeit Listener hinzuzufügen.

Dokumente, die Events auslösen, verwenden NextPDF\Event\EventAwareDocumentTrait. Seine Methode setEventDispatcher() bindet einen Dispatcher in ein Dokument ein. Ohne Dispatcher bleibt jeder Dispatch-Helfer wirkungslos.

<?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);

Dies ist ein Audit-Listener für die Produktion. Er verwendet eine hohe Priorität, damit er zuerst ausgeführt wird, protokolliert strukturiert und ergänzt der Vollständigkeit halber einen Catch-all-Listener auf der Basisklasse.

<?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);
}
}
  • Finale Klassen. EventDispatcher und ListenerProvider sind final. Nutzen Sie Komposition statt Subklassenbildung.
  • Eine leere Event-Klasse wirft. addListener('', ...) wirft InvalidConfigException. Übergeben Sie immer eine Class-String-Konstante.
  • Wildcard-Kosten. Ein Listener auf AbstractEvent wird für jedes Event ausgelöst. Geben Sie Catch-all-Listenern eine niedrige Priorität. Halten Sie sie leichtgewichtig.
  • Mutation der Ausgabe. DocumentOutputEvent enthält die PDF-Bytes. Die Engine liest sie nach dem Dispatch zurück. Wenn Sie diese Bytes ändern, erhalten Sie weitreichende Kontrolle, tragen aber auch ein echtes Risiko. Ein falscher Byte-Offset beschädigt das PDF und kann eine Signatur brechen. Beobachten Sie lieber, es sei denn, Sie verantworten das Ergebnis im Hinblick auf Determinismus und Signaturen.
  • Kein Dispatcher, keine Events. Ein Dokument, bei dem kein Dispatcher über EventAwareDocumentTrait gesetzt wurde, löst keine Events aus. Dies ist der beabsichtigte Pfad ohne Zusatzkosten. Es ist kein Einrichtungsfehler.

Der Fast-Path besteht aus einer einzigen hasListeners()-Prüfung entlang der Elternkette. Ohne Listener ist das Dispatchen nahezu ohne Overhead. Der Provider cacht die sortierte Listener-Liste pro Event-Klasse. Er leert diesen Cache nur bei einer Änderung. Halten Sie Listener nicht-blockierend. Sie laufen inline auf dem Render-Pfad.

SignatureAppliedEvent und EncryptionAppliedEvent sind die Audit-Anker. Registrieren Sie Listener mit hoher Priorität, um das Signieren und Verschlüsseln in einen manipulationssicheren Speicher zu protokollieren. Stoppen Sie die Kette bei einem Sicherheits-Event nicht, es sei denn, Sie möchten spätere Listener stummschalten. Wenn Sie sie stoppen, können nachgelagerte Audit-Hooks stillschweigend abgeschaltet werden.

Auf dieser Seite werden keine normativen Aussagen über die PSR-14-Kompatibilität hinaus getroffen, die rein per Duck-Typing besteht und das PSR-14-Paket nicht voraussetzt.

NextPDF Enterprise liefert auditierte Listener für Signatur- und Verschlüsselungs-Events, die ein manipulationssicheres Audit-Log speisen. Der Listener-Vertrag ist die öffentliche Event-API, sodass Ihre eigenen Listener mit den Enterprise-Listenern auf demselben Provider koexistieren.

Das Glossar definiert die Begriffe Event-Listener, Event-Dispatcher, Listener-Provider und stoppbares Event, die auf dieser Seite verwendet werden. Die jeweiligen kanonischen Definitionen finden Sie im veröffentlichten Glossar.