Event: تصنيف أحداث دورة الحياة وفقًا لـ PSR-14
لمحة سريعة
قسم بعنوان «لمحة سريعة»تُوزِّع وحدة Event أحداث دورة حياة مُصنَّفة في كل مرحلة من مراحل إنشاء ملف PDF. يستطيع المستمعون مراقبة المستند أو تحويله دون تغيير المكوّنات الداخلية للمحرك. يتّبع الموزِّع توصية PHP Standards Recommendation 14 (PSR-14)، لذلك تبقى أدوات PSR-14 الموجودة متوافقة.
التثبيت
قسم بعنوان «التثبيت»composer require nextpdf/core:^3تأتي وحدة Event ضمن حزمة النواة. ولا تتطلب أي تبعيات إضافية: يعكس النوعان EventInterface وStoppableEventInterface عقود PSR-14 دون الحاجة إلى حزمة psr/event-dispatcher.
نظرة مفاهيمية عامة
قسم بعنوان «نظرة مفاهيمية عامة»تتكوّن الوحدة من ثلاثة أجزاء: موزِّع، ومزوِّد مستمعين، ومجموعة ثابتة من أصناف أحداث دورة الحياة.
يستقبل EventDispatcher نسخةً من EventInterface، ويطلب من ListenerProvider المستمعين المطابقين، ثم يستدعي كلًّا منهم وفق ترتيب الأولوية. تُعيد الطريقة dispatch() كائن الحدث نفسه. يستطيع المستمِع قراءة الحالة التي وضعها المحرك في الحدث، وكتابة حالة يقرأها المحرك لاحقًا. وهذا يطابق نموذج موزِّع PSR-14.
يربط ListenerProvider صنف حدثٍ بقائمة من العناصر القابلة للاستدعاء وفق ترتيب الأولوية. يقتصر نطاق التسجيل على النسخة، دون أي حالة ساكنة، لذلك تستطيع كل عملية عاملة الاحتفاظ بنسخها الخاصة من المزوِّد. كما يمرّ المزوِّد عبر شجرة صنف الحدث وواجهاته. يرى المستمِع المسجَّل على AbstractEvent كل حدث في دورة الحياة، ويرى المستمِع المسجَّل على واجهةٍ ما كل حدث يُنفِّذها.
يضيف StoppableEventInterface التحكّم في الانتشار. يستطيع المستمِع استدعاء stopPropagation()، فيتوقّف الموزِّع عندئذٍ عن استدعاء المستمعين اللاحقين في تلك الدورة. الحدث القابل للإيقاف هو حدث خاص يحمل آليته الخاصة لإيقاف سلسلة المستمعين. يستخدم PSR-14 النموذج نفسه للأحداث القابلة للإيقاف (PSR-14 psr_14_event#x4). يُنفِّذ AbstractEvent الواجهة عبر StoppableEventTrait، وبذلك يكون كل حدث في دورة الحياة قابلًا للإيقاف افتراضيًا.
للموزِّع مسار سريع بلا عبء إضافي. فهو يتحقّق من وجود مستمِع على صنف الحدث أو أي سلف له. وعند عدم وجود أي مستمِع، يُعيد hasListeners() القيمة false، فتعود dispatch() فورًا. يتحمّل المستند الذي لا مستمعين له تحقُّقًا منطقيًا واحدًا عند كل نقطة من نقاط دورة الحياة.
EventAwareDocumentTrait هو نقطة التكامل. فهو يحمل EventDispatcher اختياريًا ويُتيح دوال مساعِدة محمية للتوزيع. يستدعي الصنف Document هذه الدوال المساعِدة عند كل نقطة من نقاط دورة الحياة. وعند عدم تعيين أي موزِّع، تبقى كل دالة مساعِدة بلا أثر.
يشمل تصنيف دورة الحياة الأحداث التالية:
| الحدث | مساحة الاسم | وقت الإطلاق |
|---|---|---|
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 | واجهة | getEventName(): non-empty-string |
NextPDF\Event\StoppableEventInterface | واجهة | isPropagationStopped(): bool |
NextPDF\Event\StoppableEventTrait | سمة | isPropagationStopped(), stopPropagation() |
NextPDF\Event\AbstractEvent | صنف مجرّد | getEventName()؛ يُنفِّذ StoppableEventInterface |
NextPDF\Event\EventDispatcher | صنف نهائي | dispatch(EventInterface): EventInterface, getListenerProvider() |
NextPDF\Event\ListenerProvider | صنف نهائي | addListener(), getListenersForEvent(), hasListeners(), getListenerCount(), clearListeners() |
NextPDF\Event\EventAwareDocumentTrait | سمة | setEventDispatcher(), getEventDispatcher() |
NextPDF\Event\Document\DocumentCreatedEvent | صنف نهائي | $document, $config |
NextPDF\Event\Document\PageAddedEvent | صنف نهائي | $document, $pageIndex, $pageSize, $orientation |
NextPDF\Event\Document\DocumentOutputEvent | صنف نهائي | getPdfData(), setPdfData(), getByteSize(), $filename, $destination |
NextPDF\Event\Content\ContentRenderedEvent | صنف نهائي | $document, $pageIndex, $contentType, $content |
NextPDF\Event\Content\FontLoadedEvent | صنف نهائي | $family, $style, $fontType, $filePath |
NextPDF\Event\Security\EncryptionAppliedEvent | صنف نهائي | $document, $algorithm, $allowPrint, $allowCopy, $allowModify |
NextPDF\Event\Security\SignatureAppliedEvent | صنف نهائي | $document, $signatureLevel, $signerName, $reason, $location |
NextPDF\Event\Writer\PdfSerializedEvent | صنف نهائي | $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).حالات حدّية ومزالق
قسم بعنوان «حالات حدّية ومزالق»- يستخدم تسجيل المستمع العام التسلسل الهرمي للأصناف. يستقبل المستمِع المسجَّل على
AbstractEventكل حدث في دورة الحياة لأن كل حدث يرث منه. حدِّد نطاق المستمعين على أصناف محدّدة عندما تريد حدثًا واحدًا فقط. - تُرتَّب أولوية المستمعين من الأعلى أولًا. وتحافظ الأولويات المتساوية على ترتيب الإدراج (فرز مستقر).
- يوقِف
stopPropagation()دورة التوزيع الحالية فقط، ويبدأ الحدث المُوزَّع التالي دورة جديدة. - تُبطَل ذاكرة التخزين المؤقت للمستمعين المرتَّبين عند أي استدعاء لـ
addListener()لأن تسجيل أب أو واجهة جديدة قد يُغيّر التحليل لعدة أصناف أحداث. - النوع المُسنَد إلى
$documentفي الأحداث ذات النطاق المستندي هوobject، وليس الصنفDocument، لإبقاء وحدة Event خاليةً من تبعية صارمة علىCore. - يتوقّع
DocumentOutputEvent::setPdfData()سلسلةً غير فارغة. واستبدال الحمولة بسلسلة فارغة يُنتج مستندًا غير صالح. - تظل دوال التوزيع المساعِدة في
EventAwareDocumentTraitبلا أثر إلى أن يُعيَّن موزِّع، لذلك لا تضيف عمليات التشغيل الخالية من المستمعين أي كلفة قابلة للقياس.
الأداء
قسم بعنوان «الأداء»التوزيع دون مستمعين هو O(1) لصنف حدثٍ طرفي: تحقُّق منطقي واحد عبر hasListeners()، ثم عودة فورية. ومع وجود مستمعين، يمرّ getListenersForEvent() عبر سلسلة أسلاف الحدث مرةً واحدة، ويفرز المُدخلات المُجمَّعة، ويُخزِّن القائمة المرتَّبة مؤقتًا لكل صنف حدث حتى الطفرة التالية. لذلك يكون تكرار التوزيع للصنف نفسه O(k) عبر k من المستمعين المطابقين. قيمة performance_budget الافتراضية لهذه الصفحة المرجعية هي wall_ms: 1500، peak_mb: 64.
ملاحظات أمنية
قسم بعنوان «ملاحظات أمنية»يعمل المستمعون داخل خط إنشاء المستند بالصلاحيات نفسها التي يملكها المستدعي. عامِل شيفرة المستمِع على أنها شيفرة موثوقة. يكشف DocumentOutputEvent البيانات الثنائية النهائية لملف PDF ويتيح لمستمِع استبدالها. ينبغي أن يعمل مستمِع التدقيق أو السلامة قبل أي مستمِع يُحوِّل البايتات. استخدم أولوية أعلى. تُبلِّغ الأحداث ذات النطاق الأمني (EncryptionAppliedEvent، SignatureAppliedEvent) عن المعامِلات المُطبَّقة لأغراض تسجيل التدقيق، لكنها لا تُغيّر النتيجة التشفيرية.
التوافق
قسم بعنوان «التوافق»| المواصفة | البند | الموضوع |
|---|---|---|
| PSR-14 (PHP-FIG) | psr_14_event#x4 | الحدث القابل للإيقاف يوقِف المستمعين اللاحقين |
يتّبع توقيع dispatch()، والفصل بين المستمِع والمزوِّد، ونموذج الحدث القابل للإيقاف معيار PSR-14. يُعلِن NextPDF عن EventInterface وStoppableEventInterface الخاصَّين به. ليست للحزمة أي تبعية تشغيلية على PSR-14، لكنها تبقى متوافقة وفق نمط البطّة (duck typing).
انظر أيضًا
قسم بعنوان «انظر أيضًا»/modules/core/contracts/— سطح الواجهات العام/modules/core/observability/— خطّافات الرصد والمقاييس/modules/core/audit/— تكامل سجلّ التدقيق/modules/core/config/—Configالمُمرَّر علىDocumentCreatedEvent/modules/core/exception/—InvalidConfigExceptionمنaddListener()
المسرد: PSR-14 · الحدث القابل للإيقاف · مزوِّد المستمعين