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

معالجة الأخطاء وفق التسلسل الهرمي للاستثناءات في NextPDF

يطرح ⁨NextPDF⁩ استثناءات محدَّدة النوع عند وقوع حالات استثنائية، ولا يُخفي أي خطأ خلف قيمة مُعادة false أو null. يرث كل استثناء خاص بالنطاق القاعدة المجردة نفسها، NextPdfException، ويُوفّر سياقًا تشخيصيًا منظَّمًا عبر ContextAwareExceptionInterface. يوضّح هذا الدليل مواضع التقاط الاستثناءات، وكيفية تسجيل السياق المنظَّم في مسار مراقبة أداء التطبيقات (⁨APM⁩). كما يبيّن الإخفاقات التي لا تغطيها كتلة الالتقاط الشاملة الواحدة.

Terminal window
composer require nextpdf/core:^3

لا تحتاج إلى أي امتداد إضافي.

يكون التسلسل الهرمي على النحو الآتي:

RuntimeException
└── NextPdfException (abstract, implements ContextAwareExceptionInterface)
├── InvalidConfigException
├── FontNotFoundException
├── FontParsingException
├── ImageProcessingException
├── WriterException
├── SignatureException
├── EncryptionException
├── HtmlParsingException
├── … (every domain exception under NextPDF\Exception)
└── Strict\StrictModeViolation (abstract)
├── Strict\IncompatibleRenderingModeException
└── Strict\OracleConformanceFailure

تترتب على هذا التسلسل الهرمي نتيجتان عمليتان، وقد جرى التحقق من كلتيهما بمقارنة الكود المصدري:

  1. يلتقط catch (NextPdfException $e) كل استثناء ضمن NextPDF\Exception، بما في ذلك انتهاكات الوضع الصارم. فكلها ترث القاعدة المجردة.
  2. لا يلتقط كل ما قد تطرحه المكتبة. يرث NextPDF\Support\DegradedException مباشرةً من RuntimeException، وليس من NextPdfException. لذلك فإن catch (NextPdfException $e) لا يلتقط رفض سياسة التدهور. للتعامل معه، التقط DegradedException (أو RuntimeException الأوسع) صراحةً. يوضح هذا الدليل ذلك الحد بدلًا من افتراض أن كتلة التقاط شاملة واحدة توفر تغطية كاملة.

يعيد NextPdfException::getContext() قيمة array<string, mixed> بمفاتيح بنمط ⁨snake_case⁩ وقيم أولية فقط، أو قوائم من القيم الأولية. يمكنك تسلسلها مباشرةً ضمن مصفوفة السياق الخاصة بمُسجِّل ⁨PSR-3.⁩ يضع ⁨PSR-3⁩ §1.3 الاستثناء تحت مفتاح السياق 'exception'. يضيف getContext() في ⁨NextPDF⁩ تفاصيل نطاقية بجوار ذلك المفتاح، لا كائن الاستثناء نفسه.

تستند واجهة ⁨API⁩ هذه إلى توثيق ⁨PHPDoc⁩ في NextPDF\Exception\NextPdfException، وNextPDF\Contracts\ContextAwareExceptionInterface، والاستثناءات النطاقية الملموسة (مثل NextPDF\Exception\FontNotFoundException، مع getFontName() / getSearchPaths() / wasFallbackAttempted())، وNextPDF\Support\DegradedException (الذي يحمل Capability وDegradationPolicy). تستخدم الأمثلة أدناه NextPdfException::getContext() ودوال الوصول الخاصة بكل استثناء.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Exception\NextPdfException;
try {
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'Hello');
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');
} catch (NextPdfException $e) {
// Every NextPDF\Exception\* (and strict-mode violation) lands here.
// $e->getContext() is APM-safe structured detail.
error_log($e->getMessage());
}

يعرض المثال الكامل كتل الالتقاط الدقيقة، وتسجيل السياق المنظَّم، والحدّ الذي تمثله DegradedException. ويحافظ أيضًا على قناة إخراج بيئة التشغيل.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Contracts\ContextAwareExceptionInterface;
use NextPDF\Exception\FontNotFoundException;
use NextPDF\Exception\NextPdfException;
use NextPDF\Support\DegradedException;
/**
* A minimal PSR-3-shaped sink. In production this is your real logger;
* the exception goes under the 'exception' key (PSR-3 §1.3) and the
* NextPDF structured context is merged in as domain detail.
*
* @param array<string, mixed> $context
*/
function logError(string $message, array $context): void
{
fwrite(STDERR, $message . ' ' . json_encode($context, JSON_THROW_ON_ERROR) . "\n");
}
$doc = Document::createStandalone();
$doc->setTitle('Exception handling patterns');
try {
$doc->addPage();
$doc->setFont('helvetica', 'B', 16);
$doc->cell(0, 12, 'Exception-aware error handling', newLine: true);
// This call succeeds; the catch blocks below show the SHAPE of handling.
$doc->setFont('helvetica', '', 11);
$doc->cell(0, 8, 'Catch specifically, then fall back to the base.', newLine: true);
} catch (FontNotFoundException $e) {
// Most specific first: actionable, typed accessors.
logError('Font missing — using a fallback face', [
'exception' => $e::class,
'font_name' => $e->getFontName(),
'searched' => $e->getSearchPaths(),
'fallback' => $e->wasFallbackAttempted(),
]);
} catch (NextPdfException $e) {
// Catch-all for every NextPDF\Exception\* including strict violations.
$context = ['exception' => $e::class];
if ($e instanceof ContextAwareExceptionInterface) {
$context += $e->getContext();
}
logError($e->getMessage(), $context);
} catch (DegradedException $e) {
// BOUNDARY: DegradedException extends RuntimeException directly, NOT
// NextPdfException. The catch above would NOT have caught it. This
// explicit block (or a broader RuntimeException) is required.
logError('Capability degraded under the active policy', [
'exception' => $e::class,
'capability' => $e->capability->id,
'policy' => $e->policy->value,
]);
}
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');
fwrite(STDERR, "Document built; handlers wired.\n");

يبقى STDOUT متاحًا لبيئة التشغيل، ولا يُكتب ملف ⁨PDF⁩ إلا إلى NEXTPDF_COOKBOOK_OUTPUT.

  • رتّب كتل الالتقاط من الأخص إلى الأعم. تطابق ⁨PHP⁩ أول كتلة catch متوافقة. إذا وضعت catch (NextPdfException $e) قبل catch (FontNotFoundException $e) فستجعل الكتلة الأخص كودًا ميتًا.
  • DegradedException ليس من نوع NextPdfException. وفقًا للتحقق من الكود المصدري، فهو يرث RuntimeException. إذا استُخدم catch (NextPdfException $e) وحده، فسيترك رفض التدهور الصارم ينتشر. التقطه (أو RuntimeException) صراحةً عندما تكون سياسة التدهور قيد العمل.
  • getContext() آمن لأنظمة ⁨APM⁩ بموجب العقد. المفاتيح بنمط ⁨snake_case.⁩ والقيم أولية أو قوائم من القيم الأولية، من دون كائنات متداخلة أو موارد. يمكنك تسلسلها مباشرةً، ولا يحتوي السياق أبدًا على بايتات المستند.
  • لا تُحلِّل رسائل الاستثناءات. الرسائل موجّهة للبشر وقابلة للتغيير. استخدم دوال الوصول المُحدَّدة النوع (getFontName()، وcapability->id، وما شابه) وgetContext() بوصفها الواجهة الآلية المستقرة.
  • تنبيه بشأن الأرقام القديمة. قد تستشهد المواد الأقدم بعدد ثابت من “استثناءات النطاق ⁨N⁩”. يتسع التسلسل الهرمي عبر الإصدارات. اعتمد على النوع الأساسي NextPdfException وعلى instanceof، ولا تعتمد أبدًا على عدد ثابت مُضمَّن في الكود.
  • أبقِ عناصر ⁨PSR-3⁩ النائبة ضمن سلاسل نصية. عند التسجيل، أبقِ الرسالة سلسلة نصية تحتوي رموز {placeholder}، وضع القيم في مصفوفة السياق (⁨PSR-3⁩ §1.2). لا تُدرِج كائن الاستثناء داخل الرسالة.

لا تضيف معالجة الاستثناءات أي تكلفة في الحالة الطبيعية. لا يطرح ⁨NextPDF⁩ استثناءات إلا في الحالات الاستثنائية، ويبني getContext() مصفوفة صغيرة عند الطلب. تنطبق ميزانية performance_budget (wall_ms: 2000، peak_mb: 96) على تشغيل بيئة هذا الدليل، لا على المستندات العشوائية.

  • صُمِّم getContext() ليكون آمنًا للتسجيل: قيم أولية فقط، من دون حمولة المستند أو بايتات الملف. ومع ذلك، تظل مسؤولًا عن القيم التي تضيفها إلى سياق السجل. نقِّ أي قيمة يقدّمها المستخدم (مثل مسار ملف) وفق سياسة التسجيل لديك قبل أن تصل إلى مَصرِف السجل.
  • لا تَعرِض رسائل الاستثناءات الخام للمستخدمين النهائيين بطريقة تكشف بنية نظام الملفات. اعرض رسالة عامة، وسجِّل السياق المنظَّم من جهة الخادم.
العبارةالمواصفةالبند⁨reference_id⁩
يُدرَج الاستثناء في سياق سجل ⁨PSR-3⁩ تحت المفتاح exception.⁨PSR-3⁩§1.3
تبقى رسائل السجل سلاسل نصية، وتطابق أسماء العناصر النائبة مفاتيح السياق.⁨PSR-3⁩§1.2
يُعاد توليد تاريخ التعديل مع كل عملية حفظ، لذا فإن المُخرَج مستقر بنيويًا (لا بحسب البايتات).⁨ISO 32000-2⁩§14.3

يُتحقَّق من هذا الدليل باستخدام ملف تعريف قابلية إعادة الإنتاج البنيوي. يحمل المُخرَج في المُذيَّل قيمة /ID وتاريخ تعديل يُعاد توليدهما مع كل عملية حفظ، لذلك يتعذّر تحقيق تطابق على مستوى البايتات. تبقى البنية المُطبَّعة بأداة ⁨qpdf⁩ مستقرة.