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

Pro الإصدار

‏⁨Stream⁩

تُصيِّر وحدة ⁨Stream⁩ دفعات من المستندات بدوام وبتزامن، مع التزام محلّي مرّة واحدة بالضبط إلى مخازن دائمة أحادية المضيف (المرّة الواحدة بالضبط عبر مضيفين متعدّدين هي حدّ ⁨Enterprise Stream⁩). وهي تقسّم العمل إلى مسؤوليتين مفصولتين بنظافة: محرّك تصيير يحوّل البيانات الوصفية المُتحقَّق منها إلى بايتات (ولا شيء سوى ذلك)، ومجموعة من المخازن الدائمة — المُلتزِم، ونقطة التفتيش، وخاصية العدم التأثيرية، والرسائل الميتة — تنشر تلك البايتات بأمان وتتيح لتشغيلٍ ما الاستئنافَ بعد عطل من دون إعادة نشر المخرَجات الملتزَم بها.

تُشحَن هذه الإمكانية في ‏⁨NextPDF Pro⁩ (nextpdf/pro) وتُفعَّل بمظروف ترخيص من فئة ⁨Pro⁩. والنشر من دون ذلك الاستحقاق لا يحمّل أصناف الإمكانية. قارِن الإصدارات واحصل على ترخيص.

لا توجد علامة ترخيص منفصلة لكل ميزة. والتزامن (عدد العمّال)، وحجم الدفعة، وميزانية إعادة المحاولة، والخلفية التخزينية للمخزن (في الذاكرة مقابل نظام ملفات دائم) معامِلات وقت تشغيل، لا مفاتيح ترخيص.

Terminal window
composer require nextpdf/pro:^3

تعيش الشيفرة ضمن النطاق الاسمي NextPDF\Pro\Stream.

تُنظَّم ⁨Stream⁩ حول وصلة مُجمَّدة — NextPDF\Pro\Stream\Engine\RenderEngineInterface — تفصل محرّك الإنتاجية عن دلالات التدفّق:

  • يملك محرّك التصيير التزامنَ والذاكرة المحدودة. فهو يُصيِّر نافذة من البيانات الوصفية المُتحقَّق منها والمُزال تكرارها مسبقًا عبر renderBatch() ويُرجِع نتيجة EngineRenderResult واحدة لكل بيانات وصفية، بترتيب الإدخال. والأهمّ أنّ المحرّك خالٍ من الآثار الجانبية فيما يخصّ المخرَج النهائي: فهو يُرجِع البايتات المُصيَّرة مع بصمة ⁨sha-256⁩ الخاصّة بها، ولا يكتب أبدًا إلى مفتاح كائن نهائي. وهذه النقاوة هي ما يجعل التسليم مرّة واحدة بالضبط ممكنًا.
  • يملك متعاونو التدفّق التسليمَ. فالمُلتزِم، ومخزن نقطة التفتيش، ومخزن خاصية العدم التأثيرية (إزالة التكرار)، ومخزن الرسائل الميتة تقرّر أين تستقرّ البايتات، وكيف يُستأنَف تشغيلٌ ما، وأيُّ عملٍ هو إعادة تشغيل، وما الذي يحدث لحالات الفشل النهائية.

يُبلَّغ عن فشل تصيير لكل بيانات وصفية كنتيجة Failed (أو Timeout) لكل عنصر؛ ولا يُجهِض الدفعة أبدًا. وينجح غلاف الدفعة دائمًا بنتائج لكل عنصر.

محرّكات التصيير والمُنفِّذات

قسم بعنوان «محرّكات التصيير والمُنفِّذات»
  • InProcessRenderEngine هو الخطّ المرجعي المتزامن أحادي العملية للصحّة. وهو يتحقّق من كلّ بيانات وصفية تحقُّقًا مُغلَقًا بأمان عبر RenderManifestValidator المشحون قبل تصييرها عبر SingleDocumentRenderer في ⁨Core⁩، فتصير البيانات الوصفية الفاسدة فشلًا لكل عنصر (رمز الخطأ SPEC-MANIFEST-INVALID) بدلًا من وصولها إلى المُصيِّر.
  • يُوزِّع ConcurrentRenderEngine دفعةً على RenderUnitExecutorInterface ويستعيد ترتيب الدفعة الحتمي بحسب فهرس الوحدة. والمخرَج متطابق بايتيًا مع تصيير متسلسل بصرف النظر عن ترتيب الإكمال؛ وأيُّ إكمال مفقود أو مكرَّر أو مجهول هو فشل صارم، لا إسقاط صامت أبدًا.
  • المُنفِّذات هي وصلة التزامن. InlineRenderUnitExecutor هو الخطّ المرجعي الحتمي؛ ويُوزِّع ProcessPoolRenderUnitExecutor دفعةً على عدد يصل إلى ⁨N⁩ من العمليات الفرعية العاملة php التي تُصيِّر بالتوازي، ثمّ يجمع نتائجها ويتحقّق من سلامتها.

التزام دائم خالٍ من الآثار الجانبية

قسم بعنوان «التزام دائم خالٍ من الآثار الجانبية»

ينشر OutputCommitterInterface::commit() البايتات المُصيَّرة إلى وجهتها النهائية مرّة واحدة بالضبط: ذرّيًّا (لا يُلاحَظ كائن جزئي أبدًا)، وبخاصية العدم التأثيرية (إعادة الالتزام بمحتوى متطابق بايتيًا لا تنفّذ أي كتابة وتُرجِع CommitReceipt بقيمة idempotentReuse = true — إيصال جديد، لا الأصلي)، ومن دون طمس صامت (البايتات المتباعدة إلى مفتاح مشغول من دون overwrite تُثير تعارضًا)، ومُتحقَّقًا من سلامته (يعيد المُلتزِم حساب البصمة قبل الكتابة). ويُنفِّذ LocalFilesystemCommitter هذا لنظام الملفات المحلّي.

RunCheckpoint هي حاجز دائم يسجّل عدد العناصر التي التزم بها تشغيلٌ ما إضافةً إلى لقطة للحالة المُفتَّحة. وعند الاستعادة يتقدّم المُعالِج سريعًا متجاوزًا الإزاحة الملتزَم بها ويستعيد الحالة المُفتَّحة، فيُستأنَف العطل في منتصف التشغيل من دون إعادة نشر المخرَجات الملتزَم بها. ويُديم FilesystemCheckpointStore كلَّ حاجز ذرّيًّا.

إزالة التكرار بخاصية العدم التأثيرية، وإعادة المحاولة، والرسائل الميتة

قسم بعنوان «إزالة التكرار بخاصية العدم التأثيرية، وإعادة المحاولة، والرسائل الميتة»

مخزن خاصية العدم التأثيرية هو المسار السريع الذي يتيح للمُعالِج اختصار الدائرة قبل تصيير بيانات وصفية مُعاد تشغيلها؛ وتبقى مقارنة بصمة المُلتزِم هي ضمانة المرّة الواحدة بالضبط الدائمة، فيتسبّب فقدان سجلّ إزالة تكرار في أسوأ الأحوال بإعادة تصيير مهدورة يُزيل المُلتزِم تكرارها. وتوفّر RetryPolicy تراجعًا أُسّيًّا محدودًا وحتميًّا لحالات الفشل العابرة (انتهاء المهلة)؛ والمهمّة التي تستنفد ميزانيتها تُلتقَط في DeadLetterStoreInterface بدلًا من فقدانها. ويشحن كلُّ مخزن نسخةً في الذاكرة (نطاق تشغيل واحد / اختبار) ونسخةً دائمة على نظام الملفات.

تُنفِّذ المخازن التي تنجو حالتها من إعادة تشغيل عملية ما علامةَ DurableCapability. ويتطلّب التشغيل الآمن عند الأعطال أن يكون كلُّ متعاون دائمًا كي يفشل سريعًا بدلًا من الوعد بدلالات مرّة واحدة بالضبط لا يستطيع مخزن في الذاكرة الحفاظ عليها عبر إعادة تشغيل.

صيِّر بيانات وصفية واحدة والتزم ببايتاتها مرّة واحدة بالضبط. يُرجِع المحرّك البايتات إضافةً إلى بصمة؛ ويَنشرها المُلتزِم.

stream-quickstart.php
<?php
declare(strict_types=1);
use NextPDF\Manifest\OutputObjectKey;
use NextPDF\Manifest\Render\SingleDocumentRenderer;
use NextPDF\Manifest\RenderManifestBuilder;
use NextPDF\Manifest\TemplateRef;
use NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter;
use NextPDF\Pro\Stream\Engine\InProcessRenderEngine;
$outputRoot = __DIR__ . '/out';
\is_dir($outputRoot) || \mkdir($outputRoot, 0o775, true);
// The engine renders bytes only — it never writes the final object.
$engine = new InProcessRenderEngine(SingleDocumentRenderer::standalone());
$target = OutputObjectKey::file('out', 'invoices/1001.pdf');
$manifest = RenderManifestBuilder::create('invoice-1001')
->withInlineInput('<h1>Invoice 1001</h1><p>Amount due: 42.00</p>')
->withTemplate(TemplateRef::html())
->withOutputKey($target)
->build();
$result = $engine->renderBatch([$manifest])[0];
// A durable committer publishes the rendered bytes exactly once.
$committer = new LocalFilesystemCommitter($outputRoot);
if ($result->isRendered()) {
$receipt = $committer->commit($result->jobId, $target, $result->bytes, $result->sha256);
echo $receipt->target->toUri(), ' (', $receipt->bytesWritten, " bytes)\n";
}

صيِّر دفعة، ووجِّه حالات انتهاء المهلة إلى سياسة إعادة المحاولة، وأرسِل حالات الفشل النهائية إلى الرسائل الميتة. ويرفض الالتزام طمس البايتات المتباعدة، فيُلتقَط تصادم المفاتيح ويُحتجَز بدلًا من فقدانه.

stream-production.php
<?php
declare(strict_types=1);
use DateTimeImmutable;
use NextPDF\Manifest\OutputObjectKey;
use NextPDF\Manifest\Render\SingleDocumentRenderer;
use NextPDF\Manifest\RenderManifest;
use NextPDF\Manifest\RenderManifestBuilder;
use NextPDF\Manifest\TemplateRef;
use NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter;
use NextPDF\Pro\Stream\Engine\EngineRenderStatus;
use NextPDF\Pro\Stream\Engine\InProcessRenderEngine;
use NextPDF\Pro\Stream\Exception\OutputCommitConflictException;
use NextPDF\Pro\Stream\Retry\DeadLetterRecord;
use NextPDF\Pro\Stream\Retry\InMemoryDeadLetterStore;
use NextPDF\Pro\Stream\Retry\RetryPolicy;
$outputRoot = __DIR__ . '/out';
\is_dir($outputRoot) || \mkdir($outputRoot, 0o775, true);
$engine = new InProcessRenderEngine(SingleDocumentRenderer::standalone(), maxBatchSize: 64);
$committer = new LocalFilesystemCommitter($outputRoot);
$deadLetter = new InMemoryDeadLetterStore();
$retry = RetryPolicy::default(); // 3 attempts, 100ms base, 30s cap.
/**
* Build one manifest and remember its output target for the commit stage.
*
* @return array{RenderManifest, OutputObjectKey}
*/
$makeJob = static function (string $jobId, string $html): array {
$target = OutputObjectKey::file('out', 'invoices/' . $jobId . '.pdf');
$manifest = RenderManifestBuilder::create($jobId)
->withInlineInput($html)
->withTemplate(TemplateRef::html())
->withOutputKey($target)
->build();
return [$manifest, $target];
};
/** @var array<non-empty-string, OutputObjectKey> $targets */
$targets = [];
$manifests = [];
foreach (['inv-2001' => '<h1>2001</h1>', 'inv-2002' => '<h1>2002</h1>'] as $id => $html) {
[$manifest, $target] = $makeJob($id, $html);
$manifests[] = $manifest;
$targets[$id] = $target;
}
foreach ($engine->renderBatch($manifests) as $result) {
// A timeout is transient — the policy decides whether to re-enqueue it.
if ($result->status === EngineRenderStatus::Timeout && $retry->shouldRetry(1)) {
// Re-enqueue on the caller's work queue after delayMsForAttempt(1) ms.
continue;
}
if (!$result->isRendered()) {
$deadLetter->add(new DeadLetterRecord(
jobId: $result->jobId,
idempotencyKeyValue: $result->jobId,
attempts: $retry->maxAttempts,
lastErrorCode: $result->errorCode ?? 'SPEC-RENDER-EXCEPTION',
lastErrorMessage: $result->errorMessage ?? '',
failedAt: new DateTimeImmutable(),
));
continue;
}
try {
// overwrite=false: identical bytes are an idempotent no-op; divergent
// bytes to an occupied key raise SPEC-COMMIT-409 instead of clobbering.
$receipt = $committer->commit(
$result->jobId,
$targets[$result->jobId],
$result->bytes,
$result->sha256,
);
} catch (OutputCommitConflictException $e) {
$deadLetter->add(new DeadLetterRecord(
jobId: $result->jobId,
idempotencyKeyValue: $result->jobId,
attempts: 1,
lastErrorCode: $e->specCode(),
lastErrorMessage: $e->getMessage(),
failedAt: new DateTimeImmutable(),
));
continue;
}
echo $receipt->idempotentReuse
? "reused {$receipt->target->toUri()}\n"
: "committed {$receipt->target->toUri()}\n";
}
if ($deadLetter->count() > 0) {
\fwrite(\STDERR, $deadLetter->count() . " job(s) dead-lettered\n");
}
  • تصيير دفعات عالية الحجم حيث تستفيد الإنتاجية من التنفيذ المتزامن (مجمَّع العمليات).
  • عمليات تشغيل طويلة الأمد يجب أن تنجو من عطل وتُستأنَف من دون نشر مزدوج للمخرَجات.
  • خطوط أنابيب يجب أن تضمن تسليم كلِّ مستند مُصيَّر إلى هدفه مرّة واحدة بالضبط.

لمستند واحد عرَضي، صيِّر مباشرةً بوحدة ⁨Writer⁩؛ فقيمة ⁨Stream⁩ في الدفعات الدائمة القابلة للاستئناف والمتزامنة.

تتوسّع الإنتاجية مع عدد العمّال في ProcessPoolRenderUnitExecutor (محدودةً بـmaxWorkers وmaxBatchSize)، فيما يُبقي المحرّك مخرَج التصيير متطابقًا بايتيًا مع الخطّ المرجعي المتسلسل. وتحدّ مهلة زمن الحائط كلَّ دفعة موازية فلا يستطيع عامل مُعلَّق الحجبَ إلى الأبد. ولا يوجد رقم إنتاجية ثابت منشور؛ فهو يعتمد على تعقيد المستند وتوازي المضيف. قِس بمستندات تمثيلية.

يُتحقَّق من البيانات الوصفية تحقُّقًا مُغلَقًا بأمان قبل التصيير. ويرفض المُلتزِم اجتياز المسارات، والبايتات الصفرية، ومخطّطات ملفّ التدفّق، والأهداف ذات الروابط الرمزية، ومتّجهات تدفّق البيانات البديل (النقطتان) في ⁨NTFS⁩، ويحلّ كلَّ مفتاح تحت جذر واحد مُهيَّأ. وتُعاد بصمة نتائج العمّال عبر العمليات ومطابقتها مع البصمة التي يُبلِّغ بها العامل، فلا يستطيع عامل مُشوَّش إفساد المخرَج بصمت. ولا تُسجِّل هذه الوحدة أيَّ محتوى مستند.

مخازن ⁨Stream⁩ الدائمة هنا مدعومة بنظام الملفات وأحادية المضيف. أمّا المرّة الواحدة بالضبط المتزامنة عبر مضيفين متعدّدين إلى المفتاح نفسه، وإزالة التكرار الدائمة عبر عمليات التشغيل، فهما مهمّة مُلتزِمات ومخازن تخزين الكائنات في ⁨Enterprise⁩؛ ومُعالِج تدفّق مهامّ المستندات الذي يقود هؤلاء المتعاونين شأنٌ من شؤون ⁨Enterprise⁩. ويوفّر ⁨Pro⁩ المحرّك، والعقود، والتنفيذات الدائمة المحلّية.

من دون ⁨Pro⁩، صيِّر المستندات واحدًا تلو الآخر بكاتب ⁨NextPDF Core⁩؛ وتدفّق الدفعات الدائم، والتنفيذ المتزامن، والالتزام مرّة واحدة بالضبط إضافاتٌ من ⁨Pro⁩. راجِع /modules/writer/.

تُوثِّق هذه الصفحة السلوك المُلاحَظ خارجيًّا وسطح واجهة البرمجة العامّة المدعومة فقط. أمّا مسارات النطاقات الاسمية الداخلية، والأصناف المساعِدة، وجداول الآليات، وأسماء ملفّات كتيّبات التشغيل، وبادئات التذاكر فخارج النطاق.