Przejdź do głównej zawartości

Event: taksonomia zdarzeń cyklu życia zgodna z PSR-14

Moduł Event emituje typowane zdarzenia cyklu życia na każdym etapie generowania pliku PDF. Listenery mogą obserwować lub modyfikować dokument bez ingerowania w wewnętrzne mechanizmy silnika. Dyspozytor jest zgodny z PHP Standards Recommendation 14 (PSR-14), dzięki czemu pozostaje kompatybilny z istniejącymi narzędziami PSR-14.

Okno terminala
composer require nextpdf/core:^3

Moduł Event jest dostarczany z pakietem core. Nie ma dodatkowych zależności: typy EventInterface i StoppableEventInterface odwzorowują kontrakty PSR-14 i nie wymagają pakietu psr/event-dispatcher.

Moduł składa się z trzech części: dyspozytora, dostawcy listenerów oraz ustalonego zestawu klas zdarzeń cyklu życia.

EventDispatcher przyjmuje instancję EventInterface, odpytuje ListenerProvider o pasujące listenery i wywołuje je w kolejności priorytetów. Metoda dispatch() zwraca ten sam obiekt zdarzenia. Listener może odczytać stan, który silnik umieścił w zdarzeniu, oraz zapisać stan, który silnik odczyta później. Odpowiada to modelowi dyspozytora PSR-14.

ListenerProvider mapuje klasę zdarzenia na listę obiektów wywoływalnych ułożonych według priorytetu. Rejestracja ma zakres instancji i nie używa stanu statycznego, dzięki czemu proces roboczy może utrzymywać własne instancje dostawcy. Dostawca sprawdza także hierarchię klas zdarzenia i jego interfejsy. Listener zarejestrowany na AbstractEvent otrzymuje każde zdarzenie cyklu życia. Listener zarejestrowany na interfejsie otrzymuje każde zdarzenie, które implementuje ten interfejs.

StoppableEventInterface dodaje kontrolę propagacji. Listener może wywołać stopPropagation(), a dyspozytor przestaje wtedy wywoływać kolejne listenery w tym cyklu. Zatrzymywalne zdarzenie to specjalny typ zdarzenia z własnym mechanizmem przerwania łańcucha listenerów. PSR-14 stosuje ten sam model dla zatrzymywalnych zdarzeń (PSR-14 psr_14_event#x4). AbstractEvent implementuje ten interfejs za pomocą StoppableEventTrait, więc każde zdarzenie cyklu życia jest domyślnie zatrzymywalne.

Dyspozytor ma szybką ścieżkę o zerowym narzucie. Sprawdza, czy jakikolwiek listener jest zarejestrowany dla klasy zdarzenia lub dowolnego jej przodka. Gdy nie ma żadnego, hasListeners() zwraca false, a dispatch() natychmiast się kończy. Dokument bez listenerów ponosi koszt jednego sprawdzenia wartości logicznej w każdym punkcie cyklu życia.

EventAwareDocumentTrait jest punktem integracji. Przechowuje opcjonalny EventDispatcher i udostępnia chronione pomocnicze metody wysyłania. Klasa Document wywołuje te metody w każdym punkcie cyklu życia. Gdy dyspozytor nie jest ustawiony, każda z nich nie wykonuje żadnej operacji.

Taksonomia cyklu życia obejmuje następujące zdarzenia:

ZdarzeniePrzestrzeń nazwWyzwalane
DocumentCreatedEventEvent\DocumentPo pełnym skonstruowaniu dokumentu
PageAddedEventEvent\DocumentPo zainicjalizowaniu strony
ContentRenderedEventEvent\ContentPo wyrenderowaniu treści HTML lub tekstowej na stronie
FontLoadedEventEvent\ContentGdy czcionka zostaje sparsowana do rejestru
EncryptionAppliedEventEvent\SecurityPo skonfigurowaniu parametrów szyfrowania
SignatureAppliedEventEvent\SecurityPo osadzeniu podpisu
PdfSerializedEventEvent\WriterPo serializacji, przed dostarczeniem wyniku
DocumentOutputEventEvent\DocumentZanim bajty PDF dotrą do miejsca docelowego
SymbolRodzajKluczowe składowe
NextPDF\Event\EventInterfaceinterfacegetEventName(): non-empty-string
NextPDF\Event\StoppableEventInterfaceinterfaceisPropagationStopped(): bool
NextPDF\Event\StoppableEventTraittraitisPropagationStopped(), stopPropagation()
NextPDF\Event\AbstractEventabstract classgetEventName(); implements StoppableEventInterface
NextPDF\Event\EventDispatcherfinal classdispatch(EventInterface): EventInterface, getListenerProvider()
NextPDF\Event\ListenerProviderfinal classaddListener(), getListenersForEvent(), hasListeners(), getListenerCount(), clearListeners()
NextPDF\Event\EventAwareDocumentTraittraitsetEventDispatcher(), getEventDispatcher()
NextPDF\Event\Document\DocumentCreatedEventfinal class$document, $config
NextPDF\Event\Document\PageAddedEventfinal class$document, $pageIndex, $pageSize, $orientation
NextPDF\Event\Document\DocumentOutputEventfinal classgetPdfData(), setPdfData(), getByteSize(), $filename, $destination
NextPDF\Event\Content\ContentRenderedEventfinal class$document, $pageIndex, $contentType, $content
NextPDF\Event\Content\FontLoadedEventfinal class$family, $style, $fontType, $filePath
NextPDF\Event\Security\EncryptionAppliedEventfinal class$document, $algorithm, $allowPrint, $allowCopy, $allowModify
NextPDF\Event\Security\SignatureAppliedEventfinal class$document, $signatureLevel, $signerName, $reason, $location
NextPDF\Event\Writer\PdfSerializedEventfinal class$byteSize, $objectCount, $pageCount, $pdfVersion, $isLinearized, $isEncrypted

addListener() rzuca NextPDF\Exception\InvalidConfigException, gdy ciąg znaków określający klasę zdarzenia jest pusty.

Zarejestruj listener, a następnie wyślij zdarzenie cyklu życia.

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

Klasa Document wewnętrznie wywołuje pomocnicze metody wysyłania po dołączeniu dyspozytora za pomocą setEventDispatcher().

Podłącz dyspozytor do dokumentu, ustal priorytety listenerów i zatrzymaj propagację w listenerze bramkującym.

<?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).
  • Rejestracja działająca jak symbol wieloznaczny opiera się na hierarchii klas. Listener zarejestrowany na AbstractEvent otrzymuje każde zdarzenie cyklu życia, ponieważ każde zdarzenie go rozszerza. Ogranicz listenery do konkretnych klas, gdy chcesz obsłużyć tylko jedno zdarzenie.
  • Listenery są sortowane według priorytetu, od najwyższego. Równe priorytety zachowują kolejność wstawiania (sortowanie stabilne).
  • stopPropagation() wstrzymuje tylko bieżący cykl wysyłania. Następne wysłane zdarzenie rozpoczyna nowy cykl.
  • Pamięć podręczna posortowanych listenerów jest unieważniana przy każdym wywołaniu addListener(), ponieważ rejestracja nowej klasy nadrzędnej lub interfejsu może zmienić sposób rozstrzygania dla kilku klas zdarzeń.
  • $document w zdarzeniach dotyczących dokumentu ma typ object, a nie klasę Document, aby moduł Event nie miał twardej zależności od Core.
  • DocumentOutputEvent::setPdfData() oczekuje niepustego ciągu znaków. Zastąpienie zawartości pustym ciągiem znaków powoduje powstanie nieprawidłowego dokumentu.
  • Pomocnicze metody wysyłania w EventAwareDocumentTrait nie wykonują żadnej operacji, dopóki nie zostanie ustawiony dyspozytor, więc przebiegi bez listenerów nie generują mierzalnego kosztu.

Wysyłanie bez listenerów ma złożoność O(1) dla liściowej klasy zdarzenia: jedno sprawdzenie wartości logicznej hasListeners(), a następnie natychmiastowy powrót. Gdy listenery są zarejestrowane, getListenersForEvent() jednokrotnie przechodzi po przodkach zdarzenia, sortuje zebrane wpisy i zapisuje posortowaną listę w pamięci podręcznej dla danej klasy zdarzenia do następnej zmiany. Powtórne wysłanie tej samej klasy ma zatem złożoność O(k) względem k pasujących listenerów. Domyślny performance_budget dla tej strony referencyjnej to wall_ms: 1500, peak_mb: 64.

Listenery działają wewnątrz potoku generowania z tymi samymi uprawnieniami co wywołujący. Traktuj kod listenera jak kod zaufany. DocumentOutputEvent udostępnia ostateczne binarne dane PDF i pozwala listenerowi je zastąpić. Listener audytu lub integralności powinien działać przed każdym listenerem przekształcającym bajty. Użyj wyższego priorytetu. Zdarzenia dotyczące bezpieczeństwa (EncryptionAppliedEvent, SignatureAppliedEvent) raportują zastosowane parametry na potrzeby zapisu audytu. Nie zmieniają wyniku kryptograficznego.

SpecyfikacjaKlauzulaTemat
PSR-14 (PHP-FIG)psr_14_event#x4Zatrzymywalne zdarzenie wstrzymuje kolejne listenery

Sygnatura dispatch(), rozdzielenie dyspozytora i dostawcy listenerów oraz model zatrzymywalnego zdarzenia są zgodne z PSR-14. NextPDF deklaruje własne EventInterface oraz StoppableEventInterface. Pakiet nie ma zależności runtime od PSR-14, a jednocześnie pozostaje kompatybilny w trybie duck-type.

  • /modules/core/contracts/ — publiczna powierzchnia interfejsów
  • /modules/core/observability/ — punkty zaczepienia telemetrii i metryk
  • /modules/core/audit/ — integracja ze ścieżką audytu
  • /modules/core/config/Config przekazywany w DocumentCreatedEvent
  • /modules/core/exception/InvalidConfigException z addListener()

Glosariusz: PSR-14 · zatrzymywalne zdarzenie · dostawca listenerów