Event: taksonomia zdarzeń cyklu życia zgodna z PSR-14
W skrócie
Dział zatytułowany „W skrócie”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.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/core:^3Moduł 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.
Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”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:
| Zdarzenie | Przestrzeń nazw | Wyzwalane |
|---|---|---|
DocumentCreatedEvent | Event\Document | Po pełnym skonstruowaniu dokumentu |
PageAddedEvent | Event\Document | Po zainicjalizowaniu strony |
ContentRenderedEvent | Event\Content | Po wyrenderowaniu treści HTML lub tekstowej na stronie |
FontLoadedEvent | Event\Content | Gdy czcionka zostaje sparsowana do rejestru |
EncryptionAppliedEvent | Event\Security | Po skonfigurowaniu parametrów szyfrowania |
SignatureAppliedEvent | Event\Security | Po osadzeniu podpisu |
PdfSerializedEvent | Event\Writer | Po serializacji, przed dostarczeniem wyniku |
DocumentOutputEvent | Event\Document | Zanim bajty PDF dotrą do miejsca docelowego |
Powierzchnia API
Dział zatytułowany „Powierzchnia API”| Symbol | Rodzaj | Kluczowe składowe |
|---|---|---|
NextPDF\Event\EventInterface | interface | getEventName(): non-empty-string |
NextPDF\Event\StoppableEventInterface | interface | isPropagationStopped(): bool |
NextPDF\Event\StoppableEventTrait | trait | isPropagationStopped(), stopPropagation() |
NextPDF\Event\AbstractEvent | abstract class | getEventName(); implements StoppableEventInterface |
NextPDF\Event\EventDispatcher | final class | dispatch(EventInterface): EventInterface, getListenerProvider() |
NextPDF\Event\ListenerProvider | final class | addListener(), getListenersForEvent(), hasListeners(), getListenerCount(), clearListeners() |
NextPDF\Event\EventAwareDocumentTrait | trait | setEventDispatcher(), getEventDispatcher() |
NextPDF\Event\Document\DocumentCreatedEvent | final class | $document, $config |
NextPDF\Event\Document\PageAddedEvent | final class | $document, $pageIndex, $pageSize, $orientation |
NextPDF\Event\Document\DocumentOutputEvent | final class | getPdfData(), setPdfData(), getByteSize(), $filename, $destination |
NextPDF\Event\Content\ContentRenderedEvent | final class | $document, $pageIndex, $contentType, $content |
NextPDF\Event\Content\FontLoadedEvent | final class | $family, $style, $fontType, $filePath |
NextPDF\Event\Security\EncryptionAppliedEvent | final class | $document, $algorithm, $allowPrint, $allowCopy, $allowModify |
NextPDF\Event\Security\SignatureAppliedEvent | final class | $document, $signatureLevel, $signerName, $reason, $location |
NextPDF\Event\Writer\PdfSerializedEvent | final class | $byteSize, $objectCount, $pageCount, $pdfVersion, $isLinearized, $isEncrypted |
addListener() rzuca NextPDF\Exception\InvalidConfigException, gdy ciąg znaków określający klasę zdarzenia jest pusty.
Przykład kodu — szybki start
Dział zatytułowany „Przykład kodu — szybki start”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().
Przykład kodu — produkcja
Dział zatytułowany „Przykład kodu — produkcja”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).Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- Rejestracja działająca jak symbol wieloznaczny opiera się na hierarchii klas. Listener zarejestrowany na
AbstractEventotrzymuje 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ń. $documentw zdarzeniach dotyczących dokumentu ma typobject, a nie klasęDocument, aby moduł Event nie miał twardej zależności odCore.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
EventAwareDocumentTraitnie wykonują żadnej operacji, dopóki nie zostanie ustawiony dyspozytor, więc przebiegi bez listenerów nie generują mierzalnego kosztu.
Wydajność
Dział zatytułowany „Wydajność”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.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”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.
Zgodność
Dział zatytułowany „Zgodność”| Specyfikacja | Klauzula | Temat |
|---|---|---|
| PSR-14 (PHP-FIG) | psr_14_event#x4 | Zatrzymywalne 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.
Zobacz też
Dział zatytułowany „Zobacz też”/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/—Configprzekazywany wDocumentCreatedEvent/modules/core/exception/—InvalidConfigExceptionzaddListener()
Glosariusz: PSR-14 · zatrzymywalne zdarzenie · dostawca listenerów