دمج ملفات PDF خارجية أو إلحاق صفحات من مستندات قائمة
نظرة سريعة
قسم بعنوان «نظرة سريعة»لديك عدة ملفات PDF على القرص، وتحتاج إلى ملف PDF واحد. تدمج هذه الوصفة المستندات القائمة من البداية إلى النهاية عبر سطح الدمج في Core، وهو NextPDF\Document\PdfMerger. مرِّر سلاسل بايتات PDF خام. يعيد الدامج ترقيم كل كائن لتفادي التعارضات، ويبني شجرة صفحات واحدة وجدول مراجع متقاطعة واحدًا، ويُرجِع NextPDF\Document\MergeResult يمكنك كتابته إلى القرص أو بثُّه إلى عميل.
يغطّي السطح نفسه المهام الثلاث التي تحتاجها في الأغلب:
- الدمج لقائمة مرتبة من ملفات PDF في مستند واحد.
- الإلحاق لملف PDF ثانٍ بعد ملف PDF أساسي.
- الإضافة في المقدمة للصفحات بوضع المستند الجديد أولًا في ترتيب الإدخال.
يجري الدمج داخل العملية، من دون متصفح بلا واجهة أو استدعاء شبكي. تحتاج إلى تثبيت Core (composer require nextpdf/core:^3) وملفّي PDF أو أكثر قابلين للقراءة.
التثبيت
قسم بعنوان «التثبيت»composer require nextpdf/core:^3نظرة مفاهيمية
قسم بعنوان «نظرة مفاهيمية»ينظّم ملف PDF الصفحات في شجرة صفحات جذرها عقدة /Pages، ويحدّد موقع كل كائن غير مباشر عبر جدول مراجع متقاطعة. عند دمج مستندَي مصدر، تتداخل أرقام كائناتهما. يحتوي الملفان دائمًا تقريبًا على كائن 1 0 obj، و/Catalog، وعقدة /Pages. إذا اكتفيت بضمّ البايتات، فستنتج ملفًا تالفًا لأن المراجع لن تعود تشير إلى الكائنات التي تحدّدها.
يعالج PdfMerger هذا التداخل. يستخرج كائنات الصفحات من كل مدخل، ويعيد ترقيم كل كائن ضمن فضاء عناوين واحد، ويعيد كتابة مرجع /Parent لكل صفحة ليشير إلى عقدة /Pages مدمجة واحدة، ويُصدر كتالوجًا واحدًا وشجرة صفحات واحدة ومُذيِّلًا واحدًا. الناتج مستند جديد بنيويًا، لا مجرد ضمّ متتابع للبايتات.
قاعدة الترتيب بسيطة: تظهر الصفحات بالترتيب نفسه لملفاتها المصدرية في قائمة الإدخال. للإلحاق، ضع المستند الأساسي أولًا. وللإضافة في المقدمة، ضع المستند الجديد أولًا. لا توجد طريقة منفصلة للإضافة في المقدمة؛ فترتيب الإدخال هو عنصر التحكم الوحيد الذي تحتاجه.
سطح API
قسم بعنوان «سطح API»يكشف new NextPDF\Document\PdfMerger() طريقتين.
- يجمع
merge(list<string> $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000): MergeResultقائمة مرتبة من سلاسل بايتات PDF خام. يحدّ المُعامِلان المقيِّدان عدد الملفات وإجمالي حجم الإدخال. كلاهما يستخدم قيمًا افتراضية آمنة للإنتاج؛ شدِّدهما بحسب كل حِمل عمل. append(string $basePdf, string $appendPdf): MergeResultمُغلِّف ميسِّر يدمج مستندين بالضبط بالترتيب. وهو مكافئ لـmerge([$basePdf, $appendPdf]).
تُرجِع الطريقتان كلتاهما NextPDF\Document\MergeResult، وهو كائن readonly يحمل $pdfData (البايتات المدمجة)، و$totalPages، و$sourceCount، و$mergedSize، والمساعد isValid() الذي يؤكّد أن الناتج يبدأ بترويسة %PDF.
المدخلات هي سلاسل بايتات خام، لا مسارات ملفات. اقرأ الملف بنفسك باستخدام file_get_contents()، أو اجلب البايتات من تخزين الكائنات. يُبقي ذلك الدامج خاليًا من افتراضات نظام الملفات، ويتيح لك دمج مستندات لا تلامس القرص أبدًا.
إذا احتجت إلى استيراد صفحة واحدة من ملف PDF خارجي بوصفها Form XObject قابلة لإعادة الاستخدام، مثلًا لختم صفحة ترويسة خلف محتوى مُولَّد، فاستخدم عقد المستورِد عبر الحزم NextPDF\Contracts\ImportedFormObjectInterface، المُنفَّذ في مستورِدات مثل nextpdf/artisan. ولتركيب مستند كامل بصفحات كاملة، استخدم سطح PdfMerger الموثَّق هنا.
عيّنة الشيفرة — البدء السريع
قسم بعنوان «عيّنة الشيفرة — البدء السريع»تقرأ هذه العيّنة ملفين وتكتب الناتج المدمج. تترك معالجة الأخطاء خارجها لإظهار شكل الاستدعاء؛ أما العيّنة الإنتاجية أدناه فتضيف كل الضمانات.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Document\PdfMerger;
$merger = new PdfMerger();
$result = $merger->merge([ file_get_contents(__DIR__ . '/cover.pdf'), file_get_contents(__DIR__ . '/body.pdf'), file_get_contents(__DIR__ . '/appendix.pdf'),]);
file_put_contents(__DIR__ . '/combined.pdf', $result->pdfData);
printf("Merged %d source(s) into %d page(s).\n", $result->sourceCount, $result->totalPages);عيّنة الشيفرة — الإنتاج
قسم بعنوان «عيّنة الشيفرة — الإنتاج»يبني هذا البرنامج المكتفي ذاتيًا مستندين صغيرين في الذاكرة، لذلك يعمل من دون ملف خارجي. يدمجهما، ويتحقق من النتيجة، ويكتب الناتج. يلتقط الاستثناءين اللذين يطلقهما سطح الدمج، ويعيد إطلاق كلٍّ منهما بسياق بدلًا من ابتلاعه. استبدل المدخلات الموجودة في الذاكرة بقراءاتك الخاصة عبر file_get_contents() أو بجلب من تخزين الكائنات، وأوصِل الناتج بطبقة الاستجابة أو التخزين لديك.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Document\MergeResult;use NextPDF\Document\PdfMerger;use NextPDF\Exception\PageLayoutException;use NextPDF\Exception\WriterException;
/** * Build a tiny labelled PDF so the program is self-contained. * * In your own code, replace calls to this helper with reads of the external * PDFs you want to combine, for example file_get_contents($path). */function buildSample(string $label, int $pages): string{ $doc = Document::createStandalone(); $doc->setTitle($label);
for ($page = 1; $page <= $pages; $page++) { $doc->addPage(); $doc->setFont('helvetica', '', 12); $doc->cell(0, 10, sprintf('%s - page %d', $label, $page), newLine: true); }
return $doc->getPdfData();}
// Validate the input set before touching the merger. An empty set is a// configuration error, not an empty success./** @var list<string> $sources Raw PDF byte strings, in output order. */$sources = [ buildSample('Cover', 1), // first in the list -> first in the output (prepend position) buildSample('Body', 2), buildSample('Appendix', 1), // last in the list -> appended after the body];
if ($sources === []) { throw new RuntimeException('No source PDFs supplied to merge.');}
$merger = new PdfMerger();
try { // Bound the merge deliberately: at most 50 files, 100 MB total input. $result = $merger->merge($sources, maxFiles: 50, maxTotalBytes: 100_000_000);} catch (PageLayoutException $e) { // Raised when the list is empty or an input does not begin with %PDF. throw new RuntimeException( sprintf('Merge rejected an input: %s', $e->getConstraint()), previous: $e, );} catch (WriterException $e) { // Raised when the total input size exceeds the configured byte cap. throw new RuntimeException( sprintf('Merge exceeded its size budget at stage "%s".', $e->getWriterState()), previous: $e, );}
if (!$result->isValid()) { throw new RuntimeException('Merged output failed its structural header check.');}
emitResult($result);
/** * Write the merged document to the cookbook side-channel, or to a default file. */function emitResult(MergeResult $result): void{ printf( "Merged %d source(s) into %d page(s), %d bytes.\n", $result->sourceCount, $result->totalPages, $result->mergedSize, );
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT'); $path = $out !== false && $out !== '' ? $out : __DIR__ . '/combined.pdf';
if (file_put_contents($path, $result->pdfData) === false) { throw new RuntimeException(sprintf('Could not write merged PDF to "%s".', $path)); }}الخرج القياسي المتوقَّع (إجمالي الصفحات هو مجموع أعداد صفحات المصادر، ويعتمد حجم البايتات على طريقة البناء):
Merged 3 source(s) into 4 page(s), <n> bytes.الحالات الحدّية والمزالق
قسم بعنوان «الحالات الحدّية والمزالق»- المدخلات بايتات، لا مسارات. يأخذ
merge()سلاسل PDF خام. اقرأ الملف بـfile_get_contents()أولًا. تمرير سلسلة مسار يجعل المدخل يفشل في فحص ترويسة%PDFويُطلِقPageLayoutException. - الترتيب هو ترتيب الإخراج. تستقر الصفحات بالترتيب الذي تظهر به ملفاتها المصدرية في القائمة. لا توجد طريقة منفصلة للإضافة في المقدمة: ضع المستند الجديد أولًا للإضافة في المقدمة، أو أخيرًا للإلحاق.
- القائمة الفارغة خطأ. قيمة
$pdfFilesالفارغة تُطلِقPageLayoutException، لا نتيجة فارغة. تحقَّق من المجموعة قبل أن تستدعي الدامج. - كل مدخل يُتحقَّق منه مقدَّمًا. يجب أن يكون كل عنصر غير فارغ وأن يبدأ بـ
%PDF. يُطلِق أول مدخل فاشلPageLayoutExceptionمع القيد المنتهَك، ولا يُدمَج أي شيء. - الحدود تُطلِق استثناءً بدلًا من البتر. تجاوز
maxFilesيُطلِق استثناءً عبر حارس الموارد الداخلي، وتجاوزmaxTotalBytesيُطلِقWriterException. لا يُسقِط الدامج الملفات بصمت ولا يقتطع البايتات أبدًا، فاضبط الحدّين كليهما بحسب حِمل عملك. - الناتج جديد بنيويًا، لا ثابت على مستوى البايتات. يحمل المستند المدمج كتالوجًا وشجرة صفحات ومُذيِّلًا جديدة. تشغيلان على المدخلات نفسها يكونان متكافئين بنيويًا، لكن لا يُضمَن أن يكونا متطابقين على مستوى البايتات. لذلك تُعلِن هذه الوصفة ملف قابلية إعادة إنتاج
structural. - التعليقات التوضيحية على مستوى الصفحة والموارد المشتركة. يركّب الدمج كائنات الصفحات في شجرة واحدة. البِنى على مستوى المستند التي تعيش خارج كائنات الصفحات في ملف مصدري لا تُنقَل. عندما تحتاج إلى استيراد صفحة واحدة بوصفها رسمًا قابلًا لإعادة الاستخدام مع موارده، استخدم مسار
ImportedFormObjectInterfaceعبر مستورِد مثلnextpdf/artisan.
الأداء
قسم بعنوان «الأداء»الدمج خطّي بإجمالي عدد الصفحات. يهيمن على العمل التحليل وإعادة ترقيم الكائنات، لا احتفاظ الدامج بسجلّاته الخاصة. تتبع ذروة الذاكرة إجمالي بايتات الإدخال، لأن كل مصدر يُحتفَظ به في الذاكرة كسلسلة بينما يُجمَّع الناتج. يُبقي حارس maxTotalBytes تلك الذروة محدودة. لمسارات المعالجة عالية الحجم، اضبط maxFiles وmaxTotalBytes على أصغر قيم يحتاجها حِمل عملك، كي تفشل الدفعة المشوَّهة أو مفرطة الحجم بسرعة بدلًا من استنزاف الذاكرة. يقع الدمج الصغير المعتاد ضمن ميزانية 1500 ms للزمن الكلي و64 MB للذروة.
ملاحظات الأمان
قسم بعنوان «ملاحظات الأمان»يجري الدمج داخل العملية؛ لا تغادر أي بايتات المستند المضيف، ولا يُجرى أي استدعاء شبكي. تعامَل مع كل ملف PDF خارجي كمدخل غير موثوق:
- أبقِ الحدود مشدَّدة.
maxFilesوmaxTotalBytesهما خطُّ دفاعك الأول ضد مدخلات حجب الخدمة. لأي سطح يقبل عمليات الرفع، اضبطهما على سقفك الحقيقي، لا على القيم الافتراضية السخيّة. - تحقَّق قبل أن تثق. نجاح الدمج يعني أن البايتات دُمِجت، لا أن المدخلات آمنة. مرِّر المدخلات غير الموثوقة عبر فاحص Core أولًا. راجع تحليل ملف PDF وفحصه للحصول على مسح فرز محدود يُظهر التشفير والتواقيع وعلامات الخطر قبل المعالجة الأثقل.
- لا تُدرِج مدخلات المستخدم في مسار أبدًا. تكتب هذه الوصفة إلى مسار ثابت أو إلى القناة الجانبية لدفتر الوصفات. اشتقّ مسارات الإخراج من قيم يتحكم بها الخادم، لا من حقل طلب أبدًا، لتفادي اجتياز المسار.
- لا أسرار في المستند. لا تُضمِّن بيانات اعتماد أو رموزًا أو معرِّفات داخلية في مستند مدمج تُرجِعه إلى عميل.
المطابقة
قسم بعنوان «المطابقة»لا تطرح هذه الوصفة أي ادعاء معياري خاص بها. تركّب مستندات قائمة عبر سطح الدمج في Core وتتحقق من النتيجة بفحص ترويسة MergeResult::isValid(). نموذج شجرة الصفحات الذي يعيد PdfMerger بناءه هو بنية شجرة صفحات PDF 2.0 الموصوفة في مرجع /modules/core/document/. للحصول على قراءة بنيوية لأي مستند مُدخَل أو مُخرَج، بما في ذلك الإصدار وعدد الصفحات والتشفير وأعلام التوقيع، استخدم فاحص Core الموثَّق في تحليل ملف PDF وفحصه.
انظر أيضًا
قسم بعنوان «انظر أيضًا»- مرجع وحدة المستند — سطح التقسيم والدمج وأجزاء المستند الكامل.
- تحليل ملف PDF وفحصه — افرز المدخلات غير الموثوقة قبل أن تدمجها.
- معالجة الأخطاء الواعية بالاستثناءات — تسلسل استثناءات NextPDF خلف
PageLayoutExceptionوWriterException. - بناء مستند متعدد الصفحات — ألِّف الصفحات التي تدمجها بعد ذلك.