Pular para o conteúdo

Event: taxonomia de eventos do ciclo de vida do PSR-14

O módulo Event despacha eventos tipados do ciclo de vida em cada etapa da geração de PDF. Os listeners podem observar ou transformar um documento sem alterar os componentes internos do engine. O dispatcher segue a PHP Standards Recommendation 14 (PSR-14), portanto as ferramentas PSR-14 existentes continuam compatíveis.

Terminal window
composer require nextpdf/core:^3

O módulo Event vem junto com o pacote core. Ele não tem dependências adicionais: os tipos EventInterface e StoppableEventInterface espelham os contratos do PSR-14 sem exigir o pacote psr/event-dispatcher.

O módulo tem três partes: um dispatcher, um provedor de listeners e um conjunto fixo de classes de evento do ciclo de vida.

EventDispatcher recebe uma instância de EventInterface, solicita ao ListenerProvider os listeners correspondentes e chama cada um na ordem de prioridade. O método dispatch() retorna o mesmo objeto de evento. Um listener pode ler o estado que o engine colocou no evento e gravar de volta o estado que o engine lerá em seguida. Isso corresponde ao modelo de dispatcher do PSR-14.

ListenerProvider mapeia uma classe de evento para uma lista de callables na ordem de prioridade. O registro tem escopo de instância, sem estado estático, portanto um processo worker pode manter suas próprias instâncias de provedor. O provedor também percorre a árvore de classes do evento e suas interfaces. Um listener em AbstractEvent vê todos os eventos do ciclo de vida. Um listener em uma interface vê todos os eventos que a implementam.

StoppableEventInterface adiciona controle de propagação. Um listener pode chamar stopPropagation(), e o dispatcher então deixa de chamar os listeners seguintes naquele ciclo. Um evento interrompível é um evento especial que traz seu próprio mecanismo para interromper a cadeia de listeners. O PSR-14 usa o mesmo modelo para eventos interrompíveis (PSR-14 psr_14_event#x4). AbstractEvent implementa a interface por meio de StoppableEventTrait, portanto todo evento do ciclo de vida é interrompível por padrão.

O dispatcher tem um caminho rápido sem sobrecarga. Ele verifica se há algum listener na classe do evento ou em qualquer ancestral. Quando não há nenhum, hasListeners() retorna false, e dispatch() retorna imediatamente. Um documento sem listeners paga apenas uma verificação booleana por ponto do ciclo de vida.

EventAwareDocumentTrait é o ponto de integração. Ele mantém um EventDispatcher opcional e expõe helpers de dispatch protegidos. A classe Document chama esses helpers em cada ponto do ciclo de vida. Quando nenhum dispatcher está definido, cada helper é um no-op.

A taxonomia do ciclo de vida inclui estes eventos:

EventoNamespaceDisparado
DocumentCreatedEventEvent\DocumentDepois que um documento é totalmente construído
PageAddedEventEvent\DocumentDepois que uma página é inicializada
ContentRenderedEventEvent\ContentDepois que conteúdo HTML ou texto é renderizado em uma página
FontLoadedEventEvent\ContentQuando uma fonte é analisada para o registro
EncryptionAppliedEventEvent\SecurityDepois que os parâmetros de criptografia são configurados
SignatureAppliedEventEvent\SecurityDepois que uma assinatura é incorporada
PdfSerializedEventEvent\WriterDepois da serialização, antes da entrega da saída
DocumentOutputEventEvent\DocumentAntes de os bytes do PDF chegarem ao destino
SímboloTipoMembros principais
NextPDF\Event\EventInterfaceinterfacegetEventName(): non-empty-string
NextPDF\Event\StoppableEventInterfaceinterfaceisPropagationStopped(): bool
NextPDF\Event\StoppableEventTraittraitisPropagationStopped(), stopPropagation()
NextPDF\Event\AbstractEventclasse abstratagetEventName(); 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() lança NextPDF\Exception\InvalidConfigException quando a string da classe do evento está vazia.

Registre um listener e, em seguida, despache um evento do 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);

A classe Document chama os helpers de dispatch internamente depois que um dispatcher é anexado por meio de setEventDispatcher().

Conecte o dispatcher a um documento, defina a prioridade dos listeners e interrompa a propagação a partir de um listener de gate.

<?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).
  • O registro com curinga usa a hierarquia de classes. Um listener em AbstractEvent recebe todos os eventos do ciclo de vida porque cada evento o estende. Limite os listeners a classes concretas quando você quiser apenas um evento.
  • A prioridade dos listeners ordena primeiro os de prioridade mais alta. Prioridades iguais mantêm a ordem de inserção (ordenação estável).
  • stopPropagation() interrompe apenas o ciclo de dispatch atual. O próximo evento despachado inicia um novo ciclo.
  • O cache de listeners ordenados é invalidado em qualquer chamada de addListener() porque um novo registro de classe pai ou de interface pode mudar a resolução de várias classes de evento.
  • $document nos eventos com escopo de documento é tipado como object, e não como a classe Document, para manter o módulo Event livre de uma dependência rígida de Core.
  • DocumentOutputEvent::setPdfData() espera uma string não vazia. Substituir o payload por uma string vazia produz um documento inválido.
  • Os helpers de dispatch em EventAwareDocumentTrait são no-ops até que um dispatcher seja definido, portanto execuções sem listeners não adicionam custo mensurável.

O dispatch sem listeners é O(1) para uma classe de evento folha: uma verificação booleana de hasListeners() e, em seguida, um retorno imediato. Com listeners, getListenersForEvent() percorre a ancestralidade do evento uma vez, ordena as entradas coletadas e armazena em cache a lista ordenada por classe de evento até a próxima mutação. Um dispatch repetido da mesma classe é, portanto, O(k) sobre k listeners correspondentes. O performance_budget padrão para esta página de referência é wall_ms: 1500, peak_mb: 64.

Os listeners são executados dentro do pipeline de geração com os mesmos privilégios do chamador. Trate o código dos listeners como código confiável. DocumentOutputEvent expõe o binário final do PDF e permite que um listener o substitua. Um listener de auditoria ou de integridade deve ser executado antes de qualquer listener que transforme os bytes. Use uma prioridade mais alta. Os eventos com escopo de segurança (EncryptionAppliedEvent, SignatureAppliedEvent) relatam os parâmetros aplicados para o registro de auditoria. Eles não alteram o resultado criptográfico.

EspecificaçãoCláusulaTópico
PSR-14 (PHP-FIG)psr_14_event#x4O evento interrompível interrompe os listeners seguintes

A assinatura de dispatch(), a separação entre listener e provedor e o modelo de evento interrompível seguem o PSR-14. O NextPDF declara seus próprios EventInterface e StoppableEventInterface. O pacote não tem dependência de runtime do PSR-14 e permanece compatível por duck typing.

  • /modules/core/contracts/ — superfície pública de interfaces
  • /modules/core/observability/ — hooks de telemetria e métricas
  • /modules/core/audit/ — integração com trilha de auditoria
  • /modules/core/config/Config passado em DocumentCreatedEvent
  • /modules/core/exception/InvalidConfigException de addListener()

Glossário: PSR-14 · evento interrompível · provedor de listeners