تخطَّ إلى المحتوى

Event: تصنيف أحداث دورة الحياة وفقًا لـ PSR-14

تُوزِّع وحدة ⁨Event⁩ أحداث دورة حياة مُصنَّفة في كل مرحلة من مراحل إنشاء ملف ⁨PDF.⁩ يستطيع المستمعون مراقبة المستند أو تحويله دون تغيير المكوّنات الداخلية للمحرك. يتّبع الموزِّع توصية ⁨PHP Standards Recommendation 14⁩ (⁨PSR-14⁩)، لذلك تبقى أدوات ⁨PSR-14⁩ الموجودة متوافقة.

Terminal window
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 هذه الدوال المساعِدة عند كل نقطة من نقاط دورة الحياة. وعند عدم تعيين أي موزِّع، تبقى كل دالة مساعِدة بلا أثر.

يشمل تصنيف دورة الحياة الأحداث التالية:

الحدثمساحة الاسموقت الإطلاق
DocumentCreatedEventEvent\Documentبعد اكتمال إنشاء المستند بالكامل
PageAddedEventEvent\Documentبعد تهيئة صفحة
ContentRenderedEventEvent\Contentبعد عرض محتوى ⁨HTML⁩ أو نصي على صفحة
FontLoadedEventEvent\Contentعند تحليل خط وإدراجه في السجل
EncryptionAppliedEventEvent\Securityبعد ضبط معامِلات التشفير
SignatureAppliedEventEvent\Securityبعد تضمين توقيع
PdfSerializedEventEvent\Writerبعد التسلسل، وقبل تسليم الإخراج
DocumentOutputEventEvent\Documentقبل إرسال بايتات ⁨PDF⁩ إلى الوجهة
الرمزالنوعالأعضاء الرئيسة
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⁩ · الحدث القابل للإيقاف · مزوِّد المستمعين