Event : taxonomie PSR-14 des événements du cycle de vie
Le module Event répartit des événements typés du cycle de vie à chaque étape de la génération du PDF. Les écouteurs observent ou transforment un document sans toucher aux rouages internes du moteur. Comme le répartiteur suit le modèle PSR-14, l’outillage PSR-14 existant reste compatible.
Installation
Section intitulée « Installation »composer require nextpdf/core:^3Le module Event est livré avec le package core. Il n’a besoin d’aucune dépendance supplémentaire : les types EventInterface et StoppableEventInterface reproduisent les contrats PSR-14 sans exiger le package psr/event-dispatcher.
Vue d’ensemble conceptuelle
Section intitulée « Vue d’ensemble conceptuelle »Le module s’articule autour de trois éléments : un répartiteur, un fournisseur d’écouteurs et un ensemble fixe de classes d’événements du cycle de vie.
EventDispatcher reçoit une instance de EventInterface. Il demande au ListenerProvider les écouteurs correspondants, puis les appelle chacun dans l’ordre de priorité. La méthode dispatch() renvoie le même objet événement. Ainsi, un écouteur peut lire l’état que le moteur a placé dans l’événement. Il peut aussi y écrire un nouvel état que le moteur relira plus tard. C’est le schéma du répartiteur PSR-14.
ListenerProvider associe une classe d’événement à une liste de callables dans l’ordre de priorité. Les enregistrements restent limités à l’instance. Il n’y a aucun état statique. Un processus worker peut donc conserver ses propres instances de fournisseur. Le fournisseur parcourt aussi la hiérarchie de classes de l’événement ainsi que ses interfaces. Ainsi, un écouteur sur AbstractEvent voit chaque événement du cycle de vie. Un écouteur sur une interface voit tout événement qui l’implémente.
StoppableEventInterface ajoute le contrôle de propagation. Un écouteur peut appeler stopPropagation(). Le répartiteur cesse alors d’appeler les écouteurs suivants pour ce cycle. Un événement arrêtable est un cas particulier : il porte sa propre logique d’interruption de la chaîne d’écouteurs. PSR-14 décrit le même modèle pour les événements arrêtables (PSR-14 psr_14_event#x4). AbstractEvent implémente l’interface via StoppableEventTrait. Chaque événement du cycle de vie est donc arrêtable par défaut.
Le répartiteur dispose d’un chemin rapide sans surcoût inutile. Il vérifie la présence d’un écouteur sur la classe de l’événement ou sur l’un de ses ancêtres. Lorsqu’il n’y en a aucun, hasListeners() renvoie false et dispatch() retourne aussitôt. Un document sans écouteur n’encourt qu’une seule vérification booléenne par point du cycle de vie.
EventAwareDocumentTrait est le point d’intégration. Il détient un EventDispatcher facultatif. Il expose des assistants de répartition protégés. La classe Document appelle ces assistants à chaque point du cycle de vie. Quand aucun répartiteur n’est défini, chaque assistant est une opération sans effet.
La taxonomie des événements du cycle de vie :
| Événement | Espace de noms | Déclenchement |
|---|---|---|
DocumentCreatedEvent | Event\Document | Après la construction complète d’un document |
PageAddedEvent | Event\Document | Après l’initialisation d’une page |
ContentRenderedEvent | Event\Content | Après le rendu d’un contenu HTML ou texte sur une page |
FontLoadedEvent | Event\Content | Lorsqu’une police est analysée et ajoutée au registre |
EncryptionAppliedEvent | Event\Security | Après la configuration des paramètres de chiffrement |
SignatureAppliedEvent | Event\Security | Après l’intégration d’une signature |
PdfSerializedEvent | Event\Writer | Après la sérialisation, avant la livraison de la sortie |
DocumentOutputEvent | Event\Document | Avant que les octets du PDF n’atteignent leur destination |
Surface d’API
Section intitulée « Surface d’API »| Symbole | Type | Membres clés |
|---|---|---|
NextPDF\Event\EventInterface | interface | getEventName(): non-empty-string |
NextPDF\Event\StoppableEventInterface | interface | isPropagationStopped(): bool |
NextPDF\Event\StoppableEventTrait | trait | isPropagationStopped(), stopPropagation() |
NextPDF\Event\AbstractEvent | classe abstraite | getEventName() ; implémente StoppableEventInterface |
NextPDF\Event\EventDispatcher | classe finale | dispatch(EventInterface): EventInterface, getListenerProvider() |
NextPDF\Event\ListenerProvider | classe finale | addListener(), getListenersForEvent(), hasListeners(), getListenerCount(), clearListeners() |
NextPDF\Event\EventAwareDocumentTrait | trait | setEventDispatcher(), getEventDispatcher() |
NextPDF\Event\Document\DocumentCreatedEvent | classe finale | $document, $config |
NextPDF\Event\Document\PageAddedEvent | classe finale | $document, $pageIndex, $pageSize, $orientation |
NextPDF\Event\Document\DocumentOutputEvent | classe finale | getPdfData(), setPdfData(), getByteSize(), $filename, $destination |
NextPDF\Event\Content\ContentRenderedEvent | classe finale | $document, $pageIndex, $contentType, $content |
NextPDF\Event\Content\FontLoadedEvent | classe finale | $family, $style, $fontType, $filePath |
NextPDF\Event\Security\EncryptionAppliedEvent | classe finale | $document, $algorithm, $allowPrint, $allowCopy, $allowModify |
NextPDF\Event\Security\SignatureAppliedEvent | classe finale | $document, $signatureLevel, $signerName, $reason, $location |
NextPDF\Event\Writer\PdfSerializedEvent | classe finale | $byteSize, $objectCount, $pageCount, $pdfVersion, $isLinearized, $isEncrypted |
addListener() lève NextPDF\Exception\InvalidConfigException lorsque la chaîne de classe d’événement est vide.
Exemple de code — Démarrage rapide
Section intitulée « Exemple de code — Démarrage rapide »Enregistre un écouteur, puis répartis un événement du cycle de vie.
<?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 appelle les assistants de répartition en interne dès qu’un répartiteur lui est attaché avec setEventDispatcher().
Exemple de code — Production
Section intitulée « Exemple de code — Production »Raccorde le répartiteur à un document, utilise la priorité des écouteurs et arrête la propagation au niveau d’un écouteur de type 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).Cas limites et pièges
Section intitulée « Cas limites et pièges »- L’enregistrement avec joker s’appuie sur la hiérarchie de classes. Un écouteur sur
AbstractEventreçoit chaque événement du cycle de vie, car chaque événement en hérite. Limite les écouteurs aux classes concrètes quand tu ne veux écouter qu’un seul événement. - La priorité des écouteurs trie du plus élevé au plus faible. Les priorités égales conservent l’ordre d’insertion (tri stable).
stopPropagation()interrompt uniquement le cycle de répartition courant. L’événement réparti suivant démarre un nouveau cycle.- Le cache des écouteurs triés est invalidé à chaque appel de
addListener(), car un nouvel enregistrement sur un parent ou une interface peut modifier la résolution pour plusieurs classes d’événements. $documentdans les événements limités au document est typéobject, et non la classeDocument, afin de garder le module Event libre de toute dépendance forte enversCore.DocumentOutputEvent::setPdfData()attend une chaîne non vide. Remplacer la charge utile par une chaîne vide produit un document invalide.- Les assistants de répartition sur
EventAwareDocumentTraitsont des opérations sans effet tant qu’aucun répartiteur n’est défini ; les exécutions sans écouteur n’ajoutent donc aucun coût mesurable.
Performance
Section intitulée « Performance »Sans écouteur, la répartition est en O(1) pour une classe d’événement feuille : une vérification booléenne hasListeners(), puis un retour immédiat. Avec des écouteurs, getListenersForEvent() parcourt une fois l’ascendance de l’événement, trie les entrées collectées et met en cache la liste triée par classe d’événement jusqu’à la prochaine mutation. La répartition répétée de la même classe est donc en O(k) pour les k écouteurs concernés. Le performance_budget par défaut de cette page de référence est wall_ms: 1500, peak_mb: 64.
Notes de sécurité
Section intitulée « Notes de sécurité »Les écouteurs s’exécutent dans le pipeline de génération avec les mêmes privilèges que l’appelant. Considère le code des écouteurs comme du code de confiance. DocumentOutputEvent expose le binaire PDF final et permet à un écouteur de le remplacer. Un écouteur d’audit ou d’intégrité doit s’exécuter avant tout écouteur qui transforme les octets (utilise une priorité plus élevée). Les événements limités à la sécurité (EncryptionAppliedEvent, SignatureAppliedEvent) rapportent les paramètres appliqués pour la journalisation d’audit. Ils ne modifient pas le résultat cryptographique.
Conformité
Section intitulée « Conformité »| Spécification | Clause | Thème |
|---|---|---|
| PSR-14 (PHP-FIG) | psr_14_event#x4 | L’événement arrêtable interrompt les écouteurs suivants |
La signature de dispatch(), la séparation fournisseur-écouteurs et le modèle d’événement arrêtable suivent PSR-14. NextPDF déclare ses propres EventInterface et StoppableEventInterface. Le package n’a aucune dépendance d’exécution PSR-14 tout en restant compatible grâce au duck typing.
Voir aussi
Section intitulée « Voir aussi »/modules/core/contracts/— surface des interfaces publiques/modules/core/observability/— hooks de télémétrie et de métriques/modules/core/audit/— intégration de la piste d’audit/modules/core/config/—Configtransmis surDocumentCreatedEvent/modules/core/exception/—InvalidConfigExceptionissu deaddListener()
Glossaire : PSR-14 · événement arrêtable · fournisseur d’écouteurs