Externe PDFs zusammenführen oder Seiten aus bestehenden Dokumenten anhängen
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Sie haben mehrere PDF-Dateien auf der Festplatte und benötigen ein einziges PDF. Dieses Recipe fügt bestehende Dokumente mit der Core-Merge-Schnittstelle nahtlos aneinander: NextPDF\Document\PdfMerger. Sie übergeben rohe PDF-Byte-Strings. Der Merger nummeriert jedes Objekt neu, um Kollisionen zu vermeiden, baut einen einzigen Seitenbaum und eine einzige Cross-Reference-Tabelle auf und gibt ein NextPDF\Document\MergeResult zurück, das Sie auf die Festplatte schreiben oder an einen Client streamen.
Dieselbe Schnittstelle deckt die drei Aufgaben ab, die Entwickler am häufigsten benötigen:
- Merge – eine geordnete Liste von PDFs zu einem Dokument zusammenführen.
- Append – ein zweites PDF nach einem Basis-PDF anhängen.
- Prepend – Seiten voranstellen, indem Sie das neue Dokument an den Anfang der Eingabereihenfolge setzen.
Das Zusammenführen läuft im Prozess, ohne headless Chrome und ohne Netzwerkaufruf. Sie benötigen eine Core-Installation (composer require nextpdf/core:^3) und zwei oder mehr lesbare PDF-Dateien.
Installation
Abschnitt betitelt „Installation“composer require nextpdf/core:^3Konzeptioneller Überblick
Abschnitt betitelt „Konzeptioneller Überblick“Ein PDF organisiert seine Seiten in einem Seitenbaum, dessen Wurzel ein /Pages-Knoten ist, und lokalisiert jedes indirekte Objekt über eine Cross-Reference-Tabelle. Wenn Sie zwei Quelldokumente zusammenführen, überschneiden sich ihre Objektnummern. Beide Dateien enthalten fast immer ein Objekt 1 0 obj, einen /Catalog und einen /Pages-Knoten. Ein einfaches Aneinanderhängen der Bytes würde eine beschädigte Datei erzeugen, weil die Referenzen nicht mehr auf die Ziele zeigen, die ihre Nummern vorgeben.
PdfMerger behebt dieses Problem. Er extrahiert die Seitenobjekte aus jeder Eingabe, nummeriert jedes Objekt in einen einzigen Adressraum um, schreibt die /Parent-Referenz jeder Seite so um, dass sie auf einen einzigen zusammengeführten /Pages-Knoten zeigt, und gibt einen Katalog, einen Seitenbaum und einen Trailer aus. Die Ausgabe ist ein strukturell frisches Dokument, keine bloß zusammengeheftete Verkettung.
Die Regel für die Reihenfolge ist einfach: Seiten erscheinen in der Reihenfolge, in der ihre Quelldateien in der Eingabeliste stehen. Zum Anhängen stellen Sie das Basisdokument an den Anfang. Zum Voranstellen stellen Sie das neue Dokument an den Anfang. Es gibt keine eigene Prepend-Methode, denn die Eingabereihenfolge ist die einzige Steuerung, die Sie benötigen.
API-Oberfläche
Abschnitt betitelt „API-Oberfläche“new NextPDF\Document\PdfMerger() stellt zwei Methoden bereit.
merge(list<string> $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000): MergeResultführt eine geordnete Liste roher PDF-Byte-Strings zusammen. Die beiden Grenzparameter begrenzen die Anzahl der Dateien und die gesamte Eingabegröße. Beide haben standardmäßig sichere Produktionswerte; passen Sie sie je nach Workload enger an.append(string $basePdf, string $appendPdf): MergeResultist ein praktischer Wrapper, der genau zwei Dokumente in dieser Reihenfolge zusammenführt. Er ist gleichwertig zumerge([$basePdf, $appendPdf]).
Beide geben ein NextPDF\Document\MergeResult zurück, ein readonly-Objekt, das $pdfData (die zusammengeführten Bytes), $totalPages, $sourceCount, $mergedSize sowie den Helfer isValid() enthält, der bestätigt, dass die Ausgabe mit dem %PDF-Header beginnt.
Eingaben sind rohe Byte-Strings, keine Dateipfade. Sie lesen die Datei selbst mit file_get_contents() (oder holen die Bytes aus einem Object Storage). Das hält den Merger frei von Annahmen über das Dateisystem und ermöglicht es Ihnen, Dokumente zusammenzuführen, die nie die Festplatte berühren.
Wenn Sie eine einzelne Seite aus einem externen PDF als wiederverwendbares Form-XObject importieren müssen – zum Beispiel, um eine Briefkopfseite hinter generierten Inhalt zu stempeln – verwenden Sie den paketübergreifenden Importer-Vertrag NextPDF\Contracts\ImportedFormObjectInterface, der von Importern wie nextpdf/artisan implementiert wird. Die Komposition ganzer Dokumente und ganzer Seiten ist die hier dokumentierte PdfMerger-Schnittstelle.
Codebeispiel – Schnellstart
Abschnitt betitelt „Codebeispiel – Schnellstart“Dieses Beispiel liest zwei Dateien ein und schreibt das zusammengeführte Ergebnis. Es lässt die Fehlerbehandlung weg, um den Aufruf zu zeigen; das Produktionsbeispiel weiter unten ergänzt die vollständigen Absicherungen.
<?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);Codebeispiel – Produktion
Abschnitt betitelt „Codebeispiel – Produktion“Dies ist ein eigenständiges Programm. Es baut zwei kleine Dokumente im Speicher auf, damit es ohne externe Dateien lauffähig ist, führt sie zusammen, validiert das Ergebnis und schreibt die Ausgabe. Es fängt die beiden Exceptions ab, die die Merge-Schnittstelle auslöst, und wirft jede mit Kontext erneut, statt sie zu verschlucken. Ersetzen Sie die In-Memory-Eingaben durch Ihre eigenen file_get_contents()-Lesevorgänge (oder Object-Storage-Abrufe) und verbinden Sie die Ausgabe mit Ihrer Response- oder Storage-Schicht.
<?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)); }}Erwartete STDOUT-Ausgabe (die Seitengesamtzahl ist die Summe der Seiten der Quellen, und die Byte-Größe hängt vom Build ab):
Merged 3 source(s) into 4 page(s), <n> bytes.Sonderfälle & Fallstricke
Abschnitt betitelt „Sonderfälle & Fallstricke“- Eingaben sind Bytes, keine Pfade.
merge()nimmt rohe PDF-Strings entgegen. Lesen Sie die Datei zuerst mitfile_get_contents(). Das Übergeben eines Pfad-Strings führt dazu, dass die Eingabe an der%PDF-Header-Prüfung scheitert, und löstPageLayoutExceptionaus. - Die Reihenfolge ist die Ausgabereihenfolge. Seiten landen in der Reihenfolge, in der ihre Quelldateien in der Liste stehen. Es gibt keine Prepend-Methode: Stellen Sie das neue Dokument an den Anfang, um es voranzustellen, und an das Ende, um es anzuhängen.
- Eine leere Liste ist ein Fehler. Ein leeres
$pdfFileslöstPageLayoutExceptionaus, nicht ein leeres Ergebnis. Validieren Sie die Menge, bevor Sie den Aufruf ausführen. - Jede Eingabe wird vorab validiert. Jeder Eintrag darf nicht leer sein und muss mit
%PDFbeginnen. Die erste Eingabe, die fehlschlägt, löstPageLayoutExceptionmit der verletzten Bedingung aus, und nichts wird zusammengeführt. - Grenzwertverletzungen lösen Fehler aus, statt Daten abzuschneiden. Das Überschreiten von
maxFileslöst den internen Ressourcen-Guard aus, und das Überschreiten vonmaxTotalByteslöstWriterExceptionaus. Der Merger lässt niemals stillschweigend Dateien fallen und schneidet keine Bytes ab; stimmen Sie daher beide Grenzen auf Ihren Workload ab. - Die Ausgabe ist strukturell frisch, nicht Byte-stabil. Das zusammengeführte Dokument trägt einen neuen Katalog, Seitenbaum und Trailer. Zwei Durchläufe über dieselben Eingaben sind strukturell gleich, aber nicht garantiert byte-genau gleich – deshalb deklariert dieses Recipe ein
structural-Reproduzierbarkeitsprofil. - Seitenbezogene Annotationen und gemeinsam genutzte Ressourcen. Die Zusammenführung setzt die Seitenobjekte zu einem Baum zusammen. Dokumentbezogene Strukturen, die in einer Quelldatei außerhalb der Seitenobjekte liegen, werden nicht übernommen. Wenn Sie eine einzelne Seite als wiederverwendbare Grafik mit ihren Ressourcen importieren müssen, verwenden Sie den
ImportedFormObjectInterface-Pfad über einen Importer wienextpdf/artisan.
Performance
Abschnitt betitelt „Performance“Das Zusammenführen ist linear in der Gesamtseitenzahl und wird vom Parsen und der Objekt-Neunummerierung bestimmt, nicht von der internen Buchführung des Mergers. Der Spitzenspeicher richtet sich nach den gesamten Eingabe-Bytes, weil jede Quelle als String im Speicher gehalten wird, während die Ausgabe zusammengesetzt wird. Der maxTotalBytes-Guard begrenzt diesen Spitzenwert. Setzen Sie für Pipelines mit hohem Volumen maxFiles und maxTotalBytes auf die kleinsten Werte, die Ihr Workload benötigt, damit ein fehlerhafter oder überdimensionierter Batch schnell scheitert, statt den Speicher zu erschöpfen. Eine typische kleine Zusammenführung bleibt innerhalb eines Budgets von 1500 ms Laufzeit und 64 MB Spitzenspeicher.
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“Die Zusammenführung läuft im Prozess; keine Dokument-Bytes verlassen den Host, und es erfolgt kein Netzwerkaufruf. Behandeln Sie jedes externe PDF als nicht vertrauenswürdige Eingabe:
- Halten Sie die Grenzen eng.
maxFilesundmaxTotalBytessind Ihre erste Verteidigungslinie gegen eine Denial-of-Service-Eingabe. Setzen Sie sie an jeder Schnittstelle, die Uploads annimmt, auf Ihre reale Obergrenze, nicht auf die großzügigen Standardwerte. - Validieren Sie, bevor Sie vertrauen. Eine erfolgreiche Zusammenführung bedeutet, dass die Bytes kombiniert wurden, nicht, dass die Eingaben sicher sind. Lassen Sie nicht vertrauenswürdige Eingaben zuerst durch den Core-Inspektor laufen. Siehe Ein PDF parsen und inspizieren für einen begrenzten Triage-Scan, der Verschlüsselung, Signaturen und Risikomarker vor der schwereren Verarbeitung kennzeichnet.
- Interpolieren Sie niemals Benutzereingaben in einen Pfad. Dieses Recipe schreibt an einen festen Pfad oder den Cookbook-Seitenkanal. Leiten Sie Ausgabepfade aus servergesteuerten Werten ab, niemals aus einem Request-Feld, damit Sie Path Traversal vermeiden.
- Keine Geheimnisse im Dokument. Betten Sie keine Anmeldedaten, Tokens oder internen Bezeichner in ein zusammengeführtes Dokument ein, das Sie an einen Client zurückgeben.
Konformität
Abschnitt betitelt „Konformität“Dieses Recipe erhebt keinen eigenen normativen Standardanspruch. Es setzt bestehende Dokumente über die Core-Merge-Schnittstelle zusammen und validiert das Ergebnis mit der MergeResult::isValid()-Header-Prüfung. Das Seitenbaum-Modell, das PdfMerger neu aufbaut, ist die PDF 2.0 Seitenbaum-Struktur, die in der Referenz /modules/core/document/ beschrieben wird. Für einen strukturellen Blick auf jedes Eingabe- oder Ausgabedokument – Version, Seitenzahl, Verschlüsselungs- und Signatur-Flags – verwenden Sie den Core-Inspektor, der in Ein PDF parsen und inspizieren dokumentiert ist.
Siehe auch
Abschnitt betitelt „Siehe auch“- Referenz des Document-Moduls – die vollständige Schnittstelle zum Aufteilen und Zusammenführen sowie für Dokumentteile.
- Ein PDF parsen und inspizieren – nicht vertrauenswürdige Eingaben triagieren, bevor du sie zusammenführst.
- Exception-bewusste Fehlerbehandlung – die NextPDF-Exception-Hierarchie hinter
PageLayoutExceptionundWriterException. - Ein mehrseitiges Dokument erstellen – die Seiten erstellen, die Sie dann kombinieren.