Bỏ qua để đến nội dung

Event: phân loại sự kiện vòng đời theo PSR-14

Module Event điều phối các sự kiện vòng đời được định kiểu ở từng giai đoạn của quá trình tạo PDF. Listener có thể quan sát hoặc biến đổi tài liệu mà không cần thay đổi phần bên trong của engine. Bộ điều phối tuân theo PHP Standards Recommendation 14 (PSR-14), nên vẫn tương thích với các công cụ PSR-14 hiện có.

Terminal window
composer require nextpdf/core:^3

Module Event đi kèm gói core. Module này không có phụ thuộc bổ sung: các kiểu EventInterfaceStoppableEventInterface phản ánh các hợp đồng PSR-14 mà không yêu cầu gói psr/event-dispatcher.

Module gồm ba phần: một bộ điều phối, một listener provider và một tập cố định các lớp sự kiện vòng đời.

EventDispatcher nhận một thực thể EventInterface, hỏi ListenerProvider về các listener phù hợp, rồi gọi từng listener theo thứ tự ưu tiên. Phương thức dispatch() trả về chính đối tượng sự kiện đó. Một listener có thể đọc trạng thái mà engine đã đặt vào sự kiện và ghi lại trạng thái mà engine sẽ đọc sau đó. Điều này khớp với mô hình bộ điều phối của PSR-14.

ListenerProvider ánh xạ một lớp sự kiện tới một danh sách callable theo thứ tự ưu tiên. Việc đăng ký được giới hạn trong phạm vi thực thể, không có trạng thái tĩnh, nên mỗi worker process có thể giữ các thực thể provider riêng. Provider cũng duyệt cây lớp của sự kiện và các interface của nó. Một listener trên AbstractEvent thấy mọi sự kiện vòng đời. Một listener trên một interface thấy mọi sự kiện triển khai interface đó.

StoppableEventInterface bổ sung khả năng kiểm soát lan truyền. Một listener có thể gọi stopPropagation(), sau đó bộ điều phối sẽ ngừng gọi các listener còn lại trong chu kỳ đó. Sự kiện có thể dừng là loại sự kiện đặc biệt tự mang cơ chế dừng chuỗi listener. PSR-14 dùng cùng mô hình cho các sự kiện có thể dừng (PSR-14 psr_14_event#x4). AbstractEvent triển khai interface thông qua StoppableEventTrait, nên theo mặc định mọi sự kiện vòng đời đều có thể dừng.

Bộ điều phối có một nhánh xử lý nhanh không phát sinh chi phí. Nó kiểm tra xem có listener nào trên lớp sự kiện hoặc bất kỳ lớp tổ tiên nào hay không. Khi không có listener nào, hasListeners() trả về false, và dispatch() trả về ngay lập tức. Một tài liệu không có listener nào chỉ tốn một lần kiểm tra boolean cho mỗi điểm vòng đời.

EventAwareDocumentTrait là điểm tích hợp. Trait này giữ một EventDispatcher tùy chọn và cung cấp các hàm trợ giúp dispatch ở mức protected. Lớp Document gọi các hàm trợ giúp này tại mỗi điểm vòng đời. Khi không đặt bộ điều phối nào, mọi hàm trợ giúp đều là no-op.

Phân loại vòng đời bao gồm các sự kiện sau:

Sự kiệnNamespaceKích hoạt
DocumentCreatedEventEvent\DocumentSau khi một tài liệu được dựng xong hoàn toàn
PageAddedEventEvent\DocumentSau khi một trang được khởi tạo
ContentRenderedEventEvent\ContentSau khi nội dung HTML hoặc văn bản được kết xuất vào một trang
FontLoadedEventEvent\ContentKhi một phông chữ được phân tích vào registry
EncryptionAppliedEventEvent\SecuritySau khi các tham số mã hóa được cấu hình
SignatureAppliedEventEvent\SecuritySau khi một chữ ký được nhúng
PdfSerializedEventEvent\WriterSau khi tuần tự hóa, trước khi giao kết quả đầu ra
DocumentOutputEventEvent\DocumentTrước khi các byte PDF được gửi đến đích
Ký hiệuLoạiThành viên chính
NextPDF\Event\EventInterfaceinterfacegetEventName(): non-empty-string
NextPDF\Event\StoppableEventInterfaceinterfaceisPropagationStopped(): bool
NextPDF\Event\StoppableEventTraittraitisPropagationStopped(), stopPropagation()
NextPDF\Event\AbstractEventabstract classgetEventName(); triển khai 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() ném NextPDF\Exception\InvalidConfigException khi chuỗi tên lớp sự kiện rỗng.

Đăng ký một listener, rồi điều phối một sự kiện vòng đời.

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

Lớp Document gọi nội bộ các hàm trợ giúp dispatch sau khi một bộ điều phối được gắn vào thông qua setEventDispatcher().

Kết nối bộ điều phối vào tài liệu, đặt độ ưu tiên cho listener, và dừng lan truyền từ một listener đóng vai trò cổng kiểm soát.

<?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).
  • Việc đăng ký dạng wildcard dùng hệ thống phân cấp lớp. Một listener trên AbstractEvent nhận mọi sự kiện vòng đời vì mọi sự kiện đều kế thừa từ nó. Hãy giới hạn listener vào các lớp cụ thể khi bạn chỉ muốn xử lý một sự kiện.
  • Độ ưu tiên của listener sắp xếp cao nhất trước. Các độ ưu tiên bằng nhau giữ nguyên thứ tự chèn vào (sắp xếp ổn định).
  • stopPropagation() chỉ dừng chu kỳ điều phối hiện tại. Sự kiện được điều phối kế tiếp bắt đầu một chu kỳ mới.
  • Bộ nhớ đệm listener đã sắp xếp bị vô hiệu hóa khi có bất kỳ lệnh gọi addListener() nào, vì việc đăng ký một lớp cha hoặc interface mới có thể thay đổi cách phân giải của nhiều lớp sự kiện.
  • $document trên các sự kiện thuộc phạm vi tài liệu có kiểu object, không phải lớp Document, để giữ cho module Event không có phụ thuộc cứng vào Core.
  • DocumentOutputEvent::setPdfData() mong đợi một chuỗi không rỗng. Việc thay payload bằng một chuỗi rỗng tạo ra một tài liệu không hợp lệ.
  • Các hàm trợ giúp dispatch trên EventAwareDocumentTrait là no-op cho đến khi đặt một bộ điều phối, nên các lần chạy không có listener không phát sinh chi phí đáng kể nào.

Điều phối khi không có listener nào là O(1) cho một lớp sự kiện lá: một lần kiểm tra boolean hasListeners(), rồi trả về ngay lập tức. Khi có listener, getListenersForEvent() duyệt cây tổ tiên của sự kiện một lần, sắp xếp các mục đã thu thập, và lưu danh sách đã sắp xếp theo từng lớp sự kiện vào bộ nhớ đệm cho đến lần thay đổi tiếp theo. Do đó, mỗi lần điều phối lặp lại của cùng một lớp là O(k) trên k listener khớp. performance_budget mặc định cho trang tham khảo này là wall_ms: 1500, peak_mb: 64.

Listener chạy bên trong pipeline tạo PDF với cùng đặc quyền như bên gọi. Hãy xem mã listener là mã đáng tin cậy. DocumentOutputEvent phơi bày dữ liệu nhị phân PDF cuối cùng và cho phép một listener thay thế dữ liệu đó. Listener kiểm toán hoặc kiểm tra toàn vẹn nên chạy trước mọi listener biến đổi các byte. Hãy dùng độ ưu tiên cao hơn. Các sự kiện thuộc phạm vi bảo mật (EncryptionAppliedEvent, SignatureAppliedEvent) báo cáo các tham số đã áp dụng để ghi nhật ký kiểm toán. Chúng không thay đổi kết quả mã hóa.

Đặc tảĐiều khoảnChủ đề
PSR-14 (PHP-FIG)psr_14_event#x4Sự kiện có thể dừng làm ngừng các listener tiếp theo

Chữ ký của dispatch(), sự tách biệt listener-provider, và mô hình sự kiện có thể dừng đều tuân theo PSR-14. NextPDF khai báo EventInterfaceStoppableEventInterface riêng. Gói này không có phụ thuộc runtime vào PSR-14, nhưng vẫn tương thích theo kiểu duck-type.

  • /modules/core/contracts/ — bề mặt interface công khai
  • /modules/core/observability/ — các hook đo lường và số liệu
  • /modules/core/audit/ — tích hợp dấu vết kiểm toán
  • /modules/core/config/Config được truyền trên DocumentCreatedEvent
  • /modules/core/exception/InvalidConfigException từ addListener()

Bảng thuật ngữ: PSR-14 · sự kiện có thể dừng · listener provider