Salta ai contenuti

Event: tassonomia PSR-14 degli eventi del ciclo di vita

Il modulo Event distribuisce eventi del ciclo di vita tipizzati in ogni fase della generazione del PDF. I listener possono osservare o trasformare un documento senza modificare gli interni del motore. Il dispatcher segue il modello PSR-14, quindi gli strumenti PSR-14 esistenti rimangono compatibili.

Terminal window
composer require nextpdf/core:^3

Il modulo Event è incluso nel pacchetto core. Non ha dipendenze aggiuntive: i tipi EventInterface e StoppableEventInterface rispecchiano i contratti PSR-14 senza richiedere il pacchetto psr/event-dispatcher.

Il modulo è composto da tre parti: un dispatcher, un provider di listener e un insieme fisso di classi di eventi del ciclo di vita.

EventDispatcher riceve un’istanza di EventInterface, richiede al ListenerProvider i listener corrispondenti e li richiama in ordine di priorità. Il metodo dispatch() restituisce lo stesso oggetto evento. In questo modo un listener può leggere lo stato che il motore ha inserito nell’evento. Può inoltre riscrivere lo stato che il motore leggerà in seguito. Questa è la forma del dispatcher PSR-14.

ListenerProvider associa una classe di evento a un elenco di callable in ordine di priorità. La registrazione è circoscritta all’istanza. Non esiste alcuno stato statico. Un processo worker può mantenere le proprie istanze di provider. Il provider percorre inoltre l’albero della classe di evento e le sue interfacce. In questo modo un listener su AbstractEvent vede ogni evento del ciclo di vita. Un listener su un’interfaccia vede ogni evento che la implementa.

StoppableEventInterface aggiunge il controllo della propagazione. Un listener può chiamare stopPropagation(). Il dispatcher smette quindi di richiamare ulteriori listener per quel ciclo. Un evento interrompibile è un caso speciale: porta con sé il proprio meccanismo per arrestare la catena dei listener. PSR-14 descrive lo stesso modello per gli eventi interrompibili (PSR-14 psr_14_event#x4). AbstractEvent implementa l’interfaccia tramite StoppableEventTrait. Di conseguenza, ogni evento del ciclo di vita è interrompibile per impostazione predefinita.

Il dispatcher dispone di un percorso rapido a costo zero. Controlla la presenza di un listener sulla classe di evento o su un qualsiasi antenato. Quando non ne esiste alcuno, hasListeners() restituisce false e dispatch() ritorna immediatamente. Un documento senza listener sostiene solo un controllo booleano per ogni punto del ciclo di vita.

EventAwareDocumentTrait è il punto di integrazione. Mantiene un EventDispatcher opzionale ed espone helper di distribuzione protetti. La classe Document richiama questi helper a ogni punto del ciclo di vita. Quando non è impostato alcun dispatcher, ogni helper è un’operazione nulla.

La tassonomia degli eventi del ciclo di vita:

EventoNamespaceGenerato
DocumentCreatedEventEvent\DocumentDopo che un documento è stato costruito completamente
PageAddedEventEvent\DocumentDopo che una pagina è stata inizializzata
ContentRenderedEventEvent\ContentDopo che il contenuto HTML o testuale è stato reso su una pagina
FontLoadedEventEvent\ContentQuando un font è stato analizzato e inserito nel registro
EncryptionAppliedEventEvent\SecurityDopo che i parametri di cifratura sono stati configurati
SignatureAppliedEventEvent\SecurityDopo che una firma è stata incorporata
PdfSerializedEventEvent\WriterDopo la serializzazione, prima della consegna dell’output
DocumentOutputEventEvent\DocumentPrima che i byte del PDF arrivino alla destinazione
SimboloTipoMembri principali
NextPDF\Event\EventInterfaceinterfacciagetEventName(): non-empty-string
NextPDF\Event\StoppableEventInterfaceinterfacciaisPropagationStopped(): bool
NextPDF\Event\StoppableEventTraittraitisPropagationStopped(), stopPropagation()
NextPDF\Event\AbstractEventclasse astrattagetEventName(); implementa StoppableEventInterface
NextPDF\Event\EventDispatcherclasse finaldispatch(EventInterface): EventInterface, getListenerProvider()
NextPDF\Event\ListenerProviderclasse finaladdListener(), getListenersForEvent(), hasListeners(), getListenerCount(), clearListeners()
NextPDF\Event\EventAwareDocumentTraittraitsetEventDispatcher(), getEventDispatcher()
NextPDF\Event\Document\DocumentCreatedEventclasse final$document, $config
NextPDF\Event\Document\PageAddedEventclasse final$document, $pageIndex, $pageSize, $orientation
NextPDF\Event\Document\DocumentOutputEventclasse finalgetPdfData(), setPdfData(), getByteSize(), $filename, $destination
NextPDF\Event\Content\ContentRenderedEventclasse final$document, $pageIndex, $contentType, $content
NextPDF\Event\Content\FontLoadedEventclasse final$family, $style, $fontType, $filePath
NextPDF\Event\Security\EncryptionAppliedEventclasse final$document, $algorithm, $allowPrint, $allowCopy, $allowModify
NextPDF\Event\Security\SignatureAppliedEventclasse final$document, $signatureLevel, $signerName, $reason, $location
NextPDF\Event\Writer\PdfSerializedEventclasse final$byteSize, $objectCount, $pageCount, $pdfVersion, $isLinearized, $isEncrypted

addListener() genera NextPDF\Exception\InvalidConfigException quando la stringa della classe di evento è vuota.

Registrare un listener e distribuire un evento del ciclo di vita.

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

La classe Document richiama internamente gli helper di distribuzione dopo il collegamento di un dispatcher tramite setEventDispatcher().

Collegare il dispatcher a un documento, usare la priorità dei listener e arrestare la propagazione da un listener di controllo.

<?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).
  • La registrazione con caratteri jolly usa la gerarchia delle classi. Un listener su AbstractEvent riceve ogni evento del ciclo di vita perché ogni evento lo estende. Circoscrivere i listener alle classi concrete quando si desidera un solo evento.
  • La priorità dei listener colloca prima quelle più alte. Le priorità uguali mantengono l’ordine di inserimento (ordinamento stabile).
  • stopPropagation() arresta solo il ciclo di distribuzione corrente. L’evento distribuito successivo avvia un nuovo ciclo.
  • La cache dei listener ordinati viene invalidata a ogni chiamata di addListener(), perché la registrazione di un nuovo genitore o di una nuova interfaccia può modificare la risoluzione per diverse classi di evento.
  • $document sugli eventi con ambito documento è tipizzato come object, non come la classe Document, per mantenere il modulo Event privo di una dipendenza rigida da Core.
  • DocumentOutputEvent::setPdfData() richiede una stringa non vuota. Sostituire il payload con una stringa vuota produce un documento non valido.
  • Gli helper di distribuzione su EventAwareDocumentTrait sono operazioni nulle finché non viene impostato un dispatcher; quindi le esecuzioni prive di listener non aggiungono alcun costo misurabile.

La distribuzione senza listener è O(1) per una classe di evento foglia: un controllo booleano hasListeners(), seguito da un ritorno immediato. Con i listener, getListenersForEvent() percorre una volta l’ascendenza dell’evento, ordina le voci raccolte e memorizza nella cache l’elenco ordinato per ciascuna classe di evento fino alla mutazione successiva. La distribuzione ripetuta della stessa classe è quindi O(k) sui k listener corrispondenti. Il performance_budget predefinito per questa pagina di riferimento è wall_ms: 1500, peak_mb: 64.

I listener vengono eseguiti all’interno della pipeline di generazione con gli stessi privilegi del chiamante. Trattare il codice dei listener come codice attendibile. DocumentOutputEvent espone il binario finale del PDF e consente a un listener di sostituirlo. Un listener di audit o di integrità deve essere eseguito prima di qualsiasi listener che trasformi i byte (usare una priorità più alta). Gli eventi con ambito di sicurezza (EncryptionAppliedEvent, SignatureAppliedEvent) riportano i parametri applicati per la registrazione di audit. Non modificano l’esito crittografico.

SpecificaClausolaArgomento
PSR-14 (PHP-FIG)psr_14_event#x4L’evento interrompibile arresta gli ulteriori listener

La firma di dispatch(), la separazione tra listener e provider e il modello di evento interrompibile seguono PSR-14. NextPDF dichiara i propri EventInterface e StoppableEventInterface. Il pacchetto non ha alcuna dipendenza runtime da PSR-14, pur restando compatibile tramite duck typing.

  • /modules/core/contracts/ — superficie delle interfacce pubbliche
  • /modules/core/observability/ — hook per telemetria e metriche
  • /modules/core/audit/ — integrazione del trail di audit
  • /modules/core/config/Config passato su DocumentCreatedEvent
  • /modules/core/exception/InvalidConfigException da addListener()

Glossario: PSR-14 · evento interrompibile · listener provider