Ga naar inhoud

Een gegenereerde PDF teruggeven vanuit een controller

Genereer een Portable Document Format (PDF)-bestand in een controlleractie en geef het terug als een Hypertext Transfer Protocol (HTTP)-respons. Elke framework-integratie bevat een PdfResponse-helper die het responsobject opbouwt, Content-Type: application/pdf instelt, beveiligingsheaders toevoegt en de bestandsnaam saneert. Deze handleiding behandelt de drie leveringsmodi voor Laravel, Symfony en CodeIgniter 4: inline voorvertoning, bestandsdownload en gestreamde levering.

Controleer eerst deze vereisten, zodat het controllerpad klaarstaat voordat u begint:

  • NextPDF core is geïnstalleerd.
  • Eén framework-integratie is geïnstalleerd en de bijbehorende service provider, bundle of service is gedetecteerd. Controleer op de installatiepagina van uw framework of die detectie is gelukt voordat u begint.
  • De gestreamde modus heeft geen extra pakketten nodig. Elke integratie bevat naast de gebufferde variant ook een gestreamde variant.

Dit is een how-to. Deze handleiding gaat ervan uit dat u al weet hoe u in uw framework een verzoek naar een controller routeert. Voor een eerste werkend voorbeeld in elk framework raadpleegt u de framework-quickstart die onder Zie ook is gelinkt.

Installeer de integratie voor uw framework. Voer een van de volgende commando’s uit.

Terminal window
composer require nextpdf/laravel
Terminal window
composer require nextpdf/symfony
Terminal window
composer require nextpdf/codeigniter

Publiceer in Laravel na de installatie de configuratie.

Terminal window
php artisan vendor:publish --tag=nextpdf-config

Symfony registreert de bundle via Flex en CodeIgniter detecteert de service automatisch. Controleer op de installatiepagina van uw framework of de detectie is gelukt voordat u doorgaat.

Elke framework-integratie volgt dezelfde driedelige structuur: u maakt een nieuw document aan, schrijft inhoud naar dat document en geeft het door aan een PdfResponse-factory die een HTTP-respons retourneert. De document-API (addPage(), cell(), setFont()) is de kern-API van de engine en is in alle frameworks identiek. De response-factory verschilt alleen in de responsklasse die de factory retourneert, omdat elk framework zijn eigen HTTP-responstype heeft.

PdfResponse biedt drie leveringsmodi. Inline stelt een Content-Disposition: inline-header in, zodat de browser de PDF in een viewer-tabblad weergeeft. Download stelt Content-Disposition: attachment in, zodat de browser het bestand opslaat. Streamed zendt de PDF-inhoud in vaste blokken uit in plaats van het hele document in het geheugen te bufferen. Kies deze modus voor grote documenten wanneer het piekgeheugen belangrijker is dan een bekende Content-Length.

Maak het document aan via het gebruikelijke resolutiepad van uw framework:

  • Laravel — los NextPDF\Contracts\DocumentFactoryInterface op uit de container met app(...) en roep create() aan. Dat retourneert een nieuw NextPDF\Core\Document: het concrete type dat de PdfResponse-factory’s accepteren.
  • Symfony — injecteer NextPDF\Symfony\Service\PdfFactory en roep create() aan. Dat retourneert een nieuw NextPDF\Core\Document waarop de geconfigureerde documentstandaarden al zijn toegepast.
  • CodeIgniter 4 — los de Pdf-library op via Services::pdf() (of de pdf()-helper), of haal een kaal document op via pdf_document().
AandachtspuntLaravelSymfonyCodeIgniter 4
Nieuw documentapp(DocumentFactoryInterface::class)->create()PdfFactory::create()pdf_document() / Services::pdf()->document()
Inline responsPdfResponse::inline($doc, $name)PdfResponse::inline($doc, $name)$pdf->inline($name) / PdfResponse::inline($doc, $name)
DownloadresponsPdfResponse::download($doc, $name)PdfResponse::download($doc, $name)$pdf->download($name) / PdfResponse::download($doc, $name)
Gestreamd inlinePdfResponse::streamInline($doc, $name)PdfResponse::streamInline($doc, $name)PdfResponse::streamInline($doc, $name)
Gestreamde downloadPdfResponse::streamDownload($doc, $name)PdfResponse::streamDownload($doc, $name)PdfResponse::streamDownload($doc, $name)
Geretourneerd typeIlluminate\Http\Response (gestreamd: StreamedResponse)Symfony\Component\HttpFoundation\Response (gestreamd: StreamedResponse)CodeIgniter\HTTP\DownloadResponse

De Laravel-PdfResponse bevindt zich op NextPDF\Laravel\Http\PdfResponse, de Symfony-variant op NextPDF\Symfony\Http\PdfResponse en de CodeIgniter-variant op NextPDF\CodeIgniter\Http\PdfResponse. Voor elk pakket documenteert de pagina over beveiliging en beheer van de integratie het volledige responsgedrag: de headerset, de dispositieregels en de sanering van de bestandsnaam. U vindt die pagina’s onder Zie ook.

Dit is de minimale downloadactie in elk framework. De documentaanroepen gebruiken dezelfde kern-API. Alleen de controllerstructuur verandert.

Laravel: app/Http/Controllers/ReportController.php
<?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');
}
}
Symfony: src/Controller/ReportController.php
<?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');
}
}
CodeIgniter 4: app/Controllers/ReportController.php
<?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');
}
}

Voor een voorvertoning in de browser in plaats van een download vervangt u de download(...)-aanroep door inline(...) in Laravel en Symfony, of door $pdf->inline('report.pdf') in CodeIgniter. De dispositie wordt inline; alle andere headers blijven gelijk.

Een productieactie injecteert de afhankelijkheden, vangt de meest specifieke exceptie op die de integratie documenteert, logt de foutklasse zonder een trace te lekken en retourneert een gedefinieerde HTTP-fout. Het onderstaande voorbeeld gebruikt constructor-injectie in Laravel. De Symfony- en CodeIgniter-equivalenten volgen dezelfde structuur en staan op de productiegebruikpagina van elke integratie.

Laravel: app/Http/Controllers/InvoiceController.php
<?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);
}
}
}

Injecteer DocumentFactoryInterface en roep in elke actie create() aan. Dit retourneert een nieuw NextPDF\Core\Document: het concrete type dat de Laravel-PdfResponse-factory’s accepteren. Door per verzoek een nieuw document op te lossen, blijft de factory in tests vervangbaar. Hergebruik één controllerinstantie niet voor twee niet-gerelateerde documenten binnen één langlopend workerproces.

Gebruik voor zeer grote documenten een gestreamde factory in plaats van de gebufferde factory om het piekgeheugen te begrenzen. De gestreamde variant retourneert een StreamedResponse (Laravel en Symfony) en zendt de inhoud uit in vaste blokken. Deze variant laat opzettelijk Content-Length weg, zodat downloadvoortgangsbalken en lengtegevoelige proxy’s geen bekende grootte zien. Geef de voorkeur aan de gebufferde download() / inline() voor kleine, latentiegevoelige responsen.

Laravel: streamed download for a large report
$document = $this->documents->create();
// ... emit content onto $document ...
return PdfResponse::streamDownload($document, 'annual-report.pdf');
  • Nieuw document per aanroep. In alle drie de integraties is het document een factoryproduct dat bij elke resolutie opnieuw wordt aangemaakt. Cache een opgelost document niet over logische documenten heen, of over verzoeken heen in een langlopende worker. Anders wordt verouderde inhoudsstatus meegenomen.
  • Lege bestandsnaam. Een lege bestandsnaam die aan een PdfResponse-factory wordt doorgegeven, valt terug op een standaardnaam (document.pdf) in plaats van een lege dispositie te produceren. Geef een expliciete, betekenisvolle bestandsnaam door.
  • Niet-ASCII-bestandsnamen. De Laravel-respons voegt automatisch een RFC 5987-filename*=-parameter toe voor niet-ASCII-namen, terwijl ASCII-namen de gewone parameter gebruiken. Codeer de bestandsnaam niet zelf met de hand.
  • Gestreamde responsen achter een bufferende proxy. Een proxy die de volledige inhoud buffert, doet het geheugenvoordeel van streamen teniet. Configureer de proxy om PDF-responsen te streamen, of gebruik een gebufferde respons op dat pad.
  • Symfony gestreamde callback. De gestreamde Symfony-variant retourneert een StreamedResponse waarvan de callback de uitvoer doorspoelt. Schrijf zelf niet meer naar de responsinhoud nadat u deze hebt teruggegeven.

Synchrone generatie binnen een controller blokkeert het verzoek gedurende de volledige PDF-build. Een document van één pagina blijft doorgaans ruim binnen een typisch verzoekbudget. Verplaats bij uitvoer van meerdere pagina’s of batchuitvoer de generatie uit de verzoekthread naar een job in de wachtrij — zie Een PDF genereren in een job in de wachtrij. De gestreamde varianten verminderen het piekgeheugen voor grote documenten, maar leveren geen bekende Content-Length. Kies ze wanneer geheugen de beperking is en een voortgangsbalk niet vereist is.

  • De PdfResponse-factory’s passen in elke integratie een vaste set respons-hardeningheaders toe en saneren de downloadbestandsnaam. Voeg die headers niet zelf toe.
  • Interpoleer nooit niet-gevalideerde gebruikersinvoer rechtstreeks in een bestandsnaam die u aan de factory doorgeeft. Geef een waarde door die u beheert, en laat de factory deze als tweede laag saneren.
  • Log in het catch-blok de exceptieklasse en een correlatie-identifier, niet het exceptiebericht of de trace. Een ruwe trace in een log sink is een informatielek.
  • Schrijf nooit een leeg catch-blok. Elk voorbeeld hier logt en retourneert een gedefinieerde foutrespons.

De pagina over beveiliging en beheer van elke integratie documenteert het dreigingsmodel van die integratie: de headerset, de regels voor bestandsnaamsanering en de levensduur van de documentbinding.

Deze handleiding doet geen normatieve claim over standaarden. Elke getoonde API-aanroep is het geverifieerde publieke oppervlak van de genoemde integratie, gecontroleerd tegen de quickstart- en productiegebruikpagina’s van elk pakket. De upstream-productiegebruikpagina’s die onder Zie ook zijn gelinkt, documenteren de headersemantiek en het containerbindingsgedrag waarop de integraties steunen, samen met hun PSR-citaten. Deze cookbook-pagina herhaalt het gebruik en laat de normatieve citaten over aan die pagina’s.