Ir al contenido

Disparadores de acciones y escuchas de eventos

NextPDF emite eventos de ciclo de vida mediante un sistema compatible con PSR-14 en NextPDF\Event. Permite registrar escuchas con prioridad y reaccionar a la creación de un documento, a una página nueva, a la carga de una fuente, a la firma, al cifrado, a la escritura o a la salida. La cadena puede detenerse cuando sea necesario.

Ventana de terminal
composer require nextpdf/core:^3

El sistema de eventos expone dos partes públicas. ListenerProvider asocia una clase de evento a una lista ordenada de callables de escucha. EventDispatcher recorre esa lista y llama a cada escucha en orden de prioridad. Ambas clases son final, por lo que deben ampliarse mediante composición en lugar de herencia.

Ambas clases cumplen PSR-14 mediante duck typing. EventDispatcher::dispatch() respeta la firma dispatch() de PSR-14 y devuelve el evento después de ejecutar las escuchas aplicables. ListenerProvider::getListenersForEvent() respeta la firma del provider de PSR-14. NextPDF no requiere el paquete PSR-14, pero, si tu proyecto lo incluye, las interfaces siguen siendo compatibles.

Dos comportamientos son especialmente relevantes para los autores de extensiones:

  • Escucha con comodín. Al resolver las escuchas, el provider recorre las clases padre del evento y sus interfaces. Vincular una escucha a la clase base AbstractEvent permite observar todos los eventos de ciclo de vida. Vincularla a una interfaz permite capturar toda una familia.
  • Prioridad y propagación. La prioridad más alta se ejecuta primero. Con prioridades iguales, se conserva el orden de registro. Todo evento que extiende AbstractEvent es detenible. Una escucha puede llamar a stopPropagation(). Entonces, el dispatcher omite las restantes.

El dispatcher tiene una ruta rápida sin costo. Cuando no hay ninguna escucha vinculada a una clase de evento ni a ningún padre, dispatch() retorna de inmediato tras una sola comprobación de hasListeners().

EventoEspacio de nombresSe emite cuandoEstabilidad
DocumentCreatedEventNextPDF\Event\DocumentFinaliza la construcción de un documentoexperimental
PageAddedEventNextPDF\Event\DocumentUna página queda completamente inicializadaexperimental
ContentRenderedEventNextPDF\Event\ContentEl contenido se renderiza en una páginaexperimental
FontLoadedEventNextPDF\Event\ContentUna familia y un estilo de fuente se cargan por primera vezexperimental
SignatureAppliedEventNextPDF\Event\SecurityLos bytes de la firma quedan incrustadosexperimental
EncryptionAppliedEventNextPDF\Event\SecurityEl cifrado queda configuradoexperimental
PdfSerializedEventNextPDF\Event\WriterLa serialización se completaexperimental
DocumentOutputEventNextPDF\Event\DocumentAntes de entregar la salidaexperimental

El dispatcher, el provider, la interfaz marcadora y la clase base son stable (desde la 3.0.0). Los payloads de los eventos son experimental. Sus argumentos de constructor y sus propiedades de solo lectura pueden cambiar en una versión menor. Un parche solo añade elementos. Conviene vincularse a los nombres de las propiedades del payload teniendo esto en cuenta.

NextPDF\Event\ListenerProvider (stable, final):

MétodoDevuelvePropósito
addListener(string $eventClass, callable $listener, int $priority = 0)voidRegistra una escucha; la prioridad más alta se ejecuta primero. Lanza InvalidConfigException ante una clase vacía.
getListenersForEvent(EventInterface $event)list<callable>Resuelve las escuchas, incluidos los registros de las clases padre y de las interfaces.
hasListeners(string $eventClass)boolComprobación sin sobrecarga a lo largo de la jerarquía de clases.
getListenerCount(string $eventClass)intCuenta solo los registros directos.
clearListeners()voidRestablece el provider.

NextPDF\Event\EventDispatcher (stable, final):

MétodoDevuelvePropósito
dispatch(EventInterface $event)EventInterfaceInvoca las escuchas en orden de prioridad; respeta la detención de la propagación; devuelve el evento.
getListenerProvider()ListenerProviderPermite acceder al provider para añadir escuchas en tiempo de ejecución.

Los documentos que emiten eventos usan NextPDF\Event\EventAwareDocumentTrait. Su método setEventDispatcher() conecta un dispatcher a un documento. Sin un dispatcher, los ayudantes de despacho no realizan ninguna acción.

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

Esta es una escucha de auditoría de producción. Usa una prioridad alta para ejecutarse primero, registra en formato estructurado y añade una escucha general en la clase base para lograr una trazabilidad más completa.

<?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);
}
}
  • Clases finales. EventDispatcher y ListenerProvider son final. Se debe componer en lugar de derivar subclases.
  • Una clase de evento vacía lanza una excepción. addListener('', ...) lanza InvalidConfigException. Se debe pasar siempre una constante de tipo class-string.
  • Costo del comodín. Una escucha en AbstractEvent se dispara para cada evento. Conviene asignar prioridad baja a las escuchas generales y mantenerlas ligeras.
  • Mutación de la salida. DocumentOutputEvent transporta los bytes del PDF. El motor los vuelve a leer después del despacho. Modificar esos bytes otorga un control amplio y conlleva un riesgo real. Un desplazamiento de bytes incorrecto corrompe el PDF y puede romper una firma. Es preferible observar, salvo que se asuma la responsabilidad del resultado para el determinismo y las firmas.
  • Sin dispatcher, no hay eventos. Un documento sin un dispatcher configurado mediante EventAwareDocumentTrait no emite eventos. Esta es la ruta sin costo prevista. No es un error de configuración.

La ruta rápida consiste en una única comprobación de la cadena de clases padre con hasListeners(). Sin escuchas, el despacho es prácticamente gratuito. El provider almacena en caché la lista ordenada de escuchas por clase de evento. Solo invalida esa caché cuando hay un cambio. Mantener las escuchas sin bloqueo. Se ejecutan en línea en la ruta de renderizado.

SignatureAppliedEvent y EncryptionAppliedEvent son los anclajes de auditoría. Registrar escuchas de prioridad alta permite dejar constancia de la firma y el cifrado en un almacén a prueba de manipulaciones. La cadena no debe detenerse en un evento de seguridad, a menos que la intención sea silenciar las escuchas posteriores. Detenerla puede desactivar de forma silenciosa los ganchos de auditoría que se ejecutan después.

En esta página no se hace ninguna afirmación normativa más allá de la compatibilidad con PSR-14, que se basa únicamente en duck typing y no requiere el paquete PSR-14.

NextPDF Enterprise incluye escuchas auditadas para los eventos de firma y de cifrado que alimentan un registro de auditoría a prueba de manipulaciones. El contrato de las escuchas es la API pública de eventos, por lo que tus propias escuchas conviven con las de Enterprise en el mismo provider.

El glosario define los términos event listener, event dispatcher, listener provider y stoppable event que se usan en esta página. Consulta el glosario publicado para ver cada definición canónica.