واجهة API ترفض التخمين
Spec: ISO/IEC 25010 ISO/IEC 25010 Spec: ISO 32000-2 ISO 32000-2 Evidence: Code-backed
لمحة سريعة
قسم بعنوان «لمحة سريعة»تُلزمك NextPDF بالتصريح بما تقصده تحديداً. وكلما غيَّرت النية البايتات — مستوى توقيع، أو وجهة إخراج، أو هدف مطابقة — فهي وسيطة صريحة مطلوبة، وليست شيئاً يستنتجه المحرك من السياق.
تُظهر هذه الصفحة هذا الموقف في مصدر المحرك نفسه: توقيعات التوابع، والوسائط المُسمّاة، والمواضع التي يُرفَض فيها كل مُدخَل غامض قبل إنتاج أي بايت.
لماذا يهمّ هذا
قسم بعنوان «لماذا يهمّ هذا»التخمين قرارٌ يُتَّخذ نيابةً عنك من دون إخبارك. في حقل نصي، قد يكون ذلك مزعجاً فحسب. أما في ملف PDF، فهو عيبٌ كامِن، لأن ما تُسلِّمه غالباً أثرٌ قانوني أو أرشيفي ستُتحقَّق صحته لاحقاً من قِبل شخصٍ آخر باستخدام أداة تحقُّق.
تأمَّل توقيعاً. تُحسَب خلاصته على نطاق بايتات مُعلَن يستثني قيمة التوقيع نفسها عمداً ( Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 ). واجهة API التي “تساعد” بهدوء — بإعادة كتابة البنية، أو استنتاج مستوى، أو حشو موضعٍ نائب — لا تساعد فعلاً. بل تغيِّر البايتات التي كان يُفترض أن يحميها التوقيع. والتخمين الذي يبدو ودوداً عند موضع الاستدعاء قد يتحوَّل بعد أسابيع إلى عطلٍ إنتاجي. كلاهما السطر نفسه من الشيفرة.
النسخة المُختصَرة
قسم بعنوان «النسخة المُختصَرة»- إذا كان خيارٌ ما يُغيِّر الإخراج وليس له افتراضي آمن، فإن NextPDF تجعله وسيطة مطلوبة، لا وسيطةً مُستنتَجة.
- الوسائط الاختيارية التي تُقرأ بغموض تكون مُسمّاة، لكي يُعبِّر موضع الاستدعاء عن النية (
newLine: true، لا قيمةtrueمُجرَّدة). - المُدخلات التي قد تكون غير آمنة تُخضَع لـالتحقُّق قبل العرض، وتُرفَض باستثناءٍ مُحدَّد النوع يُسمِّي السبب.
- نسخة المستند تُستخدَم مرة واحدة: تُبنى، وتُصدَر، وتُتلَف. لا يوجد
reset()، لذا لا مجال لتخمين “هل أُعيد استخدام هذا الشيء؟”. - لا يُصدِر المحرك أبداً أثراً يبدو معقولاً بدلاً من الأثر الذي طلبته. بل يرفض ذلك.
كيف تتعامل NextPDF مع ذلك
قسم بعنوان «كيف تتعامل NextPDF مع ذلك»الآلية بسيطة، وهذا هو المقصود. إنها نظام الأنواع، والوسائط المُسمّاة، والتعدادات بدلاً من السلاسل السحرية، وعددٌ صغير من جُمَل الحراسة المتعمَّدة الموضوعة قبل الإخراج.
يُقارِن الجدول بضعة مُدخلات غامضة. ولكلٍ منها يُبيِّن ما قد تستنتجه مكتبةٌ “تساعد”، وما تفعله NextPDF بدلاً من ذلك. كل عمود من أعمدة NextPDF يعرض سلوكاً مقتبَساً من المصدر الوارد لاحقاً في هذه الصفحة.
| مُدخَل غامض | ما تفعله مكتبةٌ تُخمِّن | ما تفعله NextPDF |
|---|---|---|
سلسلة اتجاه مثل "portait" | يلجأ إلى افتراضي ويَعرِض على أي حال | يأخذ addPage() تعداد Orientation، لا سلسلة نصية — فالخطأ المطبعي خطأ نوع، لا افتراضي صامت |
قيمة true لاحقة مُجرَّدة في cell() | يختار أي موضع منطقي يفترض أنك قصدته | القيمة المنطقية مُسمّاة عند موضع الاستدعاء (newLine: true)؛ والقيمة الحرفية غير المُسمّاة هي موضع الالتباس الذي تُزيله الواجهة |
مُغلِّف php:// أو مسار اجتياز يُمرَّر إلى save() | ”يبذل قصارى جهده” ويكتب في مكانٍ ما | يُرفَض قبل بناء PDF، باستثناء مُحدَّد النوع InvalidConfigException يُسمِّي المفتاح والقيمة والنوع المتوقَّع |
setSignature() ثم save() بينما المُوقِّع عالي المستوى غير موصول | يُصدِر ملفاً غير مُوقَّع يظن المُستدعي أنه مُوقَّع | يَرمي NotImplementedException قبل إنتاج البايتات، ويُسمِّي المسار المدعوم |
إعادة استخدام نسخة Document لعرضٍ ثانٍ | يُخمِّن ما إذا كانت الحالة المتبقية لا تزال صالحة | لا يوجد reset() ولا مسار لإعادة الاستخدام — نسخة جديدة لكل طلب عبر DocumentFactory، لذا لا توجد حالة متبقية للتخمين بشأنها |
النية وسيطة مطلوبة. يأخذ العقد الأساسي، PdfDocumentInterface، الهندسة والمحاذاة على هيئة كائنات قيمة وتعدادات مُحدَّدة النوع، لا قيماً بدائية فضفاضة:
public function addPage( ?PageSize $size = null, Orientation $orientation = Orientation::Portrait,): static;
public function cell( float $width, float $height, string $text = '', bool|string $border = false, bool $newLine = false, Alignment $align = Alignment::Left, bool $fill = false,): static;Orientation وAlignment تعدادان، لذا لا يستطيع موضع الاستدعاء تمرير "portait" وجعلها تعني “افتراضي” بصمت. وحين يوجد افتراضي، فهو افتراضي آمن (عمودي، يسار، بلا حدود)، لا تخمينٌ لما أردته على الأرجح.
القيم المنطقية الغامضة تُسمّى عند موضع الاستدعاء. عبر الأمثلة التي تعمل كمرجع فعلي للواجهة، يتكرَّر الشكل ذاته:
$document->cell(0, 15, 'Hello, NextPDF!', newLine: true);$document->setSignature(certInfo: $certInfo, level: SignatureLevel::PAdES_B_B);$pdf = $document->output(dest: OutputDestination::String);newLine: true لا لبس فيها. أما قيمة true اللاحقة المُجرَّدة فلن تكون كذلك. مستوى التوقيع هو SignatureLevel::PAdES_B_B، حالة تعداد — لا سلسلة نصية يتعيَّن على المحرك تفسيرها. وجهة الإخراج هي OutputDestination::String، لذا فإن “أعطِني البايتات، بلا ترويسات HTTP، بلا ملف” مصرحٌ بها صراحة. ولا تُستنتَج من كون اسم ملفٍ قد مُرِّر أم لا.
المُدخَل غير الآمن يُرفَض قبل كتابة أي بايت. يتحقَّق save() من مسار الوجهة قبل أن يبني PDF:
public function save(string $path): void{ // Reject stream wrappers and null bytes if (\str_contains($path, "\0") || \preg_match('#^[a-zA-Z]+://#', $path)) { throw new InvalidConfigException( configKey: 'output_path', givenValue: $path, expectedType: 'valid_path', ); } // Resolve the parent directory to prevent path traversal $dir = \dirname($path); $realDir = \realpath($dir); if ($realDir === false) { throw new InvalidConfigException( configKey: 'output_path', givenValue: $dir, expectedType: 'existing_directory', ); } // ... only now is the PDF built and written atomically}لا “يبذل المحرك قصارى جهده” مع مُغلِّف php:// أو مسار اجتياز. بل يرفض، ويُسمِّي الاستثناء المفتاح والقيمة وما كان متوقَّعاً.
يرفض المحرك بدلاً من إصدار أثرٍ مُضلِّل. أقوى صور رفض التخمين هي الامتناع عن إنتاج الإخراج أصلاً عندما يكون ذلك الإخراج غير صادق. عندما يُهيَّأ توقيعٌ عالي المستوى لكن وصلة الكاتب التي ستُوقِّع فعلاً غير موصولة، يَرمي مسار البناء قبل إنتاج البايتات، بدلاً من إصدار ملفٍ غير مُوقَّع يظن المُستدعي أنه مُوقَّع:
if ($this->padesOrchestrator !== null) { throw new NotImplementedException( feature: 'Document::setSignature()->save()/output()/getPdfData()', followUp: 'The high-level PAdES writer seam is not yet wired ... ' . 'Produce a signed PDF via the direct two-phase ' . 'PadesOrchestrator::signDocument() then finalizeSignature() ' . 'buffer API ...', );}ملف PDF غير مُوقَّع يبدو مُوقَّعاً هو بالضبط نوع الأثر الخاطئ الذي يبدو معقولاً، وهو ما وُجِد هذا المبدأ لمنعه. ويظهر الموقف ذاته في مسار CSS الصارم. فأي انحرافٍ غير مُسجَّل عن المواصفة يَرمي StrictModeViolation عند نقطة الكشف، بدلاً من عرض تقريبٍ وترك الانحراف غير مكتشَف.
الاستخدام مرة واحدة يُزيل فئةً كاملة من التخمينات. Document كائنٌ قابل للتخلُّص — يُبنى، ويُصدَر، ويُتلَف. لا يوجد reset() ولا مسار لإعادة الاستخدام. يُنشئ العامل طويل الأمد نسخةً جديدة لكل طلب عبر DocumentFactory. ولا يضطر المحرك أبداً إلى التخمين بشأن ما إذا كانت الحالة المتبقية من مستندٍ سابق لا تزال ذات معنى، لأنه لا توجد حالةٌ كهذه بحُكم البناء.
ماذا تقول الأدلة
قسم بعنوان «ماذا تقول الأدلة»هذه الصفحة Evidence: Code-backed : كل شكلٍ أعلاه مقتبَس من مصدر المحرك نفسه ومن أمثلته، لا مُعاد صياغته من النية.
- التوقيعات المُحدَّدة الأنواع الحاملة للتعدادات هي العقد العام في
PdfDocumentInterface. وأسلوب الاستدعاء بالوسائط المُسمّاة هو الشكل المُتَّسق عبر الأمثلة المعيارية التي تعمل كمرجع فعلي للواجهة. - التحقُّق من المسار قبل العرض، باستثنائه المُحدَّد النوع
InvalidConfigException، وحارس الرفض-قبل-الإصدارNotImplementedExceptionمقتبسان حرفياً من مسار الإخراج لواجهة المستند. - المرساة المعيارية هي Spec: ISO/IEC 25010, §3.32 ISO/IEC 25010 §3.32 — الحماية من خطأ المستخدم، خاصية الجودة التي وُجِدت واجهة رافضة للتخمين لتلبيتها عند موضع الاستدعاء. والمرساة الثانية هي Spec: ISO 32000-2, §12.8 ISO 32000-2 §12.8 ، وهذا سبب أن التخمين حول مستندٍ مُوقَّع ليس غير ضارٍّ أبداً. تُغطّي الخلاصة نطاق بايتاتٍ مُعلَناً يستثني قيمة التوقيع، لذا فإن أي إعادة كتابةٍ صامتة تُبطِلها.
مثال عملي
قسم بعنوان «مثال عملي»يلي ذلك برنامجٌ صغير وكامل. كل سطرٍ قد يكون غامضاً يُعبِّر عن نيته. والمُدخَل غير الآمن الوحيد يُرفَض قبل تنفيذ أي عمل.
<?php
declare(strict_types=1);
use NextPDF\Contracts\OutputDestination;use NextPDF\Core\Document;use NextPDF\Exception\InvalidConfigException;use NextPDF\ValueObjects\PageSize;use NextPDF\Contracts\Orientation;
$document = Document::createStandalone();$document->setTitle('Quarterly Report');
// Intent is explicit: a typed page size and an Orientation enum case,// not a string the engine has to interpret.$document->addPage(PageSize::a4(), Orientation::Landscape);$document->setFont('helvetica', 'B', 16);
// Ambiguous boolean is named, so the call reads as intent.$document->cell(0, 12, 'Quarterly Report', newLine: true);
try { // Unsafe path is rejected before a byte is built. $document->save('php://output/report.pdf');} catch (InvalidConfigException $e) { // "Invalid configuration for key "output_path": expected valid_path, ..." error_log($e->getMessage());
// The String destination is explicit: bytes only, no HTTP headers, // no file side effect. Nothing is inferred from a missing filename. $bytes = $document->output(dest: OutputDestination::String);}لا يوجد مسارٌ يفعل فيه هذا البرنامج الشيء الخاطئ بهدوء. فهو إما يُعبِّر عن النية ويمضي، أو يُسمِّي المشكلة ويتوقَّف.
مفهوم خاطئ شائع
قسم بعنوان «مفهوم خاطئ شائع»الاعتراض المتكرر هو “هذا مجرد إسهاب”. لكنه ليس إسهاباً، بل غيابٌ للافتراضيات الخفية. قيمة true المُجرَّدة أقصر من newLine: true بقدر الوضوح الذي تُزيله. يُقايِض المحرك بضعة محارف عند موضع الاستدعاء مقابل القضاء على فئةٍ من العلل — تلك التي تُصرَّف فيها الشيفرة، وتعمل، وتُنتج ملفاً، وتكون خاطئة.
مفهومٌ خاطئ ذو صلة هو أن الإخفاق السريع يعني “يَرمي كثيراً”. في الاستخدام العادي لا تَرمي NextPDF شيئاً. المُدخَل الصالح يمر بسلاسة. ولا تُفعَّل الحراسات إلا مع المُدخلات الغامضة أو غير الآمنة فعلاً — وهي بالضبط المُدخلات التي تريد أن تعرف بها فوراً، لا أن تُخمَّن نيابةً عنك.
الحدود والقيود
قسم بعنوان «الحدود والقيود»ينطبق رفض التخمين على النية والأمان، لا على كل وسيلة راحة. لا تزال NextPDF تملك افتراضيات آمنة: اتجاه عمودي، محاذاة لليسار، بلا حدود. والمبدأ هو أن الافتراضي يُقدَّم فقط حين يكون آمناً وغير مفاجئ، ولا يُقدَّم أبداً حين يُنتج الاستنتاج الخاطئ مستنداً خاطئاً.
تُبيِّن هذه الصفحة المبدأ على سطح الواجهة العامة الأساسية (واجهة المستند، وعقده، ومسار الإخراج). للأنظمة الفرعية نقاط دخولها الخاصة، ويُوثِّق كلٌّ منها سلوك التحقُّق الخاص به. الأشكال المقتبسة هنا حديثة اعتباراً من هذه المراجعة. وهي تُوضِّح النمط؛ لكنها ليست فهرساً شاملاً لكل حارسٍ في المحرك.
الحراسات سريعة الإخفاق الموصوفة هي حراسات صحة وأمان. وهي ليست حدّاً أمنياً بذاتها. التحقُّق من المُدخلات طبقةٌ واحدة. فلسفة التصميم ووثائق الأمان تصف الموقف الأوسع.
وثائق ذات صلة
قسم بعنوان «وثائق ذات صلة»- فلسفة تصميم NextPDF — المبدأ الذي تُبيِّنه هذه الصفحة، ضمن سياق أولويته.
- الأخطاء كميزة — ما صُمِّمت الاستثناءات المُحدَّدة الأنواع التي تَرميها هذه الحراسات لإبلاغك به.
- الأنواع الصارمة في كل مكان — كيف يجعل نظام الأنواع “عبِّر عن نيتك” قابلاً للإنفاذ بدلاً من أن يكون إرشاداً فحسب.
مسرد المصطلحات
قسم بعنوان «مسرد المصطلحات»- مدعوم بالشيفرة (مستوى الأدلة) — صفحة تُتحقَّق ادعاءاتها مقابل مصدر المحرك نفسه أو مثالٍ قابل للتشغيل، اقتباساً لا إعادة صياغة.
- الإخفاق السريع — رفض مُدخَلٍ غير صالح عند أبكر نقطة، وبسببٍ واضح، بدلاً من المضي ثم الإخفاق بغموضٍ لاحقاً.
- الوسيطة المُسمّاة — صياغة عند موضع الاستدعاء في PHP (
newLine: true) تربط قيمةً بمعامل بالاسم، فتجعل القيمة الحرفية التي كانت ستبدو غامضة وصفيةً لذاتها. - دورة حياة الاستخدام مرة واحدة — عقد
Documentالقابل للتخلُّص: إنشاء، كتابة، حفظ، تخلُّص. لاreset()، ولا إعادة استخدام. يُنشئ العاملون نسخةً جديدة لكل طلب عبرDocumentFactory. - PAdES — توقيعات PDF الإلكترونية المتقدمة، عائلة ملامح ETSI لتوقيع PDF. تُوسَّع عند أول استخدام؛ وتُغطّى بعمق في صفحات التوقيع.