Aller au contenu

Déclencheurs d'action et écouteurs d'événements

NextPDF émet des événements de cycle de vie via un système compatible PSR-14, exposé dans NextPDF\Event. Enregistre des écouteurs avec une priorité pour réagir à la création d’un document, à l’ajout d’une page, au chargement d’une police, à la signature, au chiffrement, à l’écriture sur disque ou à la sortie. Interromps la chaîne quand tu en as besoin.

Fenêtre de terminal
composer require nextpdf/core:^3

Le système d’événements expose deux composants publics. ListenerProvider associe une classe d’événement à une liste triée d’écouteurs appelables. EventDispatcher parcourt cette liste et appelle chaque écouteur par ordre de priorité. Les deux classes étant final, compose avec elles plutôt que d’en hériter.

Les deux classes s’alignent sur PSR-14 par duck typing. EventDispatcher::dispatch() correspond à la signature PSR-14 dispatch() et renvoie l’événement une fois tous les écouteurs exécutés. ListenerProvider::getListenersForEvent() correspond à la signature du provider PSR-14. NextPDF n’a pas besoin du paquet PSR-14, mais si ton projet l’installe, les interfaces restent alignées.

Deux comportements sont importants pour les auteurs d’extensions :

  • Écoute par joker. Pour résoudre les écouteurs, le provider parcourt les classes parentes de l’événement ainsi que ses interfaces. Associe un écouteur à la classe de base AbstractEvent pour surveiller chaque événement du cycle de vie. Associe-le à une interface pour capter toute une famille.
  • Priorité et propagation. La priorité la plus élevée s’exécute en premier. Les priorités égales conservent l’ordre d’enregistrement. Tout événement qui étend AbstractEvent peut être interrompu. Un écouteur peut appeler stopPropagation(). Le répartiteur ignore alors les écouteurs restants.

Le répartiteur dispose d’un chemin rapide à coût nul. Quand aucun écouteur n’est lié à une classe d’événement ou à l’un de ses parents, dispatch() renvoie aussitôt après une seule vérification hasListeners().

ÉvénementEspace de nomsDéclenché quandStabilité
DocumentCreatedEventNextPDF\Event\DocumentLa construction d’un document est terminéeexperimental
PageAddedEventNextPDF\Event\DocumentUne page est entièrement initialiséeexperimental
ContentRenderedEventNextPDF\Event\ContentDu contenu est rendu sur une pageexperimental
FontLoadedEventNextPDF\Event\ContentUne famille et un style de police sont chargés pour la première foisexperimental
SignatureAppliedEventNextPDF\Event\SecurityLes octets de signature sont intégrésexperimental
EncryptionAppliedEventNextPDF\Event\SecurityLe chiffrement est configuréexperimental
PdfSerializedEventNextPDF\Event\WriterLa sérialisation est terminéeexperimental
DocumentOutputEventNextPDF\Event\DocumentAvant l’envoi de la sortieexperimental

Le répartiteur, le provider, l’interface de marquage et la classe de base sont stable (depuis la 3.0.0). Les charges utiles des événements sont experimental. Leurs arguments de constructeur et leurs propriétés readonly peuvent changer dans une version mineure. Une version corrective n’ajoute que des éléments. Garde cela à l’esprit si tu relies ton code aux noms des propriétés de charge utile.

NextPDF\Event\ListenerProvider (stable, final) :

MéthodeRetourneRôle
addListener(string $eventClass, callable $listener, int $priority = 0)voidEnregistre un écouteur ; la priorité la plus élevée s’exécute en premier. Lève InvalidConfigException si la classe est vide.
getListenersForEvent(EventInterface $event)list<callable>Résout les écouteurs, y compris les enregistrements de parents et d’interfaces.
hasListeners(string $eventClass)boolVérifie sans surcoût dans la hiérarchie de classes.
getListenerCount(string $eventClass)intCompte uniquement les enregistrements directs.
clearListeners()voidRéinitialise le provider.

NextPDF\Event\EventDispatcher (stable, final) :

MéthodeRetourneRôle
dispatch(EventInterface $event)EventInterfaceAppelle les écouteurs par ordre de priorité ; respecte l’arrêt de propagation ; renvoie l’événement.
getListenerProvider()ListenerProviderDonne accès au provider pour ajouter des écouteurs à l’exécution.

Les documents qui émettent des événements utilisent NextPDF\Event\EventAwareDocumentTrait. Sa méthode setEventDispatcher() raccorde un répartiteur à un document. Sans répartiteur, chaque assistant de dispatch reste inactif.

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

Voici un écouteur d’audit de production. Il utilise une priorité élevée pour s’exécuter en premier, journalise les données sous forme structurée et ajoute un écouteur fourre-tout sur la classe de base par souci d’exhaustivité.

<?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);
}
}
  • Classes finales. EventDispatcher et ListenerProvider sont final. Utilise la composition, ne les sous-classe pas.
  • Une classe d’événement vide lève une exception. addListener('', ...) lève InvalidConfigException. Passe toujours une constante class-string.
  • Coût du joker. Un écouteur sur AbstractEvent se déclenche pour chaque événement. Donne aux écouteurs fourre-tout une priorité basse. Garde-les légers.
  • Mutation de la sortie. DocumentOutputEvent transporte les octets du PDF. Le moteur les relit après le dispatch. Modifier ces octets donne un contrôle étendu et comporte un risque réel. Un mauvais décalage d’octet corrompt le PDF et peut casser une signature. Privilégie l’observation, sauf si tu assumes la responsabilité du résultat pour le déterminisme et les signatures.
  • Pas de répartiteur, pas d’événements. Un document sans répartiteur défini via EventAwareDocumentTrait n’émet aucun événement. C’est le chemin à coût nul prévu. Ce n’est pas une erreur de configuration.

Le chemin rapide se limite à une seule vérification hasListeners() sur la chaîne de parents. Sans écouteur, le dispatch est quasiment gratuit. Le provider met en cache la liste triée des écouteurs par classe d’événement. Il ne vide ce cache que lorsqu’il y a un changement. Garde les écouteurs non bloquants. Ils s’exécutent en ligne sur le chemin de rendu.

SignatureAppliedEvent et EncryptionAppliedEvent sont les points d’ancrage de l’audit. Enregistre des écouteurs de priorité élevée pour journaliser la signature et le chiffrement dans un magasin inviolable. N’interromps pas la chaîne sur un événement de sécurité, sauf si tu veux faire taire les écouteurs ultérieurs. L’interrompre peut désactiver silencieusement des hooks d’audit qui s’exécutent après.

Aucune affirmation normative n’est faite sur cette page au-delà de la compatibilité PSR-14, qui repose uniquement sur le duck typing et ne nécessite pas le paquet PSR-14.

NextPDF Enterprise fournit des écouteurs audités pour les événements de signature et de chiffrement qui alimentent un journal d’audit inviolable. Comme le contrat des écouteurs est l’API publique des événements, tes propres écouteurs coexistent avec ceux d’Enterprise sur le même provider.

Le glossaire définit les termes écouteur d’événement, répartiteur d’événements, fournisseur d’écouteurs et événement interruptible utilisés sur cette page. Consulte le glossaire publié pour chaque définition canonique.