تضمين الملفات وإنشاء حافظات PDF
نظرة سريعة
قسم بعنوان «نظرة سريعة»يُرفِق هذا النمط ملفًا واحدًا أو أكثر بملف PDF، ويرتّبها في صورة حافظة PDF عندما تكون لديك عدة مرفقات. استخدمه عندما يحتاج المستند إلى تضمين أدلة داعمة داخل الملف نفسه: فاتورة مع جدول الدوام الذي استندت إليه، أو ورقة بيانات منتج مع تصدير من التصميم بمساعدة الحاسوب (CAD)، أو سجل أرشيفي يحتفظ بجدول البيانات المصدري إلى جانب التقرير المعروض.
يوفّر NextPDF نقطتي دخول على كائن المستند. يقرأ embedFile() ملفًا من القرص، بينما يضمّن embedFileFromString() بايتات موجودة في الذاكرة وتولّدها وقت التشغيل. ويسجّل كلاهما المرفق. وعند save()، يكتب المحرك كل مرفق في صورة دفق ملف مضمّن، ويغلّفه في قاموس مواصفات ملف، ويربط كل مواصفة في شجرة الأسماء EmbeddedFiles على مستوى المستند. يعرّف ISO 32000-2 شجرة الأسماء هذه بوصفها الموضع الذي ترتبط فيه تدفقات الملفات المضمّنة بالمستند ككل من خلال قاموس الأسماء.
هذه إمكانية أساسية بلا قيد تجاري. وواجهة برمجة التطبيقات (API) الخاصة بالمرفقات مستقرة منذ 1.0.0 وتعمل عبر مصفوفة التوافق 8.1-8.4.
التثبيت
قسم بعنوان «التثبيت»composer require nextpdf/core:^3لا يلزم أي امتداد اختياري.
نظرة مفاهيمية عامة
قسم بعنوان «نظرة مفاهيمية عامة»يمرّ المرفق عبر ثلاث بنى في PDF. تساعدك معرفتها على فحص المخرجات وتنقيح أي ملف غير مطابق.
- دفق الملف المضمّن. البايتات الخام للملف المرفق، مضغوطة بـ Flate، ومكتوبة في صورة كائن دفق يكون
/Typeفيه هو/EmbeddedFile. يسجّل NextPDF الحجم الأصلي، ومجموع تحقّق MD5، وتاريخ التعديل في قاموس معاملات الدفق. ويرمّز نوع امتدادات بريد الإنترنت متعددة الأغراض (MIME) المكتشَف بوصفه/Subtypeالدفق. - قاموس مواصفات الملف. الغلاف الذي يحمل البيانات الوصفية. فهو يحمل اسم الملف المعروض (
/Fوصيغة Unicode منه/UF)، ووصفًا مقروءًا للبشر (/Desc)، ومرجعًا إلى الدفق المضمّن (/EF)، وعلاقة الملف بالمستند المضيف (/AFRelationship). - شجرة الأسماء
EmbeddedFiles. فهرس وحيد على مستوى المستند يربط اسم كل مرفق بمواصفات ملفه. يشترط ISO 32000-2 أن تحمل كل مواصفة ملف يُوصَل إليها عبر هذه الشجرة مُدخلEFتشير قيمته إلى دفق ملف مضمّن. يبني NextPDF هذه الشجرة ويوازنها نيابةً عنك عندsave().
تهمّ قيمة العلاقة لأغراض المطابقة. تنصّ مذكرة تطبيق رابطة PDF رقم 0002 على أن الملف المرتبط يتطلب مُدخل AFRelationship مختارًا من المجموعة الثابتة في PDF 2.0: Source، أو Data، أو Alternative، أو Supplement، أو EncryptedPayload، أو FormData، أو Schema، أو Unspecified. يُنمذِج NextPDF تلك المجموعة في صورة التعداد AFRelationship ويرفض أي قيمة أخرى. اختر المصطلح الذي يفسّر سبب وجود الملف: جدول الدوام الذي تستند إليه الفاتورة هو Source؛ ومجموعة بيانات قابلة للقراءة آليًا خلف رسم بياني هي Data.
تأتي حافظة PDF (وتُسمى مجموعة في ISO 32000-2) في الطبقة الأعلى التالية. عندما يحمل مستند عدة مرفقات، يوجّه قاموس Collection في الفهرس القارئ إلى كيفية عرضها: جدول تفاصيل قابل للفرز، أو تخطيط بلاطات، أو مظروف مخفي. يصف ISO 32000-2 قاموس Collection بوصفه أداة التحكم التي يستخدمها معالج PDF لعرض مرفقات الملفات في صورة حافظة منظمة. يُنمذِج NextPDF ذلك في صورة كائن القيمة CollectionDictionary، مع CollectionSort لترتيب الأعمدة في عرض التفاصيل.
سطح واجهة برمجة التطبيقات
قسم بعنوان «سطح واجهة برمجة التطبيقات»تأتي الطرائق على مستوى المستند من السمة HasFileAttachments في \NextPDF\Core\Document:
embedFile(string $path, string $description = ''): static— يقرأ ملفًا من$pathويرفقه. يكتشف NextPDF نوع MIME من الامتداد؛ وتكون العلاقة الافتراضيةUnspecified. يقرأ حتى 100 MB؛ استخدمembedFileFromString()للحمولات الأكبر. يُعيد المستند للتسلسل.embedFileFromString(string $data, string $filename, string $description = '', string $afRelationship = '/Unspecified'): static— يرفق بايتات موجودة في الذاكرة تحت الاسم المعروض$filename. مرّر قيمةAFRelationshipحرفية (مع الشرطة المائلة الأولى أو بدونها) لضبط العلاقة. يُعيد المستند للتسلسل.
تعيش الأنواع الداعمة في مساحتي الأسماء \NextPDF\Navigation و\NextPDF\Document:
\NextPDF\Navigation\AFRelationship— تعداد قيم العلاقة الثماني الصالحة.AFRelationship::coerce()يُسوّي سلسلة نصية أو حالة تعداد ويطرح استثناءً عند قيمة غير معروفة.toPdfName()يُصدر القيمة الحرفية/Name.\NextPDF\Document\CollectionDictionary— يبني قاموسCollectionفي الفهرس. تختار الثوابتVIEW_DETAILS، وVIEW_TILE، وVIEW_HIDDEN، وVIEW_CUSTOM، وVIEW_NONEوضع العرض؛ ويقبل المُنشئ أيضًا اسم مستند مبدئيًّا وفرزًا اختياريًّا.\NextPDF\Document\CollectionSort— كائن قيمة لترتيب الأعمدة في حافظة بعرض التفاصيل.
نموذج التعليمات البرمجية — بداية سريعة
قسم بعنوان «نموذج التعليمات البرمجية — بداية سريعة»يرفق هذا المثال البسيط مجموعة بيانات بقيم مفصولة بفواصل (CSV) مولَّدة مع صفحة فاتورة، ويعلنها بوصفها بيانات Source التي بُنيت منها الفاتورة.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Navigation\AFRelationship;
$doc = Document::createStandalone();$doc->addPage();$doc->setFont('helvetica', 'B', 18);$doc->cell(0, 12, 'Invoice INV-2026-0042', newLine: true);
// Attach the line-item dataset the invoice was rendered from.$csv = "sku,qty,unit_price\nA-100,3,49.00\nB-220,1,180.00\n";$doc->embedFileFromString( data: $csv, filename: 'line-items.csv', description: 'Source line items for INV-2026-0042', afRelationship: AFRelationship::Source->value,);
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/invoice-with-attachment.pdf');يُظهر القارئ line-items.csv في لوحة المرفقات لديه، وتوضح له العلاقة أنه مصدر الفاتورة.
نموذج التعليمات البرمجية — للإنتاج
قسم بعنوان «نموذج التعليمات البرمجية — للإنتاج»يرفق هذا المثال الكامل ملفًا من القرص ومجموعة بيانات في الذاكرة، ويتحقق من المسار على القرص مقابل دليل أساس مُدرَج في القائمة المسموحة قبل قراءته، ويبني حافظة مرفقات قابلة للفرز. ويلتقط أكثر استثناءات NextPDF تحديدًا مما قد يطرحه مسار المرفق، ثم يُعيد رمز خروج معرّفًا بدلًا من إخفاء الفشل.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Document\CollectionDictionary;use NextPDF\Document\CollectionSort;use NextPDF\Exception\CompressionException;use NextPDF\Exception\InvalidConfigException;use NextPDF\Exception\PageLayoutException;use NextPDF\Navigation\AFRelationship;
/** * Resolve a caller-supplied filename against an allowed base directory. * * Rejects path traversal and stream wrappers so an embedded attachment can * never read outside the directory the application owns. Returns the * canonical absolute path, or null when the input escapes the base. * * @param non-empty-string $baseDir Absolute path to the allowed directory. * @param non-empty-string $userName Untrusted filename from the request. */function resolveWithinBase(string $baseDir, string $userName): ?string{ $base = \realpath($baseDir); if ($base === false) { return null; }
$candidate = \realpath($base . \DIRECTORY_SEPARATOR . \basename($userName)); if ($candidate === false || !\str_starts_with($candidate, $base . \DIRECTORY_SEPARATOR)) { return null; }
return $candidate;}
$attachmentsDir = __DIR__ . '/attachments';$requestedFile = 'timesheet-2026-05.pdf';
$safePath = resolveWithinBase($attachmentsDir, $requestedFile);if ($safePath === null) { \fwrite(\STDERR, "Rejected attachment path: outside the allowed directory\n"); exit(2);}
try { $doc = Document::createStandalone(); $doc->setTitle('Invoice INV-2026-0042 with supporting documents'); $doc->addPage(); $doc->setFont('helvetica', 'B', 18); $doc->cell(0, 12, 'Invoice INV-2026-0042', newLine: true);
// 1. A validated file from disk: the supporting timesheet. $doc->embedFile( $safePath, 'Timesheet supporting the billed hours', );
// 2. An in-memory dataset generated at runtime. $lineItems = "sku,qty,unit_price\nA-100,3,49.00\nB-220,1,180.00\n"; $doc->embedFileFromString( data: $lineItems, filename: 'line-items.csv', description: 'Machine-readable line items', afRelationship: AFRelationship::Data->value, );
// Present both attachments as a sortable details portfolio. The sort // keys reference columns declared in the portfolio /Schema; here the // built-in filename and modification-date fields order the view. $portfolio = new CollectionDictionary( view: CollectionDictionary::VIEW_DETAILS, initialDocument: 'line-items.csv', sort: new CollectionSort( keys: ['_Filename', '_ModDate'], ascending: [true, false], ), ); // $portfolio->toPdfDictionary() yields the catalog /Collection literal, // shared with the unencrypted-wrapper envelope path.
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/invoice-portfolio.pdf'; $doc->save($out);
echo "Wrote {$out} with 2 attachments and a details portfolio\n";} catch (PageLayoutException $e) { // Unreadable path, oversized file, null byte, or a MIME-type name that // exceeds the 127-byte PDF name limit. \fwrite(\STDERR, "Attachment rejected: {$e->getMessage()}\n"); exit(1);} catch (CompressionException | InvalidConfigException $e) { // The attachment data could not be compressed, or a config value was invalid. \fwrite(\STDERR, "Write failed: {$e->getMessage()}\n"); exit(1);}CollectionDictionary وCollectionSort هما كائنا قيمة. يتحققان من مدخلاتهما عند الإنشاء ويتسلسلان إلى القيمة الحرفية /Collection في الفهرس، وهي التي تقود عرض الحافظة في القارئ.
الحالات الحدّية والمزالق
قسم بعنوان «الحالات الحدّية والمزالق»- إدخال المسار مسؤوليتك. يحرس
embedFile()ضد البايتات الصفرية وغلافات الدفق ويحلّ المسار الحقيقي، لكنه لا يفرض قائمة مسموحة لدليل الأساس. عندما يأتي المسار من طلب، تحقق منه أولًا، كما يفعل نموذج الإنتاج بـresolveWithinBase(). - يسري سقف 100 MB على
embedFile()فقط. أي ملف يتجاوز104,857,600بايت يطرحPageLayoutException. للحمولات الأكبر، دفّق البايتات بنفسك ومرّرها إلىembedFileFromString(). - أسماء أنواع MIME الطويلة مرفوضة. يصبح نوع MIME المكتشَف
/Subtypeالدفق المضمّن، وهو رمز اسم PDF محدود بـ 127 بايت بمقتضى ISO 32000-2. يبقى النوع الطويل على نحو غير معتاد (تقترب بعض صيغ Office من 90 بايت) دون الحد بمسافة آمنة، لكن أي نوع يُورَّد يدويًّا ويتجاوزه يطرحPageLayoutException. دع المحرك يكتشف النوع من الامتداد ما لم يكن لديك سبب محدد لتجاوزه. - العلاقة غير المعروفة تطرح استثناءً. يرفض
AFRelationship::coerce()أي قيمة خارج المجموعة الثابتة بدلًا من خفضها إلىUnspecified. مرّر حالة تعداد (AFRelationship::Source->value) لمنع خطأ مطبعي من الوصول إلى وقت التشغيل. - يجب أن تكون أسماء الملفات متمايزة في شجرة الأسماء. يتصادم مرفقان لهما الاسم المعروض نفسه في فهرس
EmbeddedFiles. امنح كل مرفق اسم ملف فريدًا. _ModDateيُسجَّل بالتوقيت العالمي المنسق (UTC). يقرأembedFile()وقت تعديل الملف ويكتبه بـgmdate()بحيث ينتج التجهيز نفسه تاريخًا مطابقًا بايتًا ببايت عبر الأجهزة، بصرف النظر عن إعداد المنطقة الزمنية.
الأداء
قسم بعنوان «الأداء»يُضغط كل مرفق مرة واحدة بـ gzcompress() عند المستوى 9 ويُكتب في صورة دفق وحيد عند save(). يهيمن الضغط على التكلفة ويتدرّج مع حجم الحمولة المرفقة، لا مع محتوى الصفحة. تبقى حفنة من الملفات الداعمة الصغيرة (مجموعات بيانات، وجداول بيانات، وجدول دوام بصيغة PDF) داخل ميزانية 2000 ms / 64 MB. عند وجود كثير من المرفقات الكبيرة، تكون البايتات المضمّنة هي حد الذاكرة الأدنى: فمرفق بحجم 50 MB محفوظ كسلسلة نصية يشغل هذا القدر على الأقل قبل الضغط. فضّل embedFileFromString() مع التوليد على دفعات بدل تحميل عدة ملفات كبيرة دفعة واحدة.
تُبنى شجرة الأسماء مرة واحدة عند save(). وحتى 64 مُدخلًا تبقى في شجرة مسطحة أحادية الجذر. وبعد ذلك، يقسّم NextPDF الشجرة إلى نطاقات Kids وLimits متوازنة، بحيث تبقى تكلفة الفهرسة لوغاريتمية لمجموعات المرفقات الكبيرة.
ملاحظات أمنية
قسم بعنوان «ملاحظات أمنية»- تحقق من كل مسار غير موثوق مقابل قائمة مسموحة. يقرأ التضمين أي ملف يمكن لعملية PHP الوصول إليه. ومن دون فحص دليل الأساس، يحوّل اسم ملف مُصاغ لهذا الغرض المرفق إلى تضمين ملف محلي (LFI). يُظهر نموذج الإنتاج حارس القائمة المسموحة؛ طبّقه كلما لم يكن اسم الملف ثابتًا وقت الترجمة.
- عامِل البايتات المرفقة بوصفها غير موثوقة على الجانب المستهلِك. الملف المضمّن معتم بالنسبة إلى NextPDF. فالمحرك لا يحلّله ولا ينفّذه. يكمن الخطر في الموضع الذي يُفتح فيه الملف لاحقًا. اضبط العلاقة والوصف بحيث يعرف المستهلك التالي ماهيّة كل مرفق قبل استخراجه.
- لا تضع أسرارًا في المرفقات أو الأوصاف. يُخزَّن اسم الملف والوصف والبايتات بصورة صريحة ما لم يكن المستند بأكمله مشفَّرًا. لحماية مرفق، شفّر المستند بسياسة أذونات (انظر النمط ذا الصلة). لا تضمّن بيانات اعتماد أو مفاتيح أو بيانات شخصية لا تضعها في الصفحة المعروضة.
- لا يحدث وصول إلى الشبكة في هذا النمط. تُقرأ كل بايت من المسار المحلي المتحقَّق منه أو تُورَّد في الذاكرة.
المطابقة
قسم بعنوان «المطابقة»| البيان | المواصفة | البند | reference_id |
|---|---|---|---|
ترتبط تدفقات الملفات المضمّنة بالمستند من خلال مُدخل EmbeddedFiles في قاموس الأسماء. | ISO 32000-2 | 7.11.4 | |
تربط شجرة الأسماء EmbeddedFiles الأسماء بمواصفات الملفات التي يشير مُدخل EF فيها إلى دفق ملف مضمّن. | ISO 32000-2 | 7.7.4 | |
يتطلب الملف المرتبط قيمة AFRelationship من المجموعة الثابتة في PDF 2.0. | PDF Association AN002 | 3 | |
يتحكم قاموس Collection في الفهرس في عرض المرفقات في صورة حافظة. | ISO 32000-2 | 7.11.6 |
ملف تعريف القابلية لإعادة الإنتاج — بنيوي. يتباين /ID الذيل، وذرات التاريخ لكل عملية حفظ، و/ModDate الدفق المضمّن بين عمليات التشغيل، لذلك تجرّد المقارنة البنيوية تلك القيم قبل مقارنة رسم بياني الكائنات. يصف هذا النمط كيف يُنتج NextPDF البنية. ولا يؤكد مطابقة شاملة لـ PDF/A-4f، التي تعتمد على المستند الكامل. وللحصول على ملف تعريف أرشيفي يتطلب أن يعلن كل مرفق علاقة ووصفًا، انظر نمط PDF/A-4.