Restituire da un controller un PDF generato
In sintesi
Sezione intitolata “In sintesi”Generare un PDF all’interno di un’azione di controller e restituirlo come risposta HTTP. Ogni integrazione del framework fornisce un helper PdfResponse che costruisce l’oggetto di risposta del framework, imposta Content-Type: application/pdf, aggiunge gli header di sicurezza e sanifica il nome del file. Questa guida illustra le tre modalità di consegna — anteprima inline, download del file e invio in streaming — per Laravel, Symfony e CodeIgniter 4.
Verificare prima i prerequisiti seguenti, per evitare sorprese durante l’esecuzione:
- Il core NextPDF è installato.
- È installata un’integrazione del framework e il relativo service provider, bundle o servizio è rilevato. Verificare il rilevamento nella pagina di installazione del proprio framework prima di iniziare.
- La modalità in streaming non richiede pacchetti aggiuntivi. Ogni integrazione fornisce la variante in streaming insieme a quella bufferizzata.
Questa guida è pratica. Presuppone di sapere già come instradare una richiesta verso un controller nel proprio framework. Per un primo esempio eseguibile in ciascun framework, consultare la guida rapida del framework indicata in Vedere anche.
Installazione
Sezione intitolata “Installazione”Installare l’integrazione corrispondente al proprio framework. Eseguire uno dei comandi seguenti.
composer require nextpdf/laravelcomposer require nextpdf/symfonycomposer require nextpdf/codeigniterPer Laravel, pubblicare la configurazione dopo l’installazione.
php artisan vendor:publish --tag=nextpdf-configSymfony registra automaticamente il bundle tramite Flex e CodeIgniter rileva automaticamente il servizio. Confermare il rilevamento nella pagina di installazione del proprio framework prima di proseguire.
Panoramica concettuale
Sezione intitolata “Panoramica concettuale”Ogni integrazione del framework condivide la stessa struttura in tre parti: un modo per ottenere un nuovo documento, un insieme di chiamate che emettono contenuto nel documento e una factory PdfResponse che trasforma il documento finito in una risposta HTTP. L’API del documento (addPage(), cell(), setFont()) è la superficie del motore core ed è identica in tutti i framework. La factory della risposta differisce solo per la classe di risposta restituita, perché ogni framework ha il proprio tipo di risposta HTTP.
PdfResponse offre tre modalità di consegna. Inline imposta un header Content-Disposition: inline affinché il browser visualizzi il PDF in una scheda del visualizzatore. Download imposta Content-Disposition: attachment affinché il browser salvi il file. Streamed emette il corpo del PDF in blocchi di dimensione fissa anziché bufferizzare l’intero documento in memoria. Sceglierla per documenti di grandi dimensioni quando il picco di memoria conta più di un Content-Length noto.
Ottenere il documento tramite il percorso idiomatico di risoluzione del framework:
- Laravel — risolvere
NextPDF\Contracts\DocumentFactoryInterfacedal container conapp(...)e chiamarecreate(), che restituisce un nuovoNextPDF\Core\Document— il tipo concreto accettato dalle factoryPdfResponse. - Symfony — iniettare
NextPDF\Symfony\Service\PdfFactorye chiamarecreate(), che restituisce un nuovoNextPDF\Core\Documentcon i valori predefiniti configurati per il documento già applicati. - CodeIgniter 4 — risolvere la libreria
PdftramiteServices::pdf()(oppure l’helperpdf()), oppure ottenere un documento vuoto tramitepdf_document().
Superficie API
Sezione intitolata “Superficie API”| Ambito | Laravel | Symfony | CodeIgniter 4 |
|---|---|---|---|
| Nuovo documento | app(DocumentFactoryInterface::class)->create() | PdfFactory::create() | pdf_document() / Services::pdf()->document() |
| Risposta inline | PdfResponse::inline($doc, $name) | PdfResponse::inline($doc, $name) | $pdf->inline($name) / PdfResponse::inline($doc, $name) |
| Risposta di download | PdfResponse::download($doc, $name) | PdfResponse::download($doc, $name) | $pdf->download($name) / PdfResponse::download($doc, $name) |
| Inline in streaming | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) |
| Download in streaming | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) |
| Tipo restituito | Illuminate\Http\Response (in streaming: StreamedResponse) | Symfony\Component\HttpFoundation\Response (in streaming: StreamedResponse) | CodeIgniter\HTTP\DownloadResponse |
Il PdfResponse di Laravel si trova in NextPDF\Laravel\Http\PdfResponse, quello di Symfony in NextPDF\Symfony\Http\PdfResponse e quello di CodeIgniter in NextPDF\CodeIgniter\Http\PdfResponse. La pagina su sicurezza e operazioni di ogni integrazione documenta il comportamento completo della risposta per ciascun pacchetto — insieme degli header, regole di disposizione e sanificazione del nome del file. Queste pagine sono indicate in Vedere anche.
Esempio di codice — Guida rapida
Sezione intitolata “Esempio di codice — Guida rapida”Di seguito è riportata l’azione di download minima in ciascun framework. Le chiamate sul documento usano la stessa superficie core. Cambia soltanto l’impalcatura del controller.
<?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'); }}Per visualizzare l’anteprima nel browser anziché scaricare il file, sostituire la chiamata download(...) con inline(...) in Laravel e Symfony, oppure con $pdf->inline('report.pdf') in CodeIgniter. La disposizione diventa inline e tutti gli altri header rimangono invariati.
Esempio di codice — Produzione
Sezione intitolata “Esempio di codice — Produzione”Un’azione di produzione inietta le proprie dipendenze, intercetta l’eccezione più specifica documentata dall’integrazione, registra la classe dell’errore senza esporre uno stack trace e restituisce un errore HTTP definito. L’esempio seguente utilizza l’iniezione tramite costruttore in Laravel. Gli equivalenti per Symfony e CodeIgniter seguono la stessa struttura e sono documentati nella pagina sull’uso in produzione di ciascuna integrazione.
<?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); } }}Iniettare DocumentFactoryInterface e chiamare create() per ogni azione. Questa operazione restituisce un nuovo NextPDF\Core\Document — il tipo concreto accettato dalle factory PdfResponse di Laravel. Risolvere un nuovo documento per ogni richiesta mantiene la factory sostituibile nei test. Non riutilizzare una singola istanza del controller per due documenti non correlati nello stesso processo worker a esecuzione prolungata.
Per documenti molto grandi, sostituire la factory bufferizzata con una in streaming per limitare il picco di memoria. La variante in streaming restituisce un StreamedResponse (Laravel e Symfony) ed emette il corpo in blocchi di dimensione fissa. Omette deliberatamente Content-Length, quindi le barre di avanzamento del download e i proxy sensibili alla lunghezza non rilevano una dimensione nota. Preferire le varianti bufferizzate download() / inline() per risposte piccole e sensibili alla latenza.
$document = $this->documents->create();// ... emit content onto $document ...return PdfResponse::streamDownload($document, 'annual-report.pdf');Casi limite e insidie
Sezione intitolata “Casi limite e insidie”- Documento nuovo a ogni chiamata. In tutte e tre le integrazioni il documento viene creato da una factory ed è nuovo a ogni risoluzione. Non memorizzare in cache un documento risolto tra documenti logici diversi né tra richieste in un worker a esecuzione prolungata. Verrebbe trascinato stato di contenuto obsoleto.
- Nome del file vuoto. Un nome del file vuoto passato a una factory
PdfResponseusa come fallback un nome predefinito (document.pdf) anziché produrre una disposizione vuota. Passare un nome del file esplicito e significativo. - Nomi di file non ASCII. La risposta di Laravel aggiunge automaticamente un parametro RFC 5987
filename*=per i nomi non ASCII, mentre i nomi ASCII utilizzano il parametro semplice. Non codificare manualmente il nome del file. - Risposte in streaming dietro un proxy con buffering. Un proxy che bufferizza l’intero corpo annulla il vantaggio di memoria dello streaming. Configurare il proxy per trasmettere in streaming le risposte PDF oppure utilizzare una risposta bufferizzata su quel percorso.
- Callback in streaming di Symfony. La variante in streaming di Symfony restituisce uno
StreamedResponseil cui callback effettua il flush dell’output. Non scrivere manualmente nel corpo della risposta dopo averla restituita.
Prestazioni
Sezione intitolata “Prestazioni”La generazione sincrona all’interno di un controller blocca la richiesta per l’intera creazione del PDF. Per un documento a pagina singola questo rientra ampiamente in un budget di richiesta tipico. Per output su più pagine o in batch, spostare la generazione fuori dal thread della richiesta con un job in coda — vedere Generare un PDF in un job in coda. Le varianti in streaming riducono il picco di memoria per i documenti di grandi dimensioni al costo di un Content-Length sconosciuto. Sceglierle quando il vincolo è la memoria e non è richiesta una barra di avanzamento.
Note sulla sicurezza
Sezione intitolata “Note sulla sicurezza”- Le factory
PdfResponseapplicano un insieme fisso di header di hardening della risposta e sanificano il nome del file di download in ogni integrazione. Non aggiungere questi header manualmente. - Non interpolare mai input utente non convalidato direttamente in un nome del file passato alla factory. Passare un valore controllato e lasciare che la factory lo sanifichi come secondo livello.
- Nel blocco catch, registrare la classe dell’eccezione e un identificatore di correlazione, non il messaggio dell’eccezione né lo stack trace. Uno stack trace grezzo in un sink di log costituisce una fuga di informazioni.
- Non scrivere mai un blocco
catchvuoto. Ogni esempio qui riportato registra e restituisce una risposta di errore definita.
La pagina su sicurezza e operazioni di ogni integrazione documenta il modello di minaccia specifico per ciascuna integrazione — insieme degli header, regole di sanificazione del nome del file e durata del binding del documento.
Conformità
Sezione intitolata “Conformità”Questa guida non avanza alcuna affermazione normativa sugli standard. Ogni chiamata API mostrata fa parte della superficie pubblica verificata dell’integrazione indicata, controllata a fronte delle pagine di guida rapida e di uso in produzione di ciascun pacchetto. Le pagine upstream sull’uso in produzione indicate in Vedere anche documentano la semantica degli header e il comportamento di binding del container su cui si basano le integrazioni, insieme alle relative citazioni PSR. Questa pagina del cookbook riprende l’uso e rimanda le citazioni normative a tali pagine.
Vedere anche
Sezione intitolata “Vedere anche”- Generare un PDF in un job in coda — spostare questo lavoro fuori dal thread della richiesta.
- Uso in produzione di Laravel — controller con DI configurata, insieme degli header e contratto di binding PSR-11.
- Guida rapida di Symfony — controller, inline, streaming e modello di risposta.
- Guida rapida di CodeIgniter —
Services::pdf(), l’helperpdf()ePdfResponse. - Scegliere un’integrazione — selezionare il pacchetto del framework corretto.