Event: taxonomia de eventos do ciclo de vida do PSR-14
Visão geral
Seção intitulada “Visão geral”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.
Instalação
Seção intitulada “Instalação”composer require nextpdf/core:^3O 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.
Visão conceitual
Seção intitulada “Visão conceitual”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:
| Evento | Namespace | Disparado |
|---|---|---|
DocumentCreatedEvent | Event\Document | Depois que um documento é totalmente construído |
PageAddedEvent | Event\Document | Depois que uma página é inicializada |
ContentRenderedEvent | Event\Content | Depois que conteúdo HTML ou texto é renderizado em uma página |
FontLoadedEvent | Event\Content | Quando uma fonte é analisada para o registro |
EncryptionAppliedEvent | Event\Security | Depois que os parâmetros de criptografia são configurados |
SignatureAppliedEvent | Event\Security | Depois que uma assinatura é incorporada |
PdfSerializedEvent | Event\Writer | Depois da serialização, antes da entrega da saída |
DocumentOutputEvent | Event\Document | Antes de os bytes do PDF chegarem ao destino |
Superfície de API
Seção intitulada “Superfície de API”| Símbolo | Tipo | Membros principais |
|---|---|---|
NextPDF\Event\EventInterface | interface | getEventName(): non-empty-string |
NextPDF\Event\StoppableEventInterface | interface | isPropagationStopped(): bool |
NextPDF\Event\StoppableEventTrait | trait | isPropagationStopped(), stopPropagation() |
NextPDF\Event\AbstractEvent | classe abstrata | getEventName(); implementa StoppableEventInterface |
NextPDF\Event\EventDispatcher | classe final | dispatch(EventInterface): EventInterface, getListenerProvider() |
NextPDF\Event\ListenerProvider | classe final | addListener(), getListenersForEvent(), hasListeners(), getListenerCount(), clearListeners() |
NextPDF\Event\EventAwareDocumentTrait | trait | setEventDispatcher(), getEventDispatcher() |
NextPDF\Event\Document\DocumentCreatedEvent | classe final | $document, $config |
NextPDF\Event\Document\PageAddedEvent | classe final | $document, $pageIndex, $pageSize, $orientation |
NextPDF\Event\Document\DocumentOutputEvent | classe final | getPdfData(), setPdfData(), getByteSize(), $filename, $destination |
NextPDF\Event\Content\ContentRenderedEvent | classe final | $document, $pageIndex, $contentType, $content |
NextPDF\Event\Content\FontLoadedEvent | classe final | $family, $style, $fontType, $filePath |
NextPDF\Event\Security\EncryptionAppliedEvent | classe final | $document, $algorithm, $allowPrint, $allowCopy, $allowModify |
NextPDF\Event\Security\SignatureAppliedEvent | classe final | $document, $signatureLevel, $signerName, $reason, $location |
NextPDF\Event\Writer\PdfSerializedEvent | classe final | $byteSize, $objectCount, $pageCount, $pdfVersion, $isLinearized, $isEncrypted |
addListener() lança NextPDF\Exception\InvalidConfigException quando a string da classe do evento está vazia.
Exemplo de código — Início rápido
Seção intitulada “Exemplo de código — Início rápido”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().
Exemplo de código — Produção
Seção intitulada “Exemplo de código — Produção”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).Casos limite e pegadinhas
Seção intitulada “Casos limite e pegadinhas”- O registro com curinga usa a hierarquia de classes. Um listener em
AbstractEventrecebe 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. $documentnos eventos com escopo de documento é tipado comoobject, e não como a classeDocument, para manter o módulo Event livre de uma dependência rígida deCore.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
EventAwareDocumentTraitsão no-ops até que um dispatcher seja definido, portanto execuções sem listeners não adicionam custo mensurável.
Desempenho
Seção intitulada “Desempenho”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.
Notas de segurança
Seção intitulada “Notas de segurança”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.
Conformidade
Seção intitulada “Conformidade”| Especificação | Cláusula | Tópico |
|---|---|---|
| PSR-14 (PHP-FIG) | psr_14_event#x4 | O 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.
Consulte também
Seção intitulada “Consulte também”/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/—Configpassado emDocumentCreatedEvent/modules/core/exception/—InvalidConfigExceptiondeaddListener()
Glossário: PSR-14 · evento interrompível · provedor de listeners