Retourner un PDF généré depuis un contrôleur
Génère un PDF dans une action de contrôleur et renvoie-le sous forme de réponse HTTP. Chaque intégration de framework fournit un assistant PdfResponse qui construit l’objet de réponse propre au framework, définit Content-Type: application/pdf, ajoute les en-têtes de sécurité et assainit le nom de fichier. Ce guide couvre les trois modes de livraison — aperçu inline, téléchargement de fichier et diffusion en flux — pour Laravel, Symfony et CodeIgniter 4.
Vérifie d’abord ces prérequis pour éviter toute surprise en cours de route :
- Le cœur NextPDF est installé.
- Une intégration de framework est installée, avec son service provider, son bundle ou son service correctement découvert. Vérifie la découverte sur la page d’installation de ton framework avant de commencer.
- Le mode de diffusion en flux ne nécessite aucun paquet supplémentaire. Chaque intégration fournit la variante en flux en parallèle de la variante tamponnée.
Ce guide est pratique. Il suppose que tu sais déjà router une requête vers un contrôleur dans ton framework. Pour un premier exemple exécutable dans chaque framework, lis le démarrage rapide du framework indiqué dans Voir aussi.
Installation
Section intitulée « Installation »Installe l’intégration correspondant à ton framework. Exécute l’une des commandes suivantes.
composer require nextpdf/laravelcomposer require nextpdf/symfonycomposer require nextpdf/codeigniterPour Laravel, publie la configuration après l’installation.
php artisan vendor:publish --tag=nextpdf-configSymfony enregistre automatiquement le bundle via Flex, et CodeIgniter découvre automatiquement le service. Confirme cette découverte sur la page d’installation de ton framework avant de continuer.
Vue d’ensemble conceptuelle
Section intitulée « Vue d’ensemble conceptuelle »Chaque intégration de framework partage la même structure en trois parties : un moyen d’obtenir un document neuf, un ensemble d’appels qui ajoutent du contenu à ce document, et une fabrique PdfResponse qui transforme le document final en réponse HTTP. L’API du document (addPage(), cell(), setFont()) est la surface du moteur cœur et reste identique d’un framework à l’autre. La fabrique de réponse ne diffère que par la classe de réponse qu’elle renvoie, car chaque framework possède son propre type de réponse HTTP.
PdfResponse propose trois modes de livraison. Le mode Inline définit un en-tête Content-Disposition: inline pour que le navigateur affiche le PDF dans un onglet de visualisation. Le mode Download définit Content-Disposition: attachment pour que le navigateur enregistre le fichier. Le mode Streamed émet le corps du PDF par tronçons de taille fixe au lieu de tamponner le document entier en mémoire. Choisis ce mode pour les gros documents lorsque le pic de mémoire importe davantage qu’un Content-Length connu.
Obtiens le document par le mécanisme de résolution idiomatique du framework :
- Laravel — récupère
NextPDF\Contracts\DocumentFactoryInterfacedepuis le conteneur avecapp(...)et appellecreate(), qui renvoie unNextPDF\Core\Documentneuf — le type concret que les fabriquesPdfResponseacceptent. - Symfony — injecte
NextPDF\Symfony\Service\PdfFactoryet appellecreate(), qui renvoie unNextPDF\Core\Documentneuf avec les valeurs par défaut du document déjà appliquées. - CodeIgniter 4 — récupère la bibliothèque
PdfviaServices::pdf()(ou l’assistantpdf()), ou obtiens directement un document viapdf_document().
Surface de l’API
Section intitulée « Surface de l’API »| Préoccupation | Laravel | Symfony | CodeIgniter 4 |
|---|---|---|---|
| Document neuf | app(DocumentFactoryInterface::class)->create() | PdfFactory::create() | pdf_document() / Services::pdf()->document() |
| Réponse inline | PdfResponse::inline($doc, $name) | PdfResponse::inline($doc, $name) | $pdf->inline($name) / PdfResponse::inline($doc, $name) |
| Réponse de téléchargement | PdfResponse::download($doc, $name) | PdfResponse::download($doc, $name) | $pdf->download($name) / PdfResponse::download($doc, $name) |
| Inline en flux | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) | PdfResponse::streamInline($doc, $name) |
| Téléchargement en flux | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) | PdfResponse::streamDownload($doc, $name) |
| Type renvoyé | Illuminate\Http\Response (en flux : StreamedResponse) | Symfony\Component\HttpFoundation\Response (en flux : StreamedResponse) | CodeIgniter\HTTP\DownloadResponse |
Dans Laravel, PdfResponse se trouve dans NextPDF\Laravel\Http\PdfResponse, dans Symfony dans NextPDF\Symfony\Http\PdfResponse, et dans CodeIgniter dans NextPDF\CodeIgniter\Http\PdfResponse. La page de sécurité et d’exploitation de chaque intégration documente le comportement de réponse complet propre à chaque paquet — l’ensemble des en-têtes, les règles de disposition et l’assainissement du nom de fichier. Ces pages sont indiquées dans Voir aussi.
Exemple de code — Démarrage rapide
Section intitulée « Exemple de code — Démarrage rapide »Voici l’action minimale de téléchargement dans chaque framework. Les appels au document utilisent la même surface cœur. Seule la structure du contrôleur diffère.
<?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'); }}Pour afficher un aperçu dans le navigateur au lieu de télécharger, remplace l’appel download(...) par inline(...) dans Laravel et Symfony, ou par $pdf->inline('report.pdf') dans CodeIgniter. La disposition passe à inline, et tous les autres en-têtes restent inchangés.
Exemple de code — Production
Section intitulée « Exemple de code — Production »Une action de production injecte ses dépendances, intercepte l’exception la plus spécifique que l’intégration documente, journalise la classe de l’échec sans divulguer de trace, et renvoie une erreur HTTP définie. L’exemple ci-dessous utilise l’injection par constructeur de Laravel. Les équivalents Symfony et CodeIgniter suivent la même structure et sont documentés sur la page d’usage en production de chaque intégration.
<?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); } }}Injecte DocumentFactoryInterface et appelle create() dans chaque action. Cela renvoie un NextPDF\Core\Document neuf — le type concret que les fabriques PdfResponse de Laravel acceptent. Récupérer un document neuf par requête permet de remplacer la fabrique dans les tests. Ne réutilise pas une même instance de contrôleur pour deux documents sans rapport au sein d’un seul processus worker à longue durée de vie.
Pour les très gros documents, remplace la fabrique tamponnée par une fabrique en flux afin de borner le pic de mémoire. La variante en flux renvoie un StreamedResponse (Laravel et Symfony) et émet le corps par tronçons de taille fixe. Elle omet délibérément Content-Length, de sorte que les barres de progression de téléchargement et les proxys sensibles à la taille ne voient pas de taille connue. Préfère les variantes tamponnées download() / inline() pour les réponses petites et sensibles à la latence.
$document = $this->documents->create();// ... emit content onto $document ...return PdfResponse::streamDownload($document, 'annual-report.pdf');Cas limites et pièges
Section intitulée « Cas limites et pièges »- Un document neuf par appel. Dans les trois intégrations, le document vient d’une fabrique et il est neuf à chaque résolution. Ne mets pas en cache un document résolu entre des documents logiques, ni entre des requêtes dans un worker à longue durée de vie. Sinon, un état de contenu obsolète se reporterait d’une requête à l’autre.
- Nom de fichier vide. Un nom de fichier vide passé à une fabrique
PdfResponseutilise un nom par défaut (document.pdf) plutôt que de produire une disposition vide. Passe un nom de fichier explicite et significatif. - Noms de fichiers non-ASCII. La réponse Laravel ajoute automatiquement un paramètre RFC 5987
filename*=pour les noms non-ASCII, et les noms ASCII utilisent le paramètre simple. N’encode pas le nom de fichier toi-même à la main. - Réponses en flux derrière un proxy qui tamponne. Un proxy qui tamponne le corps complet annule le bénéfice mémoire du streaming. Configure le proxy pour qu’il diffuse les réponses PDF en flux, ou utilise une réponse tamponnée sur ce chemin.
- Callback en flux de Symfony. La variante Symfony en flux renvoie un
StreamedResponsedont le callback vide la sortie. N’écris pas toi-même dans le corps de la réponse après l’avoir renvoyée.
Performance
Section intitulée « Performance »La génération synchrone dans un contrôleur bloque la requête pendant toute la construction du PDF. Pour un document d’une seule page, cela reste largement dans le budget d’une requête typique. Pour une sortie multipage ou un traitement par lots, déplace la génération hors du thread de la requête avec un job mis en file d’attente — voir Générer un PDF dans un job mis en file d’attente. Les variantes en flux réduisent le pic de mémoire pour les gros documents au prix d’un Content-Length inconnu. Choisis-les quand la mémoire est la contrainte et qu’une barre de progression n’est pas requise.
Notes de sécurité
Section intitulée « Notes de sécurité »- Les fabriques
PdfResponseappliquent un ensemble fixe d’en-têtes de durcissement de la réponse et assainissent le nom de fichier de téléchargement dans chaque intégration. N’ajoute pas ces en-têtes toi-même. - N’interpole jamais une entrée utilisateur non validée directement dans un nom de fichier que tu passes à la fabrique. Passe une valeur que tu contrôles, et laisse la fabrique l’assainir comme deuxième couche.
- Dans le bloc catch, journalise la classe de l’exception et un identifiant de corrélation, pas le message ni la trace de l’exception. Une trace brute dans une destination de logs est une fuite d’information.
- N’écris jamais un bloc
catchvide. Chaque exemple ici journalise et renvoie une réponse d’erreur définie.
La page de sécurité et d’exploitation de chaque intégration documente le modèle de menace propre à cette intégration — l’ensemble des en-têtes, les règles d’assainissement du nom de fichier et la durée de vie de la liaison du document.
Conformité
Section intitulée « Conformité »Ce guide ne fait aucune revendication normative de conformité aux standards. Chaque appel d’API montré appartient à la surface publique vérifiée de l’intégration nommée, recoupée avec les pages de démarrage rapide et d’usage en production de chaque paquet. Les pages amont d’usage en production indiquées dans Voir aussi documentent la sémantique des en-têtes et le comportement de liaison au conteneur sur lesquels s’appuient les intégrations, ainsi que leurs citations PSR. Cette page du Cookbook reformule l’usage et laisse les citations normatives à ces pages.
Voir aussi
Section intitulée « Voir aussi »- Générer un PDF dans un job mis en file d’attente — déplace ce travail hors du thread de la requête.
- Usage en production avec Laravel — contrôleur câblé par injection de dépendances, ensemble des en-têtes et contrat de liaison PSR-11.
- Démarrage rapide avec Symfony — contrôleur, inline, en flux et modèle de réponse.
- Démarrage rapide avec CodeIgniter —
Services::pdf(), l’assistantpdf()etPdfResponse. - Choisir une intégration — choisis le bon paquet de framework.