تنفيذ أنماط مخصصة لاستعادة الأخطاء وإعادة المحاولة
لمحة سريعة
قسم بعنوان «لمحة سريعة»تتجاوز خدمة المستندات الإنتاجية مجرد التقاط الاستثناء وتسجيله. فهي تحدد ما الذي ينبغي فعله بعد ذلك: المتابعة بمخرجات متدهورة، أو التبديل إلى مسار تصيير ثانٍ، أو إعادة المحاولة بمدخلات يقبلها المحرك، أو تسليم الصفحات التي بُنيت قبل الفشل. تعرض هذه الوصفة أربع استراتيجيات استعادة مبنية على تسلسل استثناءات NextPDF الهرمي وعلى توابع فحص حالة المستند:
- التدهور بسلاسة عند فشل خط — التقط
NextPDF\Exception\FontNotFoundException، وتراجع إلى محرف مضمون، وتابع بناء المستند. - عارض احتياطي — عندما يرفض مسار
Document::writeHtml()داخل العملية المدخلات، أعد المحاولة عبرDocument::writeHtmlChrome()، وهو جسر Chrome فيnextpdf/artisan. - إعادة المحاولة بـ HTML بديلة — عند حدوث
NextPDF\Exception\HtmlParsingExceptionأوNextPDF\Exception\CssResolutionBudgetExceededException، أعد المحاولة بنسخة HTML مبسّطة ومعروفة الصلاحية. - استعادة المستند الجزئي — اقرأ
Document::getNumPages()بعد الفشل، واحفظ ما بُني فعليًا بدلًا من التخلص منه.
أنت تعرف بالفعل كيف تلتقط الاستثناء عند المستوى الصحيح. تتناول الصفحة المرافقة التعامل مع الأخطاء باستخدام تسلسل استثناءات NextPDF الهرمي التسلسل الهرمي نفسه. تعرض هذه الصفحة ما ينبغي فعله بعد الالتقاط.
تستهدف هذه الوصفة إصدار النواة مفتوح المصدر (OSS). كل واجهة برمجة تطبيقات (API) مذكورة هنا موجودة في nextpdf/core. الاعتمادية الاختيارية الوحيدة هي nextpdf/artisan لاستخدام احتياطي Chrome.
التثبيت
قسم بعنوان «التثبيت»composer require nextpdf/core:^3تستخدم استراتيجية العارض الاحتياطي أيضًا جسر Chrome:
composer require nextpdf/artisanعند غياب nextpdf/artisan، يطرح Document::writeHtmlChrome() الاستثناء NextPDF\Exception\PageLayoutException بدلًا من التصيير. تتعامل استراتيجية الاحتياطي أدناه مع غياب الجسر بوصفه حالة أخرى قابلة للاستعادة.
نظرة مفاهيمية عامة
قسم بعنوان «نظرة مفاهيمية عامة»تعتمد الاستعادة على حقيقتين بشأن NextPDF، وكلتاهما متحقَّق منهما مقابل الشيفرة المصدرية.
يخبرك التسلسل الهرمي للاستثناءات بما يمكن استعادته. يمتد كل استثناء مجال من القاعدة المجردة NextPDF\Exception\NextPdfException، التي تمتد من RuntimeException وتنفّذ NextPDF\Contracts\ContextAwareExceptionInterface. التقط نوعًا فرعيًا محددًا لاختيار مسار استعادة مناسب لذلك الفشل:
- يحمل
FontNotFoundExceptionالتوابعgetFontName()وgetSearchPaths()وwasFallbackAttempted()— وهذا يكفي لإعادة المحاولة بمحرف مختلف. - يحمل
HtmlParsingExceptionالتوابعgetRule()وgetPosition()وgetHtmlSnippet()— وهذا يكفي لتقرير ما إذا كانت إعادة المحاولة المبسّطة تستحق التنفيذ. - يحمل
CssResolutionBudgetExceededExceptionالتوابعgetVisits()وgetBudget()— وهذه إشارة إلى أن صفحة أنماط مجردة قد تتجاوز محدِّدًا مَرَضيًا. - حدّ مهم واحد: يمتد
NextPDF\Support\DegradedExceptionمنRuntimeExceptionمباشرةً، لا منNextPdfException. لذلك لا يلتقطcatch (NextPdfException $e)رفضًا صادرًا عن سياسة التدهور. عندما تكونNextPDF\Contracts\DegradationPolicyالنشطةStrictأوBalanced، التقطDegradedExceptionصراحةً للتعافي منه.
يمكن فحص المستند أثناء بنائه. يكشف Document حالة بنائه عبر ملحقات للقراءة فقط. يعرض getNumPages() العدد الإجمالي للصفحات، بما في ذلك الصفحة النشطة غير المُفرَّغة، ويعيد getPage() الفهرس المبدوء من الصفر للصفحة الحالية. بعد فشل يحدث في منتصف البناء، اقرأ getNumPages() لمعرفة ما إذا كانت توجد أي صفحات كاملة، ثم استدعِ save() أو getPdfData() لإصدارها. يسجّل المحرك أيضًا أحداث تدهور غير قاتلة: يعيد getWarnings() قائمة من نوع list<NextPDF\Support\Warning>، ويبلّغ hasWarnings() عمّا إذا جُمِع أيٌّ منها، ويبلّغ hasDegradedParity() عمّا إذا تأثرت دقة المخرجات. تتيح هذه التوابع لروتين الاستعادة التمييز بين “نجح بنظافة” و”نجح بدقة منخفضة” دون تحليل استثناء.
تتحكم سياسة التدهور في الأحداث التي تعالجها بوصفها استثناءات، وفي الأحداث التي تعالجها بوصفها تحذيرات. يستخدم NextPDF\Core\Config افتراضيًا DegradationPolicy::Balanced، وهي سياسة تحذّر وتتابع عند التدهور المحدود، لكنها تطرح استثناءً عند وجود تأثير حاجب. لا تطرح DegradationPolicy::Permissive أبدًا، بل تجمع كل شيء في قناة التحذيرات. تطرح DegradationPolicy::Strict استثناءً عند أي خطر امتثال أو فقدان دلالي أو تأثير حاجب. اختر السياسة أولًا، ثم اكتب الاستعادة لأشكال الفشل التي تنتجها تلك السياسة.
سطح واجهة برمجة التطبيقات
قسم بعنوان «سطح واجهة برمجة التطبيقات»تستخدم شيفرة الاستعادة أدناه الأعضاء المتحقَّق منها التالية:
NextPDF\Core\Document::createStandalone(?Config $config = null): self,addPage(),setFont(string $family, string $style = '', float $size = 12.0): static,cell(...),writeHtml(string $html): static,writeHtmlChrome(string $html, ?float $width = null, ?float $height = null): static,save(string $path): void,getPdfData(): string,getNumPages(): int,getPage(): int,getWarnings(): list<Warning>,hasWarnings(): bool,hasDegradedParity(): bool,addFontDirectory(string $directory): static.NextPDF\Core\Config::withDegradationPolicy(DegradationPolicy $policy): selfوالقيمة الافتراضيةdegradationPolicyوهيDegradationPolicy::Balanced.NextPDF\Contracts\DegradationPolicy—Strict،Balanced،Permissive.NextPDF\Exception\NextPdfException(القاعدة المجردة)،NextPDF\Exception\FontNotFoundException،NextPDF\Exception\HtmlParsingException،NextPDF\Exception\CssResolutionBudgetExceededException،NextPDF\Exception\WriterException،NextPDF\Exception\PageLayoutException.NextPDF\Support\DegradedException(يحملcapabilityوpolicy)،NextPDF\Support\Capability(id،status،reason،isDegraded())،NextPDF\Support\Warning،NextPDF\Support\WarningSeverity.
مثال شيفرة — بداية سريعة
قسم بعنوان «مثال شيفرة — بداية سريعة»تلتقط أصغر استعادة مفيدة فشل خط مفقود، وتتراجع إلى محرف مضمون، ثم تتابع. يحذف هذا المقطع المعالجة الأوسع الموجودة في المثال الإنتاجي. للحصول على معالِج كامل يتضمن التسجيل وحدّ DegradedException، اقرأ المثال الإنتاجي أدناه.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Exception\FontNotFoundException;
$doc = Document::createStandalone();$doc->addPage();
try { // A face that may not be installed on every host. $doc->setFont('CorporateSans', '', 12);} catch (FontNotFoundException $e) { // Recover: fall back to a face the engine always resolves. $doc->setFont('helvetica', '', 12);}
$doc->cell(0, 10, 'Rendered with a recovered font.', newLine: true);$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');مثال شيفرة — إنتاجي
قسم بعنوان «مثال شيفرة — إنتاجي»يربط المثال الكامل الاستراتيجيات الأربع كلها في خط تصيير واحد: احتياطي خط، واحتياطي عارض من المسار داخل العملية إلى Chrome، وإعادة محاولة بـ HTML بديلة، واستعادة مستند جزئي اعتمادًا على getNumPages(). كما يحترم قناة مخرجات أداة التشغيل، ولا يلتقط أبدًا Exception عاريًا، ولا يترك كتلة catch فارغة.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Contracts\ContextAwareExceptionInterface;use NextPDF\Contracts\DegradationPolicy;use NextPDF\Core\Config;use NextPDF\Core\Document;use NextPDF\Exception\CssResolutionBudgetExceededException;use NextPDF\Exception\FontNotFoundException;use NextPDF\Exception\HtmlParsingException;use NextPDF\Exception\NextPdfException;use NextPDF\Exception\PageLayoutException;use NextPDF\Exception\WriterException;use NextPDF\Support\DegradedException;
/** * A minimal structured sink. In production this is your PSR-3 logger; the * exception class and its structured context become log fields. * * @param array<string, mixed> $context */function logRecovery(string $message, array $context): void{ fwrite(STDERR, $message . ' ' . json_encode($context, JSON_THROW_ON_ERROR) . "\n");}
/** * Resolve a usable font, degrading from the requested face to a guaranteed * fallback. Returns the face actually applied so the caller can record it. * * @param non-empty-string $requested * @param non-empty-string $fallback * * @return non-empty-string */function applyFontWithFallback(Document $doc, string $requested, string $fallback): string{ try { $doc->setFont($requested, '', 12);
return $requested; } catch (FontNotFoundException $e) { // STRATEGY 1 — graceful degradation on a font failure. logRecovery('Font unavailable; degrading to a guaranteed face', [ 'exception' => $e::class, 'font_name' => $e->getFontName(), 'searched' => $e->getSearchPaths(), 'fallback' => $fallback, ]); $doc->setFont($fallback, '', 12);
return $fallback; }}
/** * Render HTML through the in-process pipeline, then through the Chrome bridge, * then through a simplified HTML variant. Each layer recovers a more specific * failure than the last. */function renderHtmlWithRecovery(Document $doc, string $primaryHtml, string $simplifiedHtml): void{ try { // Primary path: the in-process HTML/CSS pipeline. $doc->writeHtml($primaryHtml);
return; } catch (CssResolutionBudgetExceededException $e) { // STRATEGY 3 — retry with alternative HTML for a pathological selector. logRecovery('CSS resolution budget exceeded; retrying with simplified HTML', [ 'exception' => $e::class, 'visits' => $e->getVisits(), 'budget' => $e->getBudget(), ]); $doc->writeHtml($simplifiedHtml);
return; } catch (HtmlParsingException $e) { // STRATEGY 2 — fall back to the Chrome renderer for input the // in-process parser rejects. The Chrome bridge uses a browser CSS // engine, so it may accept what the in-process parser would not. logRecovery('In-process HTML parse failed; trying the Chrome fallback renderer', [ 'exception' => $e::class, 'rule' => $e->getRule(), 'position' => $e->getPosition(), ]);
try { $doc->writeHtmlChrome($primaryHtml);
return; } catch (PageLayoutException $chromeError) { // The Chrome bridge is absent (nextpdf/artisan not installed) or // rejected the input. Last resort: the simplified HTML variant // through the in-process pipeline. logRecovery('Chrome fallback unavailable; retrying with simplified HTML', [ 'exception' => $chromeError::class, ]); $doc->writeHtml($simplifiedHtml);
return; } }}
// --- Configure the degradation policy up front ---------------------------// Balanced (the default) warns on bounded degradation and throws only on a// blocking impact. A regulated workflow would choose DegradationPolicy::Strict.$config = (new Config())->withDegradationPolicy(DegradationPolicy::Balanced);$doc = Document::createStandalone($config);
$primaryHtml = '<h1>Quarterly report</h1><p>Body paragraph with rich styling.</p>';$simplifiedHtml = '<h1>Quarterly report</h1><p>Body paragraph (simplified).</p>';
$outputPath = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf';
try { $doc->addPage(); $applied = applyFontWithFallback($doc, 'CorporateSans', 'helvetica'); $doc->cell(0, 12, 'Custom error recovery patterns', newLine: true);
renderHtmlWithRecovery($doc, $primaryHtml, $simplifiedHtml);
$doc->save($outputPath);
logRecovery('Document built', [ 'font_applied' => $applied, 'pages' => $doc->getNumPages(), 'has_warnings' => $doc->hasWarnings(), 'degraded_parity' => $doc->hasDegradedParity(), ]);} catch (DegradedException $e) { // BOUNDARY: DegradedException extends RuntimeException directly, NOT // NextPdfException, so the catch-all below would not have caught it. // Under Strict/Balanced policy a blocking degradation lands here. logRecovery('Capability degraded under the active policy; emitting a built partial', [ 'exception' => $e::class, 'capability' => $e->capability->id, 'status' => $e->capability->status->value, 'reason' => $e->capability->reason ?? 'unknown', 'policy' => $e->policy->value, ]); // STRATEGY 4 — partial-document recovery: save whatever pages exist. if ($doc->getNumPages() > 0) { $doc->save($outputPath); }} catch (WriterException $e) { // Serialization or I/O failure: the in-memory document is valid but could // not be written. Surface the stage so infrastructure can act on it. logRecovery('PDF write failed; document was valid in memory', [ 'exception' => $e::class, 'writer_state' => $e->getWriterState(), 'output_path' => $e->getOutputPath(), ]);} catch (NextPdfException $e) { // Catch-all for every other NextPDF\Exception\*. STRATEGY 4 again: if any // complete pages were built before the failure, emit them rather than // discarding the work. $context = ['exception' => $e::class, 'pages' => $doc->getNumPages()]; if ($e instanceof ContextAwareExceptionInterface) { $context += $e->getContext(); } logRecovery('Unrecovered NextPDF failure; attempting a partial save', $context); if ($doc->getNumPages() > 0) { $doc->save($outputPath); }}
fwrite(STDERR, "Recovery pipeline complete.\n");اترك STDOUT حرًا لأداة التشغيل. تذهب تشخيصات الاستعادة إلى STDERR، ويُكتب ملف صيغة المستندات المحمولة (PDF) إلى NEXTPDF_COOKBOOK_OUTPUT فقط.
الحالات الحدّية والمزالق
قسم بعنوان «الحالات الحدّية والمزالق»- رتّب كتل الالتقاط من الأخص إلى الأعم. تطابق PHP أول
catchمتوافقة. يؤدي وضعcatch (NextPdfException $e)قبلcatch (WriterException $e)إلى تحويل الكتلة المحددة إلى شيفرة ميتة، لأنWriterExceptionيمتد منNextPdfException. DegradedExceptionيقع خارج التسلسل الهرمي. فهو يمتد منRuntimeException، لا منNextPdfException. خط معالجة يلتقطNextPdfExceptionفقط يترك رفضًا صادرًا عن سياسة صارمة ينتشر دون التقاط. التقطDegradedException(أوRuntimeExceptionأوسع) عندما تكون سياسة تدهور غير افتراضية نشطة.- يمكن أن يفشل احتياطي الخط أيضًا. إذا كان المحرف الاحتياطي نفسه غير مسجَّل، فإن استدعاء
setFont()الثاني يطرح استثناءً من جديد. استخدم اسمًا مستعارًا من Base14 مثلhelvetica، الذي يحلّه المحرك دون البحث في نظام الملفات، أو سجّل محرفًا مرفقًا عبرaddFontDirectory()عند بدء التشغيل ليكون الاحتياطي مضمونًا. getNumPages()يحسب الصفحة النشطة غير المُفرَّغة. فهو يعيد عدد الصفحات المُفرَّغة زائد واحد عندما تكون هناك صفحة مفتوحة حاليًا. يتضمن “الحفظ الجزئي” الصفحة التي كانت تُبنى عند وقوع الفشل، وهذا غالبًا ما تريده. إذا كنت تحتاج إلى الصفحات المكتملة تمامًا فقط، فتفرّع بناءً علىgetPage()أيضًا.- يغيّر احتياطي Chrome الدقة، لا التوافر فقط. يستخدم المسار داخل العملية وجسر Chrome محركَي تخطيط مختلفين، لذلك قد يبدو المستند الذي يتراجع إلى Chrome مختلفًا. تعامل مع الاحتياطي بوصفه استعادة، لا بديلًا شفافًا، وسجّل أي مسار أنتج المخرجات.
- يجب أن تستخدم إعادة المحاولة مدخلات معروفة الصلاحية. لا تنفع إعادة المحاولة بـ HTML المبسّطة إلا عندما تكون النسخة المبسّطة أبسط فعليًا: محددات متداخلة أقل، ولا سلاسل
:has()تستنزف ميزانية الحل. إعادة المحاولة بالمدخلات نفسها التي فشلت بالفعل تعود إلى الاستثناء نفسه. - افحص التحذيرات بعد التشغيل النظيف. قد يكون التصيير الذي يعود دون طرح استثناء قد تدهور رغم ذلك. تحقق من
hasDegradedParity()واقرأgetWarnings()قبل أن تعامل المخرجات بوصفها مطابقة بدقة البكسل؛ ففي ظلDegradationPolicy::Permissiveيكون كل تدهور تحذيرًا، لا استثناءً أبدًا.
الأداء
قسم بعنوان «الأداء»- لا تضيف الاستعادة تكلفة إلا على مسار الفشل. يطرح NextPDF الاستثناءات في الحالات الاستثنائية، لذلك لا يدفع التصيير النظيف شيئًا مقابل إحاطتي
try/catch. - يعيد احتياطي العارض تشغيل التصيير. تُتجاهل المحاولة داخل العملية وتبدأ محاولة Chrome من جديد، لذلك تكلّف عملية التصيير الاحتياطية، في أسوأ الحالات، زمنَي التصيير كليهما زائد رحلة الذهاب والإياب بين العمليات إلى Chrome. احسب لها حسابًا عند ضبط مهل الطلبات.
- تحلّل إعادة المحاولة بـ HTML بديلة مستندًا ثانيًا. أبقِ النسخة المبسّطة صغيرة لتكون إعادة المحاولة قليلة التكلفة مقارنة بالمحاولة الأساسية.
- يسلسل الحفظ الجزئي الصفحات التي بُنيت بالفعل. تتناسب تكلفته مع عدد الصفحات الباقية، لا مع العمل الذي فشل.
ملاحظات أمنية
قسم بعنوان «ملاحظات أمنية»- لا تعرض رسائل استثناءات خامًا أو مسارات نظام ملفات للمستخدمين النهائيين. تتضمن رسالة
FontNotFoundExceptionالأدلة التي جرى البحث فيها، ويتضمنWriterExceptionمسار المخرجات؛ وكلاهما يسرّب بنية الخادم. سجّل السياق المُهيكل من جانب الخادم، وأعد رسالة عامة إلى المستدعي. - تعامل مع HTML المُعاد محاولتها بوصفها مدخلات غير موثوقة في كل محاولة. يتدفق كلٌّ من الاحتياطي وإعادة المحاولة بـ HTML المبسّطة عبر حدّ المدخلات نفسه؛ ويطبّق كل من المسار داخل العملية وجسر Chrome سياسته الأمنية الخاصة بـ HTML، ولا تخفّف إعادة المحاولة ذلك التحقق. لا تفترض أن النسخة “المبسّطة” أكثر أمانًا لأنك أنت من أنشأها.
- يظل الحفظ الجزئي عملية كتابة ملف. طبّق على المخرجات الجزئية القواعد نفسها المتعلقة بالتحقق من المسار والصلاحيات وموقع التخزين التي تطبّقها على المخرجات الكاملة. يرفض
Document::save()أغلفة التدفق والبايتات الصفرية ويحلّ الدليل الأب لمنع اجتياز المسار، لكن الوجهة التي تمررها هي مسؤوليتك.
المطابقة
قسم بعنوان «المطابقة»لا تقدّم هذه الوصفة أي ادعاء معياري بشأن المعايير. فهي تركّب واجهات NextPDF العامة للاستثناءات وفحص المستندات ضمن تدفق تحكم للاستعادة؛ ولا تؤكد سلوكًا محددًا في ISO 32000-2 أو أي معيار آخر، لذلك لا تحمل أي كتلة citations:.
تُتحقَّق هذه الصفحة بملف تعريف إعادة الإنتاج الدلالي. يحمل المستند المستعاد في الذيل /ID وتاريخ تعديل يُعاد توليدهما عند كل حفظ، لذلك تتعذر المطابقة على مستوى البايت. مقارنة شجرة البناء التركيبية المجردة (AST) مع البيانات الوصفية فقط تبقى مستقرة عبر عمليات التشغيل.
انظر أيضًا
قسم بعنوان «انظر أيضًا»- التعامل مع الأخطاء باستخدام تسلسل استثناءات NextPDF الهرمي — دقة الالتقاط والسياق المُهيكل، وهما الأساس الذي تبني عليه هذه الصفحة.
- وحدة الاستثناءات — المرجع الكامل للاستثناءات.
- وحدة الدعم —
DegradedException،Capability،Warning، وأنواع التدهور. - وحدة الإعداد — إعداد سياسة التدهور.
- تصيير ملفات PDF بأمان في عامل طويل التشغيل — الاستعادة في عامل يعيد استخدام سجلات مشتركة.