Action triggers and event listeners
At a glance
Section titled “At a glance”NextPDF fires lifecycle events through a system in NextPDF\Event that is compatible with PHP Standards Recommendation 14 (PSR-14). Register a listener, choose a priority, and react when a document is created, a page is added, a font loads, signing or encryption runs, bytes are written, or output begins. Stop the chain only when you need to.
Install
Section titled “Install”composer require nextpdf/core:^3Conceptual overview
Section titled “Conceptual overview”The event system has two public parts. ListenerProvider maps each event class to a sorted list of listener callables. EventDispatcher walks that list and calls each listener in priority order. Both classes are final, so extend the behavior with composition, not subclassing.
Both classes match PSR-14 by duck typing. EventDispatcher::dispatch() uses the PSR-14 dispatch() signature and returns the event after every listener runs. ListenerProvider::getListenersForEvent() uses the PSR-14 provider signature. NextPDF does not require the PSR-14 package. If your project installs it, the interfaces still line up.
Two behaviors matter for extension authors:
- Wildcard listening. To resolve listeners, the provider walks the event’s parent classes and interfaces. Bind a listener to the
AbstractEventbase class to watch every lifecycle event. Bind one to an interface to catch a family. - Priority and propagation. Higher priority runs first. Equal priorities keep registration order. Every event that extends
AbstractEventis stoppable. A listener can callstopPropagation(), and the dispatcher then skips the rest.
The dispatcher has a zero-cost fast path. When no listener is bound for an event class or any parent, dispatch() returns immediately after one hasListeners() check.
Lifecycle events
Section titled “Lifecycle events”| Event | Namespace | Fired when | Stability |
|---|---|---|---|
DocumentCreatedEvent | NextPDF\Event\Document | Document construction finishes | experimental |
PageAddedEvent | NextPDF\Event\Document | A page is fully initialized | experimental |
ContentRenderedEvent | NextPDF\Event\Content | Content is rendered to a page | experimental |
FontLoadedEvent | NextPDF\Event\Content | A font family and style load for the first time | experimental |
SignatureAppliedEvent | NextPDF\Event\Security | Signature bytes are embedded | experimental |
EncryptionAppliedEvent | NextPDF\Event\Security | Encryption is configured | experimental |
PdfSerializedEvent | NextPDF\Event\Writer | Serialization completes | experimental |
DocumentOutputEvent | NextPDF\Event\Document | Output delivery is about to begin | experimental |
The dispatcher, provider, marker interface, and base class are stable (since 3.0.0). The event payloads are experimental. Their constructor arguments and readonly properties may change in a minor release. Patch releases only add. Bind to payload property names with that constraint in mind.
API surface
Section titled “API surface”NextPDF\Event\ListenerProvider (stable, final):
| Method | Returns | Purpose |
|---|---|---|
addListener(string $eventClass, callable $listener, int $priority = 0) | void | Register a listener; higher priority runs first. Throws InvalidConfigException when the class is empty. |
getListenersForEvent(EventInterface $event) | list<callable> | Resolve listeners, including registrations on parents and interfaces. |
hasListeners(string $eventClass) | bool | Check the class hierarchy with zero overhead. |
getListenerCount(string $eventClass) | int | Count direct registrations only. |
clearListeners() | void | Reset the provider. |
NextPDF\Event\EventDispatcher (stable, final):
| Method | Returns | Purpose |
|---|---|---|
dispatch(EventInterface $event) | EventInterface | Invoke listeners in priority order, respect propagation stops, and return the event. |
getListenerProvider() | ListenerProvider | Access the provider to add listeners at runtime. |
Documents that fire events use NextPDF\Event\EventAwareDocumentTrait. Its setEventDispatcher() method wires a dispatcher into one document. With no dispatcher, every dispatch helper does nothing.
Code sample — Quick start
Section titled “Code sample — Quick start”<?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);Code sample — Production
Section titled “Code sample — Production”This production audit listener uses high priority so it runs first, writes structured logs, and adds a catch-all on the base class for completeness.
<?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); }}Edge cases & gotchas
Section titled “Edge cases & gotchas”- Final classes.
EventDispatcherandListenerProviderarefinal. Compose them; do not subclass them. - Empty event class throws.
addListener('', ...)throwsInvalidConfigException. Always pass a class-string constant. - Wildcard cost. A listener on
AbstractEventfires for every event. Give catch-all listeners low priority, and keep them cheap. - Output mutation.
DocumentOutputEventcarries Portable Document Format (PDF) bytes. The engine reads them back after dispatch. If you change those bytes, you get broad control and broad risk. A wrong byte offset corrupts the PDF and can break a signature. Prefer to observe unless you own the result for determinism and signatures. - No dispatcher, no events. A document with no dispatcher set through
EventAwareDocumentTraitfires no events. This is the intended zero-cost path, not a setup error.
Performance
Section titled “Performance”The fast path is one hasListeners() parent-chain check. With no listeners, dispatch is almost free. The provider caches the sorted listener list per event class and clears that cache only when listeners change. Keep listeners non-blocking because they run inline on the render path.
Security notes
Section titled “Security notes”SignatureAppliedEvent and EncryptionAppliedEvent are the audit anchors. Register high-priority listeners to log signing and encryption into a tamper-evident store. Do not stop the chain on a security event unless you intend to mute later listeners. Stopping it can quietly disable audit hooks that run afterward.
Conformance
Section titled “Conformance”This page makes no normative claims beyond PSR-14 compatibility. That compatibility is duck-type only and does not require the PSR-14 package.
Commercial context
Section titled “Commercial context”NextPDF Enterprise ships audited listeners for signature and encryption events that feed a tamper-evident audit log. Because the listener contract is the public event application programming interface (API), your own listeners can coexist with the Enterprise ones on the same provider.
See also
Section titled “See also”- Extension authoring overview
- Custom fonts
- Custom layout and text interception
- Key Management Service (KMS) provider contract
- Service Provider Interface (SPI) stability rules
Related contracts and modules
Section titled “Related contracts and modules”- Event module reference — the PSR-14 lifecycle event taxonomy and dispatcher internals.
- Signing contracts reference — the contracts behind
SignatureAppliedEvent. - SPI stability rules — how the stable dispatcher and experimental payloads are versioned.
- Custom fonts — pairs
FontLoadedEventwith the registry contract. - Extension authoring overview — the full public SPI surface.
The glossary defines the event listener, event dispatcher, listener provider, and stoppable event terms used on this page. See the published glossary for canonical definitions.