ข้ามไปยังเนื้อหา

Event: อนุกรมวิธานเหตุการณ์วงจรชีวิตตาม PSR-14

โมดูล Event ดิสแพตช์เหตุการณ์วงจรชีวิตแบบมีชนิด (typed) ในแต่ละขั้นตอนของการสร้าง PDF ลิสเซนเนอร์สามารถเฝ้าสังเกตหรือปรับแปลงเอกสารได้โดยไม่ต้องเปลี่ยนกลไกภายในของเอนจิน ดิสแพตเชอร์เป็นไปตาม PHP Standards Recommendation 14 (PSR-14) เครื่องมือ PSR-14 ที่มีอยู่จึงยังคงใช้งานร่วมกันได้

Terminal window
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

อนุกรมวิธานวงจรชีวิตประกอบด้วยเหตุการณ์เหล่านี้:

เหตุการณ์เนมสเปซจุดที่ส่ง
DocumentCreatedEventEvent\Documentหลังจากสร้างเอกสารเสร็จสมบูรณ์
PageAddedEventEvent\Documentหลังจากเริ่มต้นหน้า
ContentRenderedEventEvent\Contentหลังจากเรนเดอร์เนื้อหา HTML หรือข้อความลงในหน้า
FontLoadedEventEvent\Contentเมื่อแจกแจงฟอนต์เข้าสู่รีจิสทรี
EncryptionAppliedEventEvent\Securityหลังจากกำหนดค่าพารามิเตอร์การเข้ารหัสลับ
SignatureAppliedEventEvent\Securityหลังจากฝังลายเซ็น
PdfSerializedEventEvent\Writerหลังจากการซีเรียลไลซ์ ก่อนการส่งมอบเอาต์พุต
DocumentOutputEventEvent\Documentก่อนที่ไบต์ของ PDF จะไปถึงปลายทาง
สัญลักษณ์ชนิดสมาชิกสำคัญ
NextPDF\Event\EventInterfaceinterfacegetEventName(): non-empty-string
NextPDF\Event\StoppableEventInterfaceinterfaceisPropagationStopped(): bool
NextPDF\Event\StoppableEventTraittraitisPropagationStopped(), stopPropagation()
NextPDF\Event\AbstractEventabstract classgetEventName(); อิมพลีเมนต์ 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() จะโยน 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 · เหตุการณ์แบบหยุดได้ · ลิสเซนเนอร์โพรไวเดอร์