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

تضمين الملفات وإنشاء حافظات PDF

يُرفِق هذا النمط ملفًا واحدًا أو أكثر بملف ⁨PDF⁩، ويرتّبها في صورة حافظة ⁨PDF⁩ عندما تكون لديك عدة مرفقات. استخدمه عندما يحتاج المستند إلى تضمين أدلة داعمة داخل الملف نفسه: فاتورة مع جدول الدوام الذي استندت إليه، أو ورقة بيانات منتج مع تصدير من التصميم بمساعدة الحاسوب (⁨CAD⁩)، أو سجل أرشيفي يحتفظ بجدول البيانات المصدري إلى جانب التقرير المعروض.

يوفّر ⁨NextPDF⁩ نقطتي دخول على كائن المستند. يقرأ embedFile() ملفًا من القرص، بينما يضمّن embedFileFromString() بايتات موجودة في الذاكرة وتولّدها وقت التشغيل. ويسجّل كلاهما المرفق. وعند save()، يكتب المحرك كل مرفق في صورة دفق ملف مضمّن، ويغلّفه في قاموس مواصفات ملف، ويربط كل مواصفة في شجرة الأسماء EmbeddedFiles على مستوى المستند. يعرّف ⁨ISO 32000-2⁩ شجرة الأسماء هذه بوصفها الموضع الذي ترتبط فيه تدفقات الملفات المضمّنة بالمستند ككل من خلال قاموس الأسماء.

هذه إمكانية أساسية بلا قيد تجاري. وواجهة برمجة التطبيقات (⁨API⁩) الخاصة بالمرفقات مستقرة منذ 1.0.0 وتعمل عبر مصفوفة التوافق 8.1-8.4.

Terminal window
composer require nextpdf/core:^3

لا يلزم أي امتداد اختياري.

يمرّ المرفق عبر ثلاث بنى في ⁨PDF.⁩ تساعدك معرفتها على فحص المخرجات وتنقيح أي ملف غير مطابق.

  1. دفق الملف المضمّن. البايتات الخام للملف المرفق، مضغوطة بـ ⁨Flate⁩، ومكتوبة في صورة كائن دفق يكون /Type فيه هو /EmbeddedFile. يسجّل ⁨NextPDF⁩ الحجم الأصلي، ومجموع تحقّق ⁨MD5⁩، وتاريخ التعديل في قاموس معاملات الدفق. ويرمّز نوع امتدادات بريد الإنترنت متعددة الأغراض (⁨MIME⁩) المكتشَف بوصفه /Subtype الدفق.
  2. قاموس مواصفات الملف. الغلاف الذي يحمل البيانات الوصفية. فهو يحمل اسم الملف المعروض (/F وصيغة ⁨Unicode⁩ منه /UF)، ووصفًا مقروءًا للبشر (/Desc)، ومرجعًا إلى الدفق المضمّن (/EF)، وعلاقة الملف بالمستند المضيف (/AFRelationship).
  3. شجرة الأسماء 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.⁩