Aller au contenu

Démarrage rapide de NextPDF avec Symfony

Injecte PdfFactory, construis un Document, puis renvoie ce document via PdfResponse. Pour générer un PDF en arrière-plan, expédie un GeneratePdfMessage sur un transport Messenger.

Injecte NextPDF\Symfony\Service\PdfFactory. Sa méthode create() renvoie un NextPDF\Core\Document vierge. Les valeurs par défaut configurées sont déjà appliquées pour toi : le créateur, l’auteur et la langue. Renvoie ensuite ce document via NextPDF\Symfony\Http\PdfResponse.

src/Controller/InvoiceController.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 InvoiceController
{
#[Route('/invoice/{number}', name: 'invoice_pdf')]
public function download(PdfFactory $pdf, string $number): Response
{
$doc = $pdf->create();
$doc->addPage();
$doc->cell(0, 10, "Invoice #{$number}", newLine: true);
$doc->cell(0, 10, 'Thank you for your business.');
return PdfResponse::download($doc, "invoice-{$number}.pdf");
}
}

PdfResponse::download() renvoie une Symfony\Component\HttpFoundation\Response. Elle contient Content-Type: application/pdf, une disposition attachment, un Content-Length, ainsi que les en-têtes de sécurité fixes du bundle. Symfony documente la classe canonique Response et son modèle d’en-têtes (https://symfony.com/doc/current/components/http_foundation.html).

Pour laisser le navigateur afficher le PDF plutôt que le télécharger, utilise inline() :

return PdfResponse::inline($doc, 'preview.pdf');

La disposition passe à inline. Tous les autres en-têtes restent identiques.

Pour les documents volumineux, les variantes en flux diffusent le PDF par blocs de 64 Ko. Cela réduit le pic d’utilisation de la mémoire. Elles renvoient une Symfony\Component\HttpFoundation\StreamedResponse et omettent Content-Length.

return PdfResponse::streamDownload($doc, 'annual-report.pdf');

streamInline() en est l’équivalent pour l’affichage en ligne. Symfony documente le contrat de rappel de StreamedResponse : un appelable void qui vide la sortie (https://symfony.com/doc/current/components/http_foundation.html).

Étape 4 — Générer un PDF de façon asynchrone

Section intitulée « Étape 4 — Générer un PDF de façon asynchrone »

Quand symfony/messenger est installé, tu peux déplacer la génération hors du thread de requête.

Implémente NextPDF\Symfony\Message\PdfBuilderInterface. Le handler te fournit un Document neuf et préconfiguré. Il te fournit aussi le contexte sérialisable issu du message.

src/Pdf/InvoicePdfBuilder.php
<?php
declare(strict_types=1);
namespace App\Pdf;
use NextPDF\Core\Document;
use NextPDF\Symfony\Message\PdfBuilderInterface;
final class InvoicePdfBuilder implements PdfBuilderInterface
{
public function build(Document $document, array $context): Document
{
$document->addPage();
$document->setFont('dejavusans', '', 12);
$document->cell(0, 10, 'Invoice #' . $context['invoice_id']);
return $document;
}
}

Le handler résout les builders depuis un service locator PSR-11 indexé par nom de classe. Par conséquent, seuls les builders enregistrés sont accessibles. Ajoute le builder à un locator dans config/services.yaml :

services:
App\Pdf\InvoicePdfBuilder: ~
nextpdf.pdf_builder_locator:
class: Symfony\Component\DependencyInjection\ServiceLocator
arguments:
- 'App\Pdf\InvoicePdfBuilder': '@App\Pdf\InvoicePdfBuilder'
tags: ['container.service_locator']
NextPDF\Symfony\Message\GeneratePdfHandler:
arguments:
$builderLocator: '@nextpdf.pdf_builder_locator'

Le handler récupère le builder auprès du locator via son identifiant de type class-string. Un identifiant de conteneur PSR-11 est une chaîne qui identifie de manière unique une entrée (PSR-11 §1.1.2).

Injecte Symfony\Component\Messenger\MessageBusInterface, puis expédie le message :

src/Controller/ReportController.php
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Pdf\InvoicePdfBuilder;
use NextPDF\Symfony\Message\GeneratePdfMessage;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route;
final class ReportController
{
#[Route('/invoice/{id}/queue', name: 'invoice_queue')]
public function queue(MessageBusInterface $bus, int $id): Response
{
$bus->dispatch(new GeneratePdfMessage(
builderClass: InvoicePdfBuilder::class,
outputPath: '/var/storage/invoices/' . $id . '.pdf',
builderContext: ['invoice_id' => $id],
));
return new Response('PDF generation queued.', 202);
}
}

GeneratePdfMessage est un DTO readonly. Son constructeur rejette les chemins de sortie vides ou qui ne se terminent pas par .pdf, les segments de traversée de chemin, les schémas de wrapper de flux et les octets nuls. Il exige aussi que builderClass soit un nom de classe syntaxiquement valide. Le handler valide de nouveau le chemin de sortie à l’exécution, avant d’écrire. Ainsi, un chemin sûr lors de l’expédition, mais devenu dangereux lors de la consommation, est tout de même rejeté. L’attribut #[AsMessageHandler] et le contrat d’expédition MessageBusInterface suivent le modèle standard de Symfony Messenger (https://symfony.com/doc/current/messenger.html).

Dans config/packages/messenger.yaml, route le message vers un transport :

framework:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
NextPDF\Symfony\Message\GeneratePdfMessage: async

Puis lance un worker :

Fenêtre de terminal
php bin/console messenger:consume async
Fenêtre de terminal
php bin/console debug:container --tag=container.service_locator
php bin/console messenger:consume async --limit=1 -vv

La première commande confirme que le builder locator est enregistré. La seconde commande consomme un seul message en file d’attente et affiche l’avancement du handler.

  • /integrations/symfony/configuration/ — ajuste les valeurs par défaut, les polices et le service de document.
  • /integrations/symfony/production-usage/ — sécurisation du worker et diffusion en flux sous charge.
  • /integrations/symfony/troubleshooting/ — problèmes courants au démarrage et à l’exécution.

Chaque ligne correspond à une affirmation normative présente sur cette page, associée à un reference_id complet de 64 caractères hexadécimaux issu du corpus SDO sous accès contrôlé. La provenance (manifeste du corpus, transport utilisé pour la récupération) est indiquée dans _sidecars/rag-citations.yaml.

SpécClausereference_idAffirmation
PSR-11psr_11_container#1.1.2.p4Contrat d’identifiant du conteneur pour has()/get()
  • /integrations/symfony/overview/ — aperçu des fonctionnalités.
  • /integrations/symfony/install/ — installation et enregistrement.
  • /integrations/symfony/integration/ — référence du câblage de bout en bout.