Ein generiertes PDF aus einem Controller zurückgeben
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Generieren Sie ein PDF innerhalb einer Controller-Action und geben Sie es als HTTP-Antwort zurück. Jede Framework-Integration enthält einen PdfResponse-Helper, der das Response-Objekt für das jeweilige Framework aufbaut, Content-Type: application/pdf setzt, die Security-Header anhängt und den Dateinamen bereinigt. Dieser Leitfaden behandelt die drei Auslieferungsmodi — Inline-Vorschau, Datei-Download und gestreamte Auslieferung — für Laravel, Symfony und CodeIgniter 4.
Prüfen Sie zuerst diese Voraussetzungen, damit während der Umsetzung keine unerwarteten Probleme auftreten:
- NextPDF-Core ist installiert.
- Eine Framework-Integration ist installiert, und ihr Service Provider, Bundle oder Service wird erkannt. Prüfen Sie die Erkennung auf der Installationsseite Ihres Frameworks, bevor Sie beginnen.
- Der gestreamte Modus benötigt keine zusätzlichen Pakete. Jede Integration bringt die gestreamte Variante zusätzlich zur gepufferten Variante mit.
Dies ist eine Anleitung. Sie setzt voraus, dass Sie bereits wissen, wie Sie in Ihrem Framework einen Request an einen Controller routen. Ein erstes lauffähiges Beispiel pro Framework finden Sie im Framework-Quickstart, der unter „Siehe auch“ verlinkt ist.
Installation
Abschnitt betitelt „Installation“Installieren Sie die Integration, die zu Ihrem Framework passt. Führen Sie einen der folgenden Befehle aus.
composer require nextpdf/laravelcomposer require nextpdf/symfonycomposer require nextpdf/codeigniterVeröffentlichen Sie bei Laravel nach der Installation die Konfiguration.
php artisan vendor:publish --tag=nextpdf-configSymfony registriert das Bundle automatisch über Flex, und CodeIgniter erkennt den Service automatisch. Bestätigen Sie die Erkennung auf der Installationsseite Ihres Frameworks, bevor Sie fortfahren.
Konzeptioneller Überblick
Abschnitt betitelt „Konzeptioneller Überblick“Jede Framework-Integration folgt demselben dreiteiligen Aufbau: eine Möglichkeit, ein frisches Dokument zu beziehen, eine Reihe von inhaltserzeugenden Aufrufen auf diesem Dokument und eine PdfResponse-Factory, die das fertige Dokument in eine HTTP-Antwort umwandelt. Die Dokument-API (addPage(), cell(), setFont()) ist die Oberfläche der Core-Engine und über alle Frameworks hinweg identisch. Die Response-Factory unterscheidet sich nur durch die Response-Klasse, die sie zurückgibt, weil jedes Framework seinen eigenen HTTP-Antworttyp hat.
PdfResponse bietet drei Auslieferungsmodi. Inline setzt einen Content-Disposition: inline-Header, sodass der Browser das PDF in einem Viewer-Tab anzeigt. Download setzt Content-Disposition: attachment, sodass der Browser die Datei speichert. Streamed gibt den PDF-Body in festen Chunks aus, statt das gesamte Dokument im Speicher zu puffern. Wählen Sie diesen Modus für große Dokumente, bei denen der Spitzenspeicherbedarf wichtiger ist als eine bekannte Content-Length.
Beziehen Sie das Dokument über den idiomatischen Auflösungspfad des Frameworks:
- Laravel — lösen Sie
NextPDF\Contracts\DocumentFactoryInterfacemitapp(...)aus dem Container auf und rufen Siecreate()auf. Der Aufruf gibt ein frischesNextPDF\Core\Documentzurück — den konkreten Typ, den diePdfResponse-Factories akzeptieren. - Symfony — injizieren Sie
NextPDF\Symfony\Service\PdfFactoryund rufen Siecreate()auf. Der Aufruf gibt ein frischesNextPDF\Core\Documentzurück, auf das die konfigurierten Dokument-Standardwerte bereits angewendet sind. - CodeIgniter 4 — lösen Sie die
Pdf-Library überServices::pdf()(oder denpdf()-Helper) auf, oder beziehen Sie ein nacktes Dokument überpdf_document().
API-Oberfläche
Abschnitt betitelt „API-Oberfläche“| Aspekt | Laravel | Symfony | CodeIgniter 4 |
|---|---|---|---|
| Frisches Dokument | app(DocumentFactoryInterface::class)->create() | PdfFactory::create() | pdf_document() / Services::pdf()->document() |
| Inline-Antwort | PdfResponse::inline($doc, $name) | PdfResponse::inline($doc, $name) | $pdf->inline($name) / PdfResponse::inline($doc, $name) |
| Download-Antwort | PdfResponse::download($doc, $name) | PdfResponse::download($doc, $name) | $pdf->download($name) / PdfResponse::download($doc, $name) |
| Gestreamt inline | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) |
| Gestreamter Download | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) |
| Rückgabetyp | Illuminate\Http\Response (gestreamt: StreamedResponse) | Symfony\Component\HttpFoundation\Response (gestreamt: StreamedResponse) | CodeIgniter\HTTP\DownloadResponse |
Die Laravel-PdfResponse liegt unter NextPDF\Laravel\Http\PdfResponse, die Symfony-Variante unter NextPDF\Symfony\Http\PdfResponse und die CodeIgniter-Variante unter NextPDF\CodeIgniter\Http\PdfResponse. Die Security-and-Operations-Seite jeder Integration dokumentiert das vollständige Response-Verhalten pro Paket — Header-Set, Disposition-Regeln und Dateinamen-Bereinigung. Diese Seiten sind unter „Siehe auch“ verlinkt.
Codebeispiel — Quick Start
Abschnitt betitelt „Codebeispiel — Quick Start“Hier sehen Sie die minimale Download-Action in jedem Framework. Die Dokument-Aufrufe verwenden dieselbe Core-Oberfläche. Nur das Controller-Gerüst unterscheidet sich.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Laravel\Http\PdfResponse;
final class ReportController extends Controller{ public function download(): Response { $document = app(DocumentFactoryInterface::class)->create(); $document->addPage(); $document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf'); }}<?php
declare(strict_types=1);
namespace App\Controller;
use NextPDF\Symfony\Http\PdfResponse;use NextPDF\Symfony\Service\PdfFactory;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Attribute\Route;
final class ReportController{ #[Route('/report', name: 'report_pdf')] public function download(PdfFactory $pdf): Response { $document = $pdf->create(); $document->addPage(); $document->cell(0, 10, 'Monthly report', newLine: true);
return PdfResponse::download($document, 'report.pdf'); }}<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\DownloadResponse;use NextPDF\CodeIgniter\Config\Services;
final class ReportController extends BaseController{ public function download(): DownloadResponse { $pdf = Services::pdf(); $pdf->document()->addPage(); $pdf->document()->cell(0, 10, 'Monthly report');
return $pdf->download('report.pdf'); }}Wenn das PDF im Browser angezeigt statt heruntergeladen werden soll, ersetzen Sie bei Laravel und Symfony den download(...)-Aufruf durch inline(...) und bei CodeIgniter durch $pdf->inline('report.pdf'). Die Disposition wird zu inline, und alle anderen Header bleiben gleich.
Codebeispiel — Produktion
Abschnitt betitelt „Codebeispiel — Produktion“Eine Produktions-Action injiziert ihre Abhängigkeiten, fängt die spezifischste Exception ab, die die Integration dokumentiert, loggt die Fehlerklasse, ohne einen Trace preiszugeben, und gibt eine definierte HTTP-Fehlerantwort zurück. Das folgende Beispiel nutzt Constructor Injection von Laravel. Die Symfony- und CodeIgniter-Entsprechungen folgen demselben Aufbau und sind auf der Produktionseinsatz-Seite jeder Integration dokumentiert.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Laravel\Http\PdfResponse;use Psr\Log\LoggerInterface;use Throwable;
final class InvoiceController extends Controller{ public function __construct( private readonly DocumentFactoryInterface $documents, private readonly LoggerInterface $logger, ) {}
public function show(int $invoiceId): Response { try { $document = $this->documents->create(); $document->addPage(); $document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
return PdfResponse::download( $document, "invoice-{$invoiceId}.pdf", ); } catch (Throwable $exception) { // Log the exception class, never the message or a stack trace, // so internal detail does not leak into the log sink. $this->logger->error('Invoice PDF generation failed', [ 'invoice_id' => $invoiceId, 'exception' => $exception::class, ]);
return new Response('Could not generate the invoice PDF.', 500); } }}Injizieren Sie DocumentFactoryInterface und rufen Sie create() pro Action auf. Das gibt ein frisches NextPDF\Core\Document zurück — den konkreten Typ, den die Laravel-PdfResponse-Factories akzeptieren. Wenn Sie pro Request ein frisches Dokument auflösen, bleibt die Factory in Tests austauschbar. Verwenden Sie eine Controller-Instanz innerhalb eines einzelnen lang laufenden Worker-Prozesses nicht für zwei nicht zusammenhängende Dokumente wieder.
Ersetzen Sie bei sehr großen Dokumenten die gepufferte Factory durch eine gestreamte, um den Spitzenspeicherbedarf zu begrenzen. Die gestreamte Variante gibt eine StreamedResponse (Laravel und Symfony) zurück und schreibt den Body in festen Chunks. Sie lässt Content-Length bewusst weg, sodass Download-Fortschrittsbalken und längenabhängige Proxys keine bekannte Größe sehen. Bevorzugen Sie das gepufferte download() / inline() für kleine, latenzempfindliche Antworten.
$document = $this->documents->create();// ... emit content onto $document ...return PdfResponse::streamDownload($document, 'annual-report.pdf');Sonderfälle & Fallstricke
Abschnitt betitelt „Sonderfälle & Fallstricke“- Frisches Dokument pro Aufruf. In allen drei Integrationen stammt das Dokument aus einer Factory und ist pro Auflösung frisch. Speichern Sie ein aufgelöstes Dokument nicht über logische Dokumente hinweg im Cache, auch nicht über Requests hinweg in einem lang laufenden Worker. Veralteter Inhaltszustand wird sonst übernommen.
- Leerer Dateiname. Ein leerer Dateiname, der an eine
PdfResponse-Factory übergeben wird, fällt auf einen Standardnamen (document.pdf) zurück, statt eine leere Disposition zu erzeugen. Übergeben Sie einen expliziten, aussagekräftigen Dateinamen. - Nicht-ASCII-Dateinamen. Die Laravel-Antwort fügt für Nicht-ASCII-Namen automatisch einen RFC 5987-
filename*=-Parameter hinzu, und ASCII-Namen verwenden den einfachen Parameter. Kodieren Sie den Dateinamen nicht selbst von Hand. - Gestreamte Antworten hinter einem puffernden Proxy. Ein Proxy, der den vollständigen Body puffert, hebt den Speichervorteil des Streamings auf. Konfigurieren Sie den Proxy so, dass er PDF-Antworten streamt, oder verwenden Sie auf diesem Pfad eine gepufferte Antwort.
- Gestreamter Symfony-Callback. Die gestreamte Symfony-Variante gibt eine
StreamedResponsezurück, deren Callback die Ausgabe flusht. Schreiben Sie nicht selbst in den Response-Body, nachdem Sie ihn zurückgegeben haben.
Performance
Abschnitt betitelt „Performance“Synchrone Generierung innerhalb eines Controllers blockiert den Request für den gesamten PDF-Build. Bei einem einseitigen Dokument bleibt das deutlich innerhalb eines typischen Request-Budgets. Verlagern Sie bei mehrseitiger oder Batch-Ausgabe die Generierung mit einem Queued Job vom Request-Thread weg — siehe Ein PDF in einem Queued Job generieren. Die gestreamten Varianten reduzieren den Spitzenspeicherbedarf bei großen Dokumenten zum Preis einer unbekannten Content-Length. Wählen Sie sie, wenn der Speicher die Einschränkung ist und kein Fortschrittsbalken benötigt wird.
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“- Die
PdfResponse-Factories wenden in jeder Integration einen festen Satz von Response-Härtungs-Headern an und bereinigen den Download-Dateinamen. Fügen Sie diese Header nicht selbst hinzu. - Interpolieren Sie niemals nicht validierte Benutzereingaben direkt in einen Dateinamen, den Sie an die Factory übergeben. Übergeben Sie einen Wert, den Sie kontrollieren, und lassen Sie die Factory ihn als zweite Schicht bereinigen.
- Loggen Sie im catch-Block die Exception-Klasse und einen Korrelations-Identifier, nicht die Exception-Nachricht oder den Trace. Ein roher Trace in einem Log-Sink ist ein Informationsleck.
- Schreiben Sie niemals einen leeren
catch-Block. Jedes Beispiel hier loggt und gibt eine definierte Fehlerantwort zurück.
Die Security-and-Operations-Seite jeder Integration dokumentiert das Bedrohungsmodell pro Integration — Header-Set, Regeln zur Dateinamen-Bereinigung und die Lebensdauer der Dokumentbindung.
Konformität
Abschnitt betitelt „Konformität“Dieser Leitfaden erhebt keinen Anspruch auf normative Standardsetzung. Jeder gezeigte API-Aufruf entspricht der geprüften öffentlichen Oberfläche der genannten Integration, abgeglichen mit den Quickstart- und Produktionseinsatz-Seiten jedes Pakets. Die unter „Siehe auch“ verlinkten vorgelagerten Produktionseinsatz-Seiten dokumentieren die Header-Semantik und das Container-Binding-Verhalten, auf die sich die Integrationen stützen, samt ihrer PSR-Zitate. Diese Cookbook-Seite beschreibt die Verwendung und überlässt die normativen Zitate jenen Seiten.
Siehe auch
Abschnitt betitelt „Siehe auch“- Ein PDF in einem Queued Job generieren — verlagern Sie diese Arbeit vom Request-Thread weg.
- Laravel-Produktionseinsatz — per DI verdrahteter Controller, Header-Set und der PSR-11-Binding-Vertrag.
- Symfony-Quickstart — Controller, inline, gestreamt und das Response-Modell.
- CodeIgniter-Quickstart —
Services::pdf(), derpdf()-Helper undPdfResponse. - Eine Integration auswählen — wählen Sie das passende Framework-Paket.