الأخطاء بوصفها ميزة
Spec: ISO 9241-110, §5.6.4 ISO 9241-110 §5.6.4 Evidence: Code-backed
لمحة سريعة
قسم بعنوان «لمحة سريعة»يتعامل NextPDF مع التسلسل الهرمي لاستثناءاته بوصفه سطح API، مُصمَّمًا بالعناية نفسها التي تُصمَّم بها الطرائق التي تطلقه. تكون حالة الفشل محددة، ومُصنَّفة النوع، وقابلة للالتقاط عند مستوى الدقة الذي تحتاجه، وتحمل سياقًا مُهيكلًا لسجلاتك.
تعرض هذه الصفحة ذلك السطح كما يظهر في المصدر الفعلي للمحرك: النوع الأساس، والأنواع الفرعية المُصنَّفة النوع، والمنشئات المُسمَّاة التي تربط السبب الجذري بالرسالة، والسياق المُهيكل الذي يكشفه كل استثناء في NextPDF.
لماذا يهم هذا
قسم بعنوان «لماذا يهم هذا»رسالة الخطأ هي صوت المحرك حين يخاطبك في أسوأ لحظة ممكنة: بيئة الإنتاج، الساعة 2 a.m.، ومستند كان ينبغي أن يُسلَّم. وما تقوله تلك الرسالة عندئذٍ هو ما يحدد هل تكون الخطوة التالية إصلاحًا أم تحقيقًا طويلًا.
إنّ RuntimeException: something went wrong العام لا يترك لك مسارًا واضحًا. يخبرك أنّ المحرك قد فشل، لكنه لا يخبرك بما فشل، ولا أين فشل، وبالتأكيد لا يخبرك بما ينبغي فعله. إرشادات العوامل البشرية صريحة في هذا الشأن. ينبغي أن يشرح الخطأ نفسه بما يكفي ليجعل إصلاحه الخطوة التالية البديهية، لا مشروع بحث ( Spec: ISO 9241-110, §5.6.4.3 ISO 9241-110 §5.6.4.3 ). الاستثناء الذي يسمّي السبب والعلاج ليس ترفًا. إنه الفرق بين إصلاح يستغرق خمس دقائق وإصلاح يستغرق خمس ساعات.
النسخة المختصرة
قسم بعنوان «النسخة المختصرة»- تمتد كل حالة فشل في NextPDF من نوع أساس مجرد واحد،
NextPdfException، لذا يمكنك التقاط جميع أخطاء المكتبة بنوع واحد. - وتحته توجد أنواع فرعية محددة ومُصنَّفة النوع — خط لا يمكن العثور عليه، أو تهيئة غير صالحة، أو عملية توقيع فشلت — لتلتقط حالة الفشل التي يمكنك معالجتها بالضبط.
- يطبّق كل استثناء في NextPDF واجهة
ContextAwareExceptionInterfaceويكشفgetContext(): خريطة مُهيكلة آمنة للتسجيل، فلا تحلل أبدًا سلسلة رسالة لاستعادة التشخيصات. - الرسائل قابلة للتنفيذ: تربط المنشئات المُسمَّاة السبب الجذري الفعلي (وغالبًا الإصلاح) بالرسالة، بدلًا من الاكتفاء بقالب عام.
- يوثّق كل صنف استثناء من يمكنه التصرف بشأنه — المطور، أو البنية التحتية، أو مُستدعي المكتبة — فيبدأ الفرز قبل أن تقرأ تتبُّع المكدس.
كيف يتعامل NextPDF مع ذلك
قسم بعنوان «كيف يتعامل NextPDF مع ذلك»التسلسل الهرمي ضحل عن قصد. هناك نوع أساس واحد، وطبقة من الأنواع الخاصة بالمجال، وعقد يلتزم به كل واحد منها.
نوع أساس واحد، شامل الالتقاط بحكم التصميم. إنّ NextPdfException مجرد، يمتد من RuntimeException، ويطبّق ContextAwareExceptionInterface:
abstract class NextPdfException extends RuntimeException implements ContextAwareExceptionInterface{ /** @return array<string, mixed> */ public function getContext(): array { return []; }}جعله مجردًا قرار تصميمي. لن تلتقط أبدًا النوع الأساس المبهم عن طريق الخطأ، لأنه لا يُطلق مباشرةً أبدًا. إنما تلتقطه عمدًا، بوصفه شبكة أمان، وتلتقط نوعًا فرعيًا محددًا حين يمكنك فعل شيء محدد.
أنواع فرعية محددة ومُصنَّفة النوع. الخط المفقود ليس خطأً عامًا؛ إنه FontNotFoundException، ويحمل البيانات التي تحتاجها للتصرف:
final class FontNotFoundException extends NextPdfException{ public function __construct( private readonly string $fontName, private readonly array $searchPaths, private readonly bool $fallbackAttempted, ?Throwable $previous = null, ) { parent::__construct( \sprintf('Font "%s" not found. Searched: [%s].', $fontName, \implode(', ', $searchPaths)), 0, $previous, ); } // getFontName(), getSearchPaths(), wasFallbackAttempted(), getContext()}تسمّي الرسالة الخط والمسارات الدقيقة التي جرى البحث فيها. لا تخمّن أي دليل كان مفقودًا؛ فالاستثناء يخبرك.
سياق مُهيكل، لا استخلاص من السلاسل النصية. يُرجِع كل استثناء خريطة بصيغة snake_case تقتصر على الأنواع الأولية، وآمنة لتسلسلها مباشرةً في سجل أو في حمولة APM:
public function getContext(): array{ return [ 'config_key' => $this->configKey, 'given_value' => $this->givenValue, 'expected_type' => $this->expectedType, ];}العقد صريح بشأن السبب. يمكن لوسيط تسجيل أن يستدعي $logger->error($e->getMessage(), $e->getContext()) لأي استثناء مهما كان في NextPDF دون أن يحلل الرسالة أبدًا. الرسالة للبشر. والسياق للآلات. ولا ينبغي لأيٍّ منهما أن يؤدي دور الآخر.
رسائل قابلة للتنفيذ عبر المنشئات المُسمَّاة. هنا تتوقف الأخطاء عن كونها عرضية وتصبح مُصمَّمة. لا يقول SignatureException فقط “فشل التوقيع عند المستوى B-LT”. إنه يقدّم منشئات مُسمَّاة تربط السبب الجذري الحقيقي، وغالبًا العلاج الدقيق، بالرسالة:
public static function tsaUrlEmpty(string $signatureLevel): self{ return new self('', $signatureLevel, null, 'TSA endpoint URL is empty: pass a non-empty `tsaUrl` to the TsaClient ' . 'constructor (e.g. "https://timestamp.example.com/tsa") or remove the ' . 'TSA client wiring if no timestamping is required at this signature level');}تذكر الرسالة ما هو الخطأ وما ينبغي فعله بشأنه. توجد منشئات شقيقة لحزمة قدرات مفقودة، وعميل HTTP غائب، وخوارزمية مخصصة للملخص فقط اختيرت عن طريق الخطأ، ونوع مفتاح لا يطابق الخوارزمية، وغير ذلك. يحوّل كل واحد منها فئة من الفشل إلى جملة يمكن للمطور التصرف بناءً عليها دون قراءة مصدر المحرك.
حالات فشل صاخبة عن قصد. توجد بعض الاستثناءات تحديدًا لكي تتحول فجوة صامتة إلى فجوة صاخبة. يحمل NotImplementedException وسم feature قابلًا للبحث الآلي بـ grep ومرجع followUp:
final class NotImplementedException extends NextPdfException{ public function __construct( public readonly string $feature, public readonly string $followUp, ?Throwable $previous = null, ) { parent::__construct( \sprintf('%s is not implemented in this release. %s', $feature, $followUp), 0, $previous, ); }}المسار الذي يُبلَغ لكنه غير موصول يطلق هذا بدلًا من إرجاع عملية لاغية تبدو معقولة. الفكرة نفسها تقود StrictModeViolation، الذي تحمل أنواعه الفرعية وسمًا قصيرًا قابلًا للبحث بـ grep للبنية المنحرفة إضافةً إلى سياق اختياري للموضع والاستشهاد. يصبح الانحراف عن المواصفة توقفًا مُصنَّف النوع ومصحوبًا بسياقه، لا عرضًا خاطئًا بصمت.
بيانات وصفية للفرز في الصنف نفسه. يسمّي كل صنف استثناء من يمكنه التصرف بشأنه في كتلة توثيقه. على سبيل المثال، FontNotFoundException هو “المطور (تحقق من مسار الخط) أو البنية التحتية (أصلِح أذونات الملفات)”. InvalidConfigException هو “المطور (أصلِح التهيئة قبل استدعاء NextPDF)”. NotImplementedException هو “مُستدعو المكتبة — إما إزالة الاستدعاء أو التثبيت على إصدار مستقبلي”. يبدأ الفرز قبل تتبُّع المكدس، لأن السؤال “هل هذا من مسؤوليتي أم من مسؤولية العمليات؟” له بالفعل إجابة مدوَّنة.
يلخص الجدول التصميم وما تمنحك إياه كل خاصية.
| خاصية التصميم | في المصدر | ما يمنحك إياه |
|---|---|---|
| نوع أساس مجرد واحد | NextPdfException (مجرد، يطبّق واجهة السياق) | التقاط كل خطأ في المكتبة بنوع واحد، دون التقاط النوع الأساس المبهم عن طريق الخطأ |
| أنواع فرعية محددة ومُصنَّفة النوع | FontNotFoundException، InvalidConfigException، SignatureException، … | التقاط حالة الفشل التي يمكنك معالجتها بالضبط |
| سياق مُهيكل | getContext() — أنواع أولية بصيغة snake_case فقط | التسجيل أو الإرسال إلى APM دون تحليل سلسلة رسالة |
| رسائل قابلة للتنفيذ | المنشئات المُسمَّاة تربط السبب الجذري + العلاج | جملة يمكنك التصرف بناءً عليها، لا قالب |
| صاخب عن قصد | NotImplementedException، StrictModeViolation | تحويل فجوة صامتة إلى توقف مُصنَّف النوع قابل للبحث بـ grep |
| بيانات وصفية للفرز | ”قابل للتنفيذ من قِبَل:” في كتلة توثيق كل صنف | معرفة لمن المشكلة قبل قراءة التتبُّع |
ما يقوله الدليل
قسم بعنوان «ما يقوله الدليل»هذه الصفحة Evidence: Code-backed : كل صنف وتوقيع وشكل رسالة مقتبس من فضاء أسماء الاستثناءات في المحرك، وليس مُعادًا صياغته.
- النوع الأساس المجرد وعقد
ContextAwareExceptionInterfaceالخاص به، والأنواع الفرعية المُصنَّفة النوع، وشكلgetContext()، ومنشئاتSignatureExceptionالمُسمَّاة، جميعها مقتبسة حرفيًا من المصدر. - إنّ أسطر الفرز “قابل للتنفيذ من قِبَل:” هي عقود في كتل توثيق الأصناف ضمن الملفات نفسها.
- المرتكز في العوامل البشرية هو Spec: ISO 9241-110 ISO 9241-110 — §5.6.4.3، بشأن الأخطاء التي تشرح نفسها بما يكفي لإصلاحها، ومبدأ المتانة في مواجهة أخطاء الاستخدام في §6. يعامل المحرك المطور بوصفه المستخدم، والاستثناء بوصفه الواجهة التي يجب أن تستوفي تلك البنود.
مثال عملي
قسم بعنوان «مثال عملي»التقِط على نطاق واسع بوصفه شبكة أمان، والتقِط على نحو محدد حيث يمكنك التصرف، ومرِّر السياق المُهيكل مباشرةً إلى مُسجِّلك — دون تحليل الرسالة.
<?php
declare(strict_types=1);
use NextPDF\Core\Document;use NextPDF\Exception\FontNotFoundException;use NextPDF\Exception\NextPdfException;use Psr\Log\LoggerInterface;
function renderInvoice(LoggerInterface $logger): ?string{ try { $document = Document::createStandalone(); $document->setTitle('Invoice 2026-0042'); $document->addPage(); $document->setFont('BrandSans', '', 12); $document->cell(0, 10, 'Thank you for your business.', newLine: true);
return $document->getPdfData(); } catch (FontNotFoundException $e) { // Specific: we can recover — fall back to a built-in font. // getContext() is log-safe structured data, not a parsed string. $logger->warning($e->getMessage(), $e->getContext());
return null; // caller re-renders with 'helvetica' } catch (NextPdfException $e) { // Backstop: any other NextPDF failure, still with structured context. $logger->error($e->getMessage(), $e->getContext());
return null; }}تتعافى كتلة catch المحددة لأن نوع الاستثناء أخبرها بأن التعافي ممكن. وتسجّل شبكة الأمان سياقًا مُهيكلًا لكل ما عدا ذلك. ولا يقرأ التطبيق الرسالة في أي لحظة لمعرفة ما حدث.
سوء فهم شائع
قسم بعنوان «سوء فهم شائع»القراءة الخاطئة المعتادة هي أن شجرة استثناءات عميقة تعني هندسة مفرطة، وأن نوع خطأ واحد سيكون أبسط. سيكون أبسط للمحرك وأسوأ لك. نوع واحد يعني أن كل حالة فشل تتحول إلى تتبُّع مكدس عام، وأن منطق التعافي يصبح مطابقة لسلسلة نصية. تلك المطابقة هشة؛ وأول إعادة صياغة للرسالة تكسرها. ينقل تسلسل هرمي صغير ومحدد تلك المعرفة إلى نظام الأنواع، حيث يمكن للمترجِم ولكتل catch لديك استخدامها.
سوء فهم ثانٍ هو أن الرسالة والسياق يكرران الشيء نفسه. هذا غير صحيح. الرسالة نص نثري لإنسان يقرأ سطر سجل. السياق خريطة مُصنَّفة النوع للتوجيه البرمجي أو التنبيه أو لوحات المعلومات. الخلط بينهما هو تحديدًا فخ تحليل السلاسل النصية الذي وُجِد عقد getContext() لإزالته.
الحدود والقيود
قسم بعنوان «الحدود والقيود»التسلسل الهرمي ضحل عن قصد. لا يُنشئ NextPDF صنف استثناء متمايزًا لكل حالة فشل يمكن تصورها. إنما يُنشئ صنفًا حين يكون التقاط تلك الحالة بعينها أمرًا قد يفعله مُستدعٍ على نحو معقول. الإفراط في التقسيم سيستبدل مشكلة قائمة التقاط متضخمة بمشكلة تحليل السلاسل النصية.
إنّ getContext() مُهيكل للسجلات و APM، لذا يُرجِع، بحكم العقد، أنواعًا أولية وقوائم من الأنواع الأولية فقط، دون كائنات متداخلة. إنه سياق تشخيصي، لا لقطة مُسلسلة من دواخل المحرك. كما أنه ليس صيغة نقل مستقرة لبناء مخططات خارجية بالاستناد إليها.
تصف هذه الصفحة السطح التصميمي للاستثناءات. تتطور المجموعة الدقيقة من الاستثناءات وحقولها مع المحرك. الأصناف والأشكال المقتبسة هنا محدَّثة حتى تاريخ هذه المراجعة، وهي توضيحية للعقد، لا فهرس مُجمَّد. العقد — نوع أساس واحد، وأنواع فرعية مُصنَّفة النوع، وسياق مُهيكل، ورسائل قابلة للتنفيذ — هو الجزء المستقر.
مستندات ذات صلة
قسم بعنوان «مستندات ذات صلة»- واجهة برمجية ترفض التخمين — الحراس سريعو الفشل الذين يطلقون هذه الاستثناءات من الأساس.
- فلسفة تصميم NextPDF — لماذا يُعَدّ مبدأ “الأخطاء سطح برمجي” مبدأً من الدرجة الأولى.
- نموذج خط الأنابيب — أين تظهر حالات الفشل هذه أثناء انتقال مستند عبر المحرك، وكيف تُرصَد.
مسرد المصطلحات
قسم بعنوان «مسرد المصطلحات»- مدعوم بالشيفرة (مستوى الدليل) — صفحة تُدقَّق ادعاءاتها بالاستناد إلى المصدر الفعلي للمحرك، مقتبسةً منه لا مُعاد صياغتها.
- استثناء مدرك للسياق — استثناء في NextPDF يطبّق
ContextAwareExceptionInterfaceويكشفgetContext(). تُرجِع تلك الطريقة خريطة بصيغة snake_case من حقول تشخيصية أولية، آمنة لتسلسلها في سجل أو حمولة APM دون تحليل سلسلة الرسالة. - منشئ مُسمَّى — طريقة مصنع ساكنة (على سبيل المثال
SignatureException::tsaUrlEmpty()) تبني استثناءً برسالة مرتبطة بسبب جذري محدد، وغالبًا بعلاجه. - PAdES — التوقيعات الإلكترونية المتقدمة لملفات PDF، وهي عائلة ملامح ETSI لتوقيع ملفات PDF. يُفصَّل عند أول استخدام؛ ويُغطَّى بعمق في صفحات التوقيع.
- TSA — سلطة الختم الزمني، وهي الخدمة الموثوقة التي تُصدِر أختام RFC 3161 الزمنية المستخدَمة في ملامح PAdES الأعلى.