Event: อนุกรมวิธานเหตุการณ์วงจรชีวิตตาม PSR-14
ภาพรวมโดยสังเขป
หัวข้อที่มีชื่อว่า “ภาพรวมโดยสังเขป”โมดูล Event ดิสแพตช์เหตุการณ์วงจรชีวิตแบบมีชนิด (typed) ในแต่ละขั้นตอนของการสร้าง PDF ลิสเซนเนอร์สามารถเฝ้าสังเกตหรือปรับแปลงเอกสารได้โดยไม่ต้องเปลี่ยนกลไกภายในของเอนจิน ดิสแพตเชอร์เป็นไปตาม PHP Standards Recommendation 14 (PSR-14) เครื่องมือ PSR-14 ที่มีอยู่จึงยังคงใช้งานร่วมกันได้
การติดตั้ง
หัวข้อที่มีชื่อว่า “การติดตั้ง”composer require nextpdf/core:^3โมดูล Event ถูกรวมมากับแพ็กเกจ core โมดูลนี้ไม่มีดีเพนเดนซีเพิ่มเติม: ชนิด EventInterface และ StoppableEventInterface สะท้อนสัญญาของ PSR-14 โดยไม่ต้องเพิ่ม psr/event-dispatcher เป็นแพ็กเกจแยกต่างหาก
ภาพรวมเชิงแนวคิด
หัวข้อที่มีชื่อว่า “ภาพรวมเชิงแนวคิด”โมดูลนี้มีสามส่วน: ดิสแพตเชอร์ ลิสเซนเนอร์โพรไวเดอร์ และชุดคลาสเหตุการณ์วงจรชีวิตที่กำหนดไว้แน่นอน
EventDispatcher รับอินสแตนซ์ของ EventInterface สอบถาม ListenerProvider เพื่อค้นหาลิสเซนเนอร์ที่ตรงกัน แล้วเรียกใช้แต่ละตัวตามลำดับความสำคัญ เมท็อด dispatch() คืนค่าออบเจ็กต์เหตุการณ์ตัวเดิม ลิสเซนเนอร์สามารถอ่านสถานะที่เอนจินใส่ไว้ในเหตุการณ์ และเขียนสถานะกลับคืนให้เอนจินอ่านในภายหลัง รูปแบบนี้สอดคล้องกับโมเดลดิสแพตเชอร์ของ PSR-14
ListenerProvider จับคู่คลาสเหตุการณ์กับรายการ callable ตามลำดับความสำคัญ การลงทะเบียนมีขอบเขตอยู่ที่อินสแตนซ์ และไม่มีสถานะแบบ static ดังนั้นกระบวนการเวิร์กเกอร์จึงถืออินสแตนซ์โพรไวเดอร์ของตนเองได้ โพรไวเดอร์ยังไล่ตามต้นไม้คลาสของเหตุการณ์และอินเทอร์เฟซต่าง ๆ ของเหตุการณ์ด้วย ลิสเซนเนอร์ที่ผูกกับ AbstractEvent จะเห็นทุกเหตุการณ์ในวงจรชีวิต ลิสเซนเนอร์ที่ผูกกับอินเทอร์เฟซจะเห็นทุกเหตุการณ์ที่อิมพลีเมนต์อินเทอร์เฟซนั้น
StoppableEventInterface เพิ่มการควบคุมการแพร่กระจาย ลิสเซนเนอร์สามารถเรียก stopPropagation() ได้ จากนั้นดิสแพตเชอร์จะหยุดเรียกลิสเซนเนอร์ถัดไปสำหรับรอบนั้น เหตุการณ์แบบหยุดได้คือเหตุการณ์พิเศษที่มีวิธีหยุดเชนของลิสเซนเนอร์อยู่ในตัว PSR-14 ใช้โมเดลเดียวกันนี้สำหรับเหตุการณ์แบบหยุดได้ (PSR-14 psr_14_event#x4) AbstractEvent อิมพลีเมนต์อินเทอร์เฟซนี้ผ่าน StoppableEventTrait ดังนั้นทุกเหตุการณ์ในวงจรชีวิตจึงเป็นแบบหยุดได้โดยค่าเริ่มต้น
ดิสแพตเชอร์มีเส้นทางลัดที่ไม่มีโอเวอร์เฮด ตัวดิสแพตเชอร์จะตรวจสอบว่ามีลิสเซนเนอร์บนคลาสเหตุการณ์หรือบรรพบุรุษใด ๆ หรือไม่ หากไม่มีเลย hasListeners() จะคืนค่า false และ dispatch() จะคืนค่าทันที เอกสารที่ไม่มีลิสเซนเนอร์จะมีต้นทุนเพียงการตรวจค่าบูลีนหนึ่งครั้งต่อจุดในวงจรชีวิต
EventAwareDocumentTrait คือจุดเชื่อมต่อสำหรับการบูรณาการ เทรตนี้ถือ EventDispatcher แบบเลือกได้ และเปิดให้ใช้ตัวช่วยดิสแพตช์แบบ protected คลาส Document เรียกใช้ตัวช่วยเหล่านี้ในแต่ละจุดของวงจรชีวิต เมื่อไม่ได้ตั้งค่าดิสแพตเชอร์ ตัวช่วยทุกตัวจะเป็น no-op
อนุกรมวิธานวงจรชีวิตประกอบด้วยเหตุการณ์เหล่านี้:
| เหตุการณ์ | เนมสเปซ | จุดที่ส่ง |
|---|---|---|
DocumentCreatedEvent | Event\Document | หลังจากสร้างเอกสารเสร็จสมบูรณ์ |
PageAddedEvent | Event\Document | หลังจากเริ่มต้นหน้า |
ContentRenderedEvent | Event\Content | หลังจากเรนเดอร์เนื้อหา HTML หรือข้อความลงในหน้า |
FontLoadedEvent | Event\Content | เมื่อแจกแจงฟอนต์เข้าสู่รีจิสทรี |
EncryptionAppliedEvent | Event\Security | หลังจากกำหนดค่าพารามิเตอร์การเข้ารหัสลับ |
SignatureAppliedEvent | Event\Security | หลังจากฝังลายเซ็น |
PdfSerializedEvent | Event\Writer | หลังจากการซีเรียลไลซ์ ก่อนการส่งมอบเอาต์พุต |
DocumentOutputEvent | Event\Document | ก่อนที่ไบต์ของ PDF จะไปถึงปลายทาง |
พื้นผิว API
หัวข้อที่มีชื่อว่า “พื้นผิว API”| สัญลักษณ์ | ชนิด | สมาชิกสำคัญ |
|---|---|---|
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(); อิมพลีเมนต์ 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() จะโยน NextPDF\Exception\InvalidConfigException เมื่อสตริงคลาสเหตุการณ์ว่างเปล่า
ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว”ลงทะเบียนลิสเซนเนอร์ จากนั้นดิสแพตช์เหตุการณ์ในวงจรชีวิต
<?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);คลาส Document เรียกใช้ตัวช่วยดิสแพตช์ภายในหลังจากผูกดิสแพตเชอร์ผ่าน setEventDispatcher() แล้ว
ตัวอย่างโค้ด — สำหรับใช้งานจริง
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — สำหรับใช้งานจริง”เชื่อมต่อดิสแพตเชอร์เข้ากับเอกสาร ตั้งลำดับความสำคัญของลิสเซนเนอร์ และหยุดการแพร่กระจายจากลิสเซนเนอร์ที่ทำหน้าที่เป็นเกตควบคุม
<?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).กรณีขอบและข้อควรระวัง
หัวข้อที่มีชื่อว่า “กรณีขอบและข้อควรระวัง”- การลงทะเบียนแบบ wildcard ใช้ลำดับชั้นของคลาส ลิสเซนเนอร์ที่ผูกกับ
AbstractEventจะได้รับทุกเหตุการณ์ในวงจรชีวิต เพราะทุกเหตุการณ์สืบทอดจากคลาสนี้ จำกัดขอบเขตลิสเซนเนอร์ไว้ที่คลาสรูปธรรมเมื่อคุณต้องการเฉพาะเหตุการณ์เดียว - ลำดับความสำคัญของลิสเซนเนอร์เรียงจากสูงสุดก่อน ความสำคัญที่เท่ากันจะคงลำดับการแทรกไว้ (การเรียงแบบเสถียร)
stopPropagation()หยุดเฉพาะรอบการดิสแพตช์ปัจจุบันเท่านั้น เหตุการณ์ที่ดิสแพตช์ถัดไปจะเริ่มรอบใหม่- แคชลิสเซนเนอร์ที่เรียงแล้วจะถูกทำให้ใช้ไม่ได้เมื่อมีการเรียก
addListener()ใด ๆ เพราะการลงทะเบียนพาเรนต์หรืออินเทอร์เฟซใหม่สามารถเปลี่ยนผลการจับคู่ (resolution) ของคลาสเหตุการณ์หลายคลาสได้ $documentบนเหตุการณ์ที่มีขอบเขตระดับเอกสารมีชนิดเป็นobjectไม่ใช่คลาสDocumentเพื่อให้โมดูล Event ไม่ต้องพึ่งพาCoreเป็นดีเพนเดนซีแบบตายตัวDocumentOutputEvent::setPdfData()คาดหวังสตริงที่ไม่ว่างเปล่า การแทนที่เพย์โหลดด้วยสตริงว่างเปล่าจะทำให้ได้เอกสารที่ไม่ถูกต้อง- ตัวช่วยดิสแพตช์บน
EventAwareDocumentTraitเป็น no-op จนกว่าจะตั้งค่าดิสแพตเชอร์ ดังนั้นการรันที่ไม่มีลิสเซนเนอร์จึงไม่เพิ่มต้นทุนที่วัดได้
ประสิทธิภาพ
หัวข้อที่มีชื่อว่า “ประสิทธิภาพ”การดิสแพตช์ที่ไม่มีลิสเซนเนอร์เป็น O(1) สำหรับคลาสเหตุการณ์ที่เป็นใบ (leaf): ตรวจค่าบูลีน hasListeners() หนึ่งครั้ง แล้วคืนค่าทันที เมื่อมีลิสเซนเนอร์ getListenersForEvent() จะไล่ตามบรรพบุรุษของเหตุการณ์หนึ่งครั้ง เรียงรายการที่รวบรวมได้ และแคชรายการที่เรียงแล้วต่อคลาสเหตุการณ์จนกว่าจะมีการเปลี่ยนแปลงครั้งถัดไป ดังนั้นการดิสแพตช์คลาสเดิมซ้ำจึงเป็น O(k) โดย k คือจำนวนลิสเซนเนอร์ที่ตรงกัน ค่าเริ่มต้นของ performance_budget สำหรับหน้าอ้างอิงนี้คือ wall_ms: 1500, peak_mb: 64 ตามลำดับ
หมายเหตุด้านความปลอดภัย
หัวข้อที่มีชื่อว่า “หมายเหตุด้านความปลอดภัย”ลิสเซนเนอร์ทำงานภายในไปป์ไลน์การสร้างด้วยสิทธิ์เดียวกับผู้เรียก จงปฏิบัติต่อโค้ดของลิสเซนเนอร์เป็นโค้ดที่เชื่อถือได้ DocumentOutputEvent เปิดเผยไบนารี PDF สุดท้ายและอนุญาตให้ลิสเซนเนอร์แทนที่ได้ ลิสเซนเนอร์สำหรับการตรวจสอบหรือความสมบูรณ์ (audit หรือ integrity) ควรทำงานก่อนลิสเซนเนอร์ใด ๆ ที่แปลงไบต์ โดยใช้ลำดับความสำคัญที่สูงกว่า เหตุการณ์ที่มีขอบเขตด้านความปลอดภัย (EncryptionAppliedEvent, SignatureAppliedEvent) จะรายงานพารามิเตอร์ที่นำมาใช้สำหรับการบันทึกการตรวจสอบ เหตุการณ์เหล่านี้ไม่เปลี่ยนผลลัพธ์ทางการเข้ารหัสลับ
ความสอดคล้อง
หัวข้อที่มีชื่อว่า “ความสอดคล้อง”| ข้อกำหนด | ข้อ | หัวข้อ |
|---|---|---|
| PSR-14 (PHP-FIG) | psr_14_event#x4 | เหตุการณ์แบบหยุดได้จะระงับลิสเซนเนอร์ตัวถัดไป |
ลายเซ็นของ dispatch() การแยกลิสเซนเนอร์โพรไวเดอร์ และโมเดลเหตุการณ์แบบหยุดได้เป็นไปตาม PSR-14 NextPDF ประกาศ EventInterface และ StoppableEventInterface ของตนเอง แพ็กเกจนี้ไม่มีดีเพนเดนซีขณะรันไทม์ต่อ PSR-14 แต่ยังคงเข้ากันได้แบบ duck-type
ดูเพิ่มเติม
หัวข้อที่มีชื่อว่า “ดูเพิ่มเติม”/modules/core/contracts/— พื้นผิวของอินเทอร์เฟซสาธารณะ/modules/core/observability/— ฮุกด้าน telemetry และเมตริก/modules/core/audit/— การบูรณาการเส้นทางการตรวจสอบ/modules/core/config/—Configที่ส่งต่อบนDocumentCreatedEvent/modules/core/exception/—InvalidConfigExceptionจากaddListener()
อภิธานศัพท์: PSR-14 · เหตุการณ์แบบหยุดได้ · ลิสเซนเนอร์โพรไวเดอร์