Event: taxonomía de eventos del ciclo de vida PSR-14
En resumen
Sección titulada «En resumen»El módulo Event despacha eventos tipados del ciclo de vida en cada etapa de la generación del PDF. Los listeners observan o transforman un documento sin modificar las partes internas del motor. El dispatcher sigue el modelo PSR-14, por lo que las herramientas PSR-14 existentes siguen siendo compatibles.
Instalación
Sección titulada «Instalación»composer require nextpdf/core:^3El módulo Event está incluido en el paquete del núcleo. No tiene dependencias adicionales: los tipos EventInterface y StoppableEventInterface reflejan los contratos PSR-14 sin requerir el paquete psr/event-dispatcher.
Panorama conceptual
Sección titulada «Panorama conceptual»El módulo consta de tres partes: un dispatcher, un listener provider y un conjunto fijo de clases de eventos del ciclo de vida.
EventDispatcher recibe una instancia de EventInterface. Le solicita al ListenerProvider los listeners coincidentes. Luego llama a cada uno en orden de prioridad. El método dispatch() devuelve el mismo objeto de evento. De este modo, un listener puede leer el estado que el motor colocó en el evento. También puede escribir en él un estado que el motor lee más adelante. Así funciona el dispatcher PSR-14.
ListenerProvider asigna una clase de evento a una lista de callables en orden de prioridad. El registro está acotado a la instancia. No hay estado estático. Un proceso de trabajo puede tener sus propias instancias del provider. El provider también recorre la jerarquía de clases del evento y sus interfaces. Así, un listener sobre AbstractEvent ve cada evento del ciclo de vida. Un listener sobre una interfaz ve cada evento que la implementa.
StoppableEventInterface añade control de propagación. Un listener puede llamar a stopPropagation(). Entonces el dispatcher deja de llamar a más listeners en ese ciclo. Un evento detenible es un caso especial: incorpora su propio mecanismo para detener la cadena de listeners. PSR-14 describe el mismo modelo para eventos detenibles (PSR-14 psr_14_event#x4). AbstractEvent implementa la interfaz mediante StoppableEventTrait. Así, cada evento del ciclo de vida es detenible de forma predeterminada.
El dispatcher tiene una ruta rápida sin sobrecarga. Comprueba si hay algún listener registrado en la clase de evento o en cualquier ancestro. Cuando no hay ninguno, hasListeners() devuelve false y dispatch() retorna de inmediato. Un documento sin listeners solo paga una comprobación booleana por punto de ciclo de vida.
EventAwareDocumentTrait es el punto de integración. Contiene un EventDispatcher opcional. Expone helpers de despacho protegidos. La clase Document llama a estos helpers en cada punto del ciclo de vida. Cuando no hay ningún dispatcher configurado, cada helper se comporta como una operación nula.
La taxonomía de eventos de ciclo de vida:
| Evento | Namespace | Se dispara |
|---|---|---|
DocumentCreatedEvent | Event\Document | Después de construir por completo un documento |
PageAddedEvent | Event\Document | Después de inicializar una página |
ContentRenderedEvent | Event\Content | Después de renderizar contenido HTML o de texto en una página |
FontLoadedEvent | Event\Content | Cuando se analiza una fuente y se incorpora al registro |
EncryptionAppliedEvent | Event\Security | Después de configurar los parámetros de cifrado |
SignatureAppliedEvent | Event\Security | Después de incrustar una firma |
PdfSerializedEvent | Event\Writer | Después de la serialización, antes de la entrega de la salida |
DocumentOutputEvent | Event\Document | Antes de que los bytes del PDF lleguen al destino |
Superficie de la API
Sección titulada «Superficie de la API»| Símbolo | Tipo | Miembros clave |
|---|---|---|
NextPDF\Event\EventInterface | interface | getEventName(): non-empty-string |
NextPDF\Event\StoppableEventInterface | interface | isPropagationStopped(): bool |
NextPDF\Event\StoppableEventTrait | trait | isPropagationStopped(), stopPropagation() |
NextPDF\Event\AbstractEvent | clase abstracta | getEventName(); implements StoppableEventInterface |
NextPDF\Event\EventDispatcher | clase final | dispatch(EventInterface): EventInterface, getListenerProvider() |
NextPDF\Event\ListenerProvider | clase final | addListener(), getListenersForEvent(), hasListeners(), getListenerCount(), clearListeners() |
NextPDF\Event\EventAwareDocumentTrait | trait | setEventDispatcher(), getEventDispatcher() |
NextPDF\Event\Document\DocumentCreatedEvent | clase final | $document, $config |
NextPDF\Event\Document\PageAddedEvent | clase final | $document, $pageIndex, $pageSize, $orientation |
NextPDF\Event\Document\DocumentOutputEvent | clase final | getPdfData(), setPdfData(), getByteSize(), $filename, $destination |
NextPDF\Event\Content\ContentRenderedEvent | clase final | $document, $pageIndex, $contentType, $content |
NextPDF\Event\Content\FontLoadedEvent | clase final | $family, $style, $fontType, $filePath |
NextPDF\Event\Security\EncryptionAppliedEvent | clase final | $document, $algorithm, $allowPrint, $allowCopy, $allowModify |
NextPDF\Event\Security\SignatureAppliedEvent | clase final | $document, $signatureLevel, $signerName, $reason, $location |
NextPDF\Event\Writer\PdfSerializedEvent | clase final | $byteSize, $objectCount, $pageCount, $pdfVersion, $isLinearized, $isEncrypted |
addListener() lanza NextPDF\Exception\InvalidConfigException cuando la cadena de la clase de evento está vacía.
Ejemplo de código — Inicio rápido
Sección titulada «Ejemplo de código — Inicio rápido»Registrar un listener y despachar un evento del ciclo de vida.
<?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 clase Document llama internamente a los helpers de despacho una vez que se adjunta un dispatcher mediante setEventDispatcher().
Ejemplo de código — Producción
Sección titulada «Ejemplo de código — Producción»Conectar el dispatcher a un documento, usar la prioridad de los listeners y detener la propagación desde un listener de control.
<?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).Casos límite y trampas
Sección titulada «Casos límite y trampas»- El registro con comodín usa la jerarquía de clases. Un listener sobre
AbstractEventrecibe cada evento del ciclo de vida porque cada evento lo extiende. Acotar los listeners a clases concretas cuando se necesite un solo evento. - La prioridad de los listeners ordena de mayor a menor. Las prioridades iguales conservan el orden de inserción (ordenación estable).
stopPropagation()detiene únicamente el ciclo de despacho actual. El siguiente evento despachado inicia un ciclo nuevo.- La caché de listeners ordenados se invalida con cualquier llamada a
addListener(), porque un nuevo registro de un padre o de una interfaz puede cambiar la resolución de varias clases de eventos. $documenten los eventos con alcance de documento está tipado comoobject, no como la claseDocument, para mantener el módulo Event libre de una dependencia fuerte deCore.DocumentOutputEvent::setPdfData()espera una cadena no vacía. Reemplazar la carga útil por una cadena vacía produce un documento no válido.- Los helpers de despacho de
EventAwareDocumentTraitson operaciones nulas hasta que se configura un dispatcher, por lo que las ejecuciones sin listeners no añaden ningún costo medible.
Rendimiento
Sección titulada «Rendimiento»El despacho sin listeners es O(1) para una clase de evento hoja: una comprobación booleana con hasListeners() y, a continuación, un retorno inmediato. Con listeners, getListenersForEvent() recorre una vez la ascendencia del evento, ordena las entradas recopiladas y almacena en caché la lista ordenada por clase de evento hasta la siguiente mutación. Por lo tanto, el despacho repetido de la misma clase es O(k) sobre k listeners coincidentes. El performance_budget predeterminado para esta página de referencia es wall_ms: 1500, peak_mb: 64.
Notas de seguridad
Sección titulada «Notas de seguridad»Los listeners se ejecutan dentro de la canalización de generación con los mismos privilegios que el código llamador. El código de los listeners debe tratarse como código de confianza. DocumentOutputEvent expone el binario final del PDF y permite que un listener lo reemplace. Un listener de auditoría o de integridad debería ejecutarse antes que cualquier listener que transforme los bytes (usar una prioridad más alta). Los eventos con alcance de seguridad (EncryptionAppliedEvent, SignatureAppliedEvent) informan los parámetros aplicados para el registro de auditoría. No cambian el resultado criptográfico.
Conformidad
Sección titulada «Conformidad»| Especificación | Cláusula | Tema |
|---|---|---|
| PSR-14 (PHP-FIG) | psr_14_event#x4 | El evento detenible detiene los listeners posteriores |
La firma de dispatch(), la separación entre dispatcher y listener provider y el modelo de evento detenible siguen PSR-14. NextPDF declara su propia EventInterface y StoppableEventInterface. El paquete no tiene ninguna dependencia de PSR-14 en tiempo de ejecución y mantiene la compatibilidad por duck typing.
Véase también
Sección titulada «Véase también»/modules/core/contracts/— superficie de interfaces públicas/modules/core/observability/— hooks de telemetría y métricas/modules/core/audit/— integración del registro de auditoría/modules/core/config/—Configpasado enDocumentCreatedEvent/modules/core/exception/—InvalidConfigExceptionlanzado desdeaddListener()
Glosario: PSR-14 · evento detenible · listener provider