البايتات نفسها في كل مرة: ملفات PDF القابلة للتكرار
Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3
لمحة سريعة
قسم بعنوان «لمحة سريعة»ابنِ ملف PDF من المُدخَلات نفسها مرتين وستتوقع الملف نفسه. لا تستطيع معظم مكتبات PDF أن تعد بذلك — أعد البناء وقارِن، فتنحرف البايتات. يستطيع NextPDF تثبيت الشيئين المتحركين، كي تُنتِج المُدخَلات نفسها البايتات نفسها، في كل مرة.
لماذا يهم هذا
قسم بعنوان «لماذا يهم هذا»المُخرَج المتطابق البايتات ليس مقياس غرور. إنه الأساس الذي تقوم عليه ثلاثة أشياء تريدها الفرق فعلاً.
الأول هو التخزين المؤقت. إن كان البناء دالة محضة لمُدخَلاته، فتجزئة مُخرَجه مفتاح تخزين مؤقت. مُدخَلات نفسها، تجزئة نفسها، تخطَّ العمل وقدّم الملف المخزَّن. حين تتجول البايتات، تتجول التجزئة، ولا يصيب التخزين المؤقت أبداً.
الثاني هو شاهد العبث. خط الأنابيب الذي يستطيع إعادة توليد الملف ذاته الذي شحنه يستطيع أن يثبت، لاحقاً، أن مستنداً مؤرشفاً لم يُغيَّر: أعد بناءه، وجزّئ كليهما، وقارِن. إن اختلف ولو بايت واحد بسبب ساعة مُضمَّنة، زال البرهان وعدت إلى “ثِق بي”.
الثالث هو تكامل مستمر جدير بالثقة. يسجّل اختبار الملف الذهبي مُخرَجاً معروف الصلاح ويفشل حين يغيّره تغيير. تلك الإشارة لا تكون ذات معنى إلا حين يعيد محرك دون تغيير إنتاج ملف دون تغيير. إن اختلف كل تشغيل في طابع زمني، فالملف الذهبي ضوضاء، ويتعلّم الفريق تجاهل بناء أحمر — أغلى عادة في الاختبار.
الخلاصة المختصرة
قسم بعنوان «الخلاصة المختصرة»في ملف NextPDF الحتمي، الحقلان اللذان يتحكم فيهما المحرك واللذان كانا سينحرفان لولا ذلك بين عمليتي بناء متطابقتين هما التاريخان و/ID. يفترض هذا أن بقية خط الأنابيب مستقرة بالفعل — المُدخَلات نفسها، وتسلسل لا يتغير من تلقاء نفسه (المزيد عن ذلك أدناه):
- التواريخ المُضمَّنة. يحمل قاموس معلومات المستند
CreationDateوModDate(Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3)، وتعكسهما بيانات XMP الوصفية. التقط “الآن” وقت البناء فيختلف كل إعادة بناء. - معرّف الملف. مصفوفة
/IDزوج من سلاسل البايتات يعرّف الملف (Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4)، مخزَّن في قاموس الخاتمة (Spec: ISO 32000-2, §7.5.5ISO 32000-2 §7.5.5). تشتقّه المكتبات عادةً من الوقت الحالي زائد بايتات عشوائية، فيكون مختلفاً في كل تشغيل بالتصميم.
ثبّت كليهما — طابعاً زمنياً ثابتاً وبذرة ثابتة لـ/ID — فيصير المُخرَج دالة حتمية لمحتواه. اترك المحتوى وشأنه فيكون الملف متطابقاً بايتاً ببايت. هذا هو الانضباط نفسه الذي أرساه مشروع Reproducible Builds للبرمجيات المُترجَمة، مطبَّقاً على طبقة المستند.
كيف يتعامل NextPDF مع هذا
قسم بعنوان «كيف يتعامل NextPDF مع هذا»الحتمية في NextPDF كائن تهيئة، لا حيلة اختبار. يكشف المحرك كائن قيمة DeterministicSettings في المساحة الأسمائية NextPDF\Core. إنه final readonly، غير قابل للتغيير، ويثبّت بالضبط مصدرَي الانحراف المشتقّين من الساعة والعشوائية المذكورين أعلاه: التاريخان و/ID. تثبيتهما يزيل أشيع مصدرَي انحراف، لكنه لا يضمن وحده مُخرَجاً متطابق البايتات. سلوك التسلسل الآخر للمحرك — ترتيب الكائنات، وتجزئة الخطوط، وإعدادات الضغط — يجب أن يكون حتمياً أيضاً كي يتكرر المُخرَج، ويُبقي NextPDF تلك مستقرة بالتصميم.
يأخذ مُنشئه وسيطين:
public function __construct( public DateTimeImmutable $timestamp, public string $fileIdSeed,) { // ...}$timestamp هو اللحظة الثابتة الواحدة المكتوبة إلى كل حقل تاريخ — CreationDate، وModDate، ومرآتهما في XMP. مرّر DateTimeImmutable واحداً فيتوقف المستند عن سؤال ساعة الحائط عن الوقت. $fileIdSeed هو المُدخَل الذي يثبّت /ID في الخاتمة: سلسلة ست عشرية من 32 محرفاً. أعطِ البذرة نفسها فيشتقّ المحرك معرّف الملف نفسه بدلاً من أخذ عينة من الساعة ومصدر عشوائي.
يصدّق الكائن مُدخَله بنفسه. يجب أن تكون البذرة 32 محرفاً ست عشرياً بالضبط؛ وأي شيء آخر يُرفَض عند الإنشاء بـInvalidConfigException بدلاً من إنتاج /ID مختلف المظهر بصمت. هذا هو موقف رفض-التخمين نفسه الذي يتخذه بقية المحرك — يفشل المُدخَل الغامض بصوت عالٍ بدلاً من تغيير البايتات بهدوء.
مع تثبيت كليهما، الوصفة هي التي جعلها مشروع Reproducible Builds مألوفة: أعد بناءه، وقارِنه، فيكون الفرق فارغاً.
- Fix the inputsThe same content, fonts, and settings that produced the original document.
- Pin the timestampOne DateTimeImmutable feeds CreationDate, ModDate, and the XMP dates — no wall clock.
- Pin the /ID seedA 32-character hex seed derives the trailer /ID instead of a clock-plus-random value.
- BuildThe output is now a pure function of content; the two moving parts are held still.
- Rebuild and diffRegenerate from the same inputs and compare bytes — an empty diff is the proof.
مثال عملي
قسم بعنوان «مثال عملي»شكل صغير كامل. تُبنَى الإعدادات مرة واحدة وتُعاد استخدامها، فيُصدِر تشغيلان للبرنامج نفسه الملف نفسه.
<?php
declare(strict_types=1);
use NextPDF\Core\DeterministicSettings;use NextPDF\Exception\InvalidConfigException;
// One fixed instant for every date field — never the wall clock.$timestamp = new DateTimeImmutable('2026-01-01T00:00:00+00:00');
// A 32-character hex seed pins the trailer /ID. Same seed, same /ID.$fileIdSeed = '0123456789abcdef0123456789abcdef';
try { $deterministic = new DeterministicSettings( timestamp: $timestamp, fileIdSeed: $fileIdSeed, );} catch (InvalidConfigException $e) { // A malformed seed (not exactly 32 hex chars) is refused here, // before any document is built — not silently coerced. error_log($e->getMessage());
throw $e;}
// Hand $deterministic to the document configuration. With both moving// parts pinned, building the same content twice yields identical bytes://// sha256(build_one) === sha256(build_two)البذرة مُدخَل بناء تتحكم فيه، لا سرّ. خزّنها بجانب بقية تهيئة بنائك. المقصود أنها ثابتة، فمعرّف الملف الذي تنتجه ثابت أيضاً.
مفهوم خاطئ شائع
قسم بعنوان «مفهوم خاطئ شائع»الفخ الأول هو “أزلتُ الطابع الزمني، فبنائي قابل للتكرار الآن.” عادةً ليس كذلك، لأن مصفوفة /ID هي الأهدأ بين المصدرين. التواريخ مرئية في لوحة بيانات وصفية وسهلة التذكّر؛ أما /ID في الخاتمة فغير مرئي لمعظم القرّاء ويُعاد توليده من الساعة ومصدر عشوائي في كل تشغيل. البناء الذي يثبّت التواريخ وحدها لا يزال يُنتِج ملفاً مختلفاً في كل مرة. عليك تثبيت كليهما.
الفخ الثاني هو معاملة الحتمية ميزة أمان بحد ذاتها. /ID المثبَّت يجعل ملفاً قابلاً للتكرار؛ ولا يجعله موقَّعاً، ولا يثبت وحده أن بناءين متطابقان. المقارنة بايتاً ببايت أو التجزئة تثبت تطابق البناءين؛ أما تثبيت /ID فيزيل مصدراً واحداً فقط من الفروق الزائفة. ولا شيء من ذلك يثبت أن طرفاً ثالثاً يكفل الملف. القابلية للتكرار والتوقيع طبقتان متكاملتان، لا بديلتان.
الحدود والقيود
قسم بعنوان «الحدود والقيود»تثبّت الحتمية الأجزاء المتحركة الخاصة بالمحرك. ولا تثبّت مُدخَلاتك. إن ضمّن محتواك طابعاً زمنياً حياً، أو سحب خطاً تغيّر على القرص، أو عرض قيمة تعتمد على التاريخ الحالي، فيتغير المُخرَج لأن المُدخَل تغيّر — وذلك صحيح. يزيل DeterministicSettings لاحتمية المحرك، لا لاحتميتك. البناء القابل للتكرار لا يزال يتطلب مُدخَلات قابلة للتكرار.
| Edition | Availability |
|---|---|
| Core | دعم كامل. يُشحَن |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
مستندات ذات صلة
قسم بعنوان «مستندات ذات صلة»- اختبار الملف الذهبي — تقنية التكامل المستمر التي تعتمد على مُخرَج متطابق البايتات، ولماذا يكون المحرك الحتمي شرطها المسبق.
- التحديثات التزايدية — كيف ينمو ملف PDF بالإلحاق، حيث تهمّ مصفوفة
/IDمجدداً لربط ملف بإصداراته الأسبق. - البيانات الوصفية وحزمة XMP — أين تستقر التواريخ المُضمَّنة، وكيف تعكس حزمة XMP قاموس معلومات المستند.
- تشريح ملف PDF — الخاتمة، وقسم المراجع التقاطعية، وأين تستقر مصفوفة
/IDفي بنية الملف.
مسرد المصطلحات
قسم بعنوان «مسرد المصطلحات»- متطابق البايتات — ملفان متطابقان تماماً، بايتاً ببايت. أقوى صور “نفسه”، وما تستطيع تجزئة أو مقارنة التحقق منه.
/ID(معرّف الملف) — مصفوفة سلسلتَي بايتات تعرّف ملف PDF وإصداراته (ISO 32000-2 §14.4)، مخزَّنة في قاموس الخاتمة (§7.5.5). تُشتَقّ عادةً من الساعة زائد بايتات عشوائية، ولهذا يتغير في كل بناء غير مثبَّت.- قاموس معلومات المستند — البنية التي تحمل
CreationDateوModDate(ISO 32000-2 §14.3.3). أحد مصدرَي اللاحتمية اللذين يجب أن يثبّتهما بناء حتمي. - الملف الذهبي — مُخرَج مسجَّل معروف الصلاح يقارن به اختبار؛ ذو معنى فقط حين يعيد محرك دون تغيير إنتاج ملف دون تغيير.
- البناء القابل للتكرار — بناء مُخرَجه دالة حتمية لمُدخَلاته، فإعادة البناء من المُدخَلات نفسها تُنتِج البايتات نفسها. المصطلح يأتي من مشروع Reproducible Builds للبرمجيات المُترجَمة.