Event: phân loại sự kiện vòng đời theo PSR-14
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”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ó.
Cài đặt
Phần tiêu đề “Cài đặt”composer require nextpdf/core:^3Module Event đi kèm gói core. Module này không có phụ thuộc bổ sung: các kiểu EventInterface và StoppableEventInterface phản ánh các hợp đồng PSR-14 mà không yêu cầu gói psr/event-dispatcher.
Tổng quan khái niệm
Phần tiêu đề “Tổng quan khái niệm”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ện | Namespace | Kích hoạt |
|---|---|---|
DocumentCreatedEvent | Event\Document | Sau khi một tài liệu được dựng xong hoàn toàn |
PageAddedEvent | Event\Document | Sau khi một trang được khởi tạo |
ContentRenderedEvent | Event\Content | Sau khi nội dung HTML hoặc văn bản được kết xuất vào một trang |
FontLoadedEvent | Event\Content | Khi một phông chữ được phân tích vào registry |
EncryptionAppliedEvent | Event\Security | Sau khi các tham số mã hóa được cấu hình |
SignatureAppliedEvent | Event\Security | Sau khi một chữ ký được nhúng |
PdfSerializedEvent | Event\Writer | Sau khi tuần tự hóa, trước khi giao kết quả đầu ra |
DocumentOutputEvent | Event\Document | Trước khi các byte PDF được gửi đến đích |
Bề mặt API
Phần tiêu đề “Bề mặt API”| Ký hiệu | Loại | Thành viên chính |
|---|---|---|
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(); triển khai 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() ném NextPDF\Exception\InvalidConfigException khi chuỗi tên lớp sự kiện rỗng.
Mẫu mã — bắt đầu nhanh
Phần tiêu đề “Mẫu mã — bắt đầu nhanh”Đă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().
Mẫu mã — sản xuất
Phần tiêu đề “Mẫu mã — sản xuất”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).Trường hợp biên & lưu ý
Phần tiêu đề “Trường hợp biên & lưu ý”- Việc đăng ký dạng wildcard dùng hệ thống phân cấp lớp. Một listener trên
AbstractEventnhậ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. $documenttrên các sự kiện thuộc phạm vi tài liệu có kiểuobject, không phải lớpDocument, để giữ cho module Event không có phụ thuộc cứng vàoCore.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
EventAwareDocumentTraitlà 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.
Hiệu năng
Phần tiêu đề “Hiệu năng”Đ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.
Lưu ý bảo mật
Phần tiêu đề “Lưu ý bảo mật”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.
Tuân thủ
Phần tiêu đề “Tuân thủ”| Đặc tả | Điều khoản | Chủ đề |
|---|---|---|
| PSR-14 (PHP-FIG) | psr_14_event#x4 | Sự 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 EventInterface và StoppableEventInterface 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.
Xem thêm
Phần tiêu đề “Xem thêm”/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ênDocumentCreatedEvent/modules/core/exception/—InvalidConfigExceptiontừaddListener()
Bảng thuật ngữ: PSR-14 · sự kiện có thể dừng · listener provider