Aller au contenu

Utiliser le package Laravel NextPDF en production

En production, résous le contrat de document par injection dans le constructeur. Gère les échecs d’écriture du PDF avec une exception spécifique. Déplace la génération lourde ou par lots vers GeneratePdfJob, et branche des rappels explicites de réussite et d’échec.

Fenêtre de terminal
composer require nextpdf/laravel
php artisan vendor:publish --tag=nextpdf-config

Configure la connexion de file d’attente dans config/nextpdf.php. Définis queue.connection, queue.queue et queue.timeout. Assure-toi ensuite qu’un worker est lancé sur la connexion configurée.

Le conteneur expose NextPDF\Contracts\PdfDocumentInterface sous forme de liaison de fabrique. Chaque résolution produit un nouveau NextPDF\Core\Document. PSR-11 autorise un conteneur à renvoyer des valeurs différentes lors d’appels get() successifs, selon la stratégie de liaison (PSR-11 §1.1.2). Ici, le package utilise une liaison de fabrique pour que l’état mutable lié à la requête ne franchisse jamais les limites de cette requête. Les registres de polices et d’images sont des singletons. Cela préserve le contrat selon lequel un identifiant lié se résout vers son entrée enregistrée (PSR-11 §1.1.2), tout en partageant les ressources coûteuses à l’échelle du worker.

Privilégie l’injection par constructeur à la façade dans le code de production. La dépendance reste ainsi explicite, et le contrôleur demeure testable unitairement sans initialiser la racine de la façade.

Contrôleur câblé par injection de dépendances avec gestion typée des erreurs

Section intitulée « Contrôleur câblé par injection de dépendances avec gestion typée des erreurs »
resource: NextPDF\Contracts\PdfDocumentInterface + src/Laravel/Http/PdfResponse.php
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use NextPDF\Contracts\PdfDocumentInterface;
use NextPDF\Laravel\Http\PdfResponse;
use Psr\Log\LoggerInterface;
use Throwable;
final class InvoiceController extends Controller
{
public function __construct(
private readonly PdfDocumentInterface $document,
private readonly LoggerInterface $logger,
) {}
public function show(int $invoiceId): Response
{
try {
$this->document->addPage();
$this->document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
$this->document->cell(0, 10, 'Thank you for your business.');
return PdfResponse::download(
$this->document,
"invoice-{$invoiceId}.pdf",
);
} catch (Throwable $exception) {
// Rethrow as an HTTP-meaningful failure; never swallow.
$this->logger->error('Invoice PDF generation failed', [
'invoice_id' => $invoiceId,
'exception' => $exception::class,
]);
return new Response('Could not generate the invoice PDF.', 500);
}
}
}

Injecte PdfDocumentInterface, et non le Document concret, afin que la liaison reste interchangeable dans les tests. Le conteneur renvoie un nouveau document à chaque instanciation du contrôleur. Ne réutilise pas la même instance de contrôleur pour deux documents sans lien dans un même processus.

Le bloc catch journalise la classe de l’exception et renvoie une erreur HTTP définie plutôt que de divulguer une trace d’appels. Utilise Psr\Log\LoggerInterface, que le conteneur résout vers le logger du framework. PSR-3 laisse l’échappement des espaces réservés à l’implémenteur et demande aux appelants de ne pas pré-échapper les valeurs de contexte (PSR-3 §1.2). Transmets un contexte structuré, et non des chaînes interpolées.

Génération en file d’attente avec rappels de réussite et d’échec

Section intitulée « Génération en file d’attente avec rappels de réussite et d’échec »

GeneratePdfJob est un job ShouldQueue. Par défaut, il utilise trois tentatives, un délai d’expiration de 120 secondes et un backoff de 10 secondes. Tu peux remplacer ces trois valeurs via config/nextpdf.php. La closure de construction reçoit le document résolu par le conteneur et doit renvoyer un document configuré.

resource: src/Laravel/Jobs/GeneratePdfJob.php
<?php
declare(strict_types=1);
namespace App\Jobs;
use NextPDF\Contracts\PdfDocumentInterface;
use NextPDF\Laravel\Jobs\GeneratePdfJob;
use Psr\Log\LoggerInterface;
use Throwable;
final class DispatchMonthlyStatement
{
public function __construct(private readonly LoggerInterface $logger) {}
public function __invoke(int $accountId): void
{
// Dispatchable::dispatch() is `public static`: it constructs the
// job from the arguments it receives and returns a PendingDispatch.
// Pass every constructor argument — including the callbacks — to
// the static call. Building an instance and then calling
// `$job->dispatch(...)` would discard that instance (and its
// callbacks) and queue a different job from only the static args.
GeneratePdfJob::dispatch(
storage_path("app/statements/{$accountId}.pdf"),
static fn (PdfDocumentInterface $document): PdfDocumentInterface => $document
->addPage()
->cell(0, 10, "Statement for account {$accountId}", newLine: true),
function (string $path) use ($accountId): void {
$this->logger->info('Statement PDF written', [
'account_id' => $accountId,
'path' => $path,
]);
},
function (Throwable $exception) use ($accountId): void {
$this->logger->error('Statement PDF failed', [
'account_id' => $accountId,
'exception' => $exception::class,
]);
},
);
}
}

GeneratePdfJob::dispatch() transmet ses arguments directement au constructeur (string $outputPath, callable $builder, ?callable $onSuccess, ?callable $onFailure). Ainsi, les rappels de réussite et d’échec sont branchés dans le job effectivement mis en file d’attente. Cela correspond à la forme positionnelle GeneratePdfJob::dispatch($path, $builder) de /integrations/laravel/quickstart/. Le rappel de réussite reçoit le chemin de sortie et le rappel d’échec reçoit le Throwable. Le job expose aussi des setters fluides then() et catch() qui renvoient le job pour le chaînage. N’utilise ces setters que lorsque tu conserves et distribues cette même instance, par exemple via l’assistant dispatch(). Le job expose également une méthode failed(), que l’exécuteur de file d’attente invoque en cas d’échec terminal. Les rappels sont enveloppés dans des closures sérialisables afin de rester valides lors du transport de la file d’attente.

PropriétéValeur par défautClé de configuration
tries3non pilotable par configuration ; sous-classe pour changer
timeout120nextpdf.queue.timeout
backoff10non pilotable par configuration ; sous-classe pour changer
nom de la file d’attentepdfnextpdf.queue.queue
connexionpar défautnextpdf.queue.connection

tries et backoff sont des propriétés publiques lues sur l’instance du job. Le job fourni ne les pilote pas depuis la configuration. Pour les remplacer lorsque ta politique de réessai diffère, crée une sous-classe de GeneratePdfJob.

  • La closure de construction doit renvoyer un PdfDocumentInterface. Le job enregistre cette valeur de retour, pas l’instance résolue à l’origine. Le test du job vérifie explicitement ce contrat.
  • La résolution de SignerInterface renvoie null, sauf si la signature est activée, qu’un certificat est configuré et que nextpdf/premium est installé. Vérifie toujours qu’elle n’est pas null avant de signer.
  • Les workers à longue durée de vie (Octane/RoadRunner/Swoole) partagent le registre de polices verrouillé. Configure preload_fonts pour que le préchauffage ait lieu une seule fois au démarrage du worker, plutôt qu’à la première requête.
  • Pour un job en échec, failed() est invoquée après épuisement de tries. L’échec d’une tentative individuelle n’appelle pas onFailure tant que l’exécuteur de file d’attente n’a pas déclaré un échec terminal.

La génération synchrone dans le contrôleur bloque la requête pendant toute la construction du PDF. Pour une sortie multipage ou par lots, mets GeneratePdfJob en file d’attente et renvoie immédiatement la réponse. Les registres singletons amortissent l’analyse des polices et le décodage des images sur la durée de vie du worker. Le coût par requête se limite alors à la construction du document et à l’émission du contenu.

Le contrôleur utilisant l’injection de dépendances journalise la classe de l’exception, pas son message ni sa trace, afin de ne pas divulguer de détails internes dans les journaux. GeneratePdfJob valide le chemin de sortie côté worker afin d’atténuer les charges utiles sérialisées altérées pendant le transport de la file d’attente. La couverture complète se trouve dans /integrations/laravel/security-and-operations/.

AffirmationSourceClausereference_id
Un identifiant lié se résout vers son entrée enregistréeConteneur PSR-11§1.1.2
Les résolutions successives peuvent différer selon la stratégie de liaison (liaison de fabrique)Conteneur PSR-11§1.1.2

Les recommandations de journalisation figurent dans la spécification PSR-3. Elles indiquent que l’échappement des espaces réservés relève de l’implémenteur et que les appelants transmettent un contexte structuré. Voir le document psr_3_logger §1.2.

La sortie signée PAdES B-B et l’archivage PDF/A via nextpdf/premium utilisent la même surface d’injection de dépendances. Il s’agit d’une capacité Enterprise optionnelle. Le package Core documenté ici ne nécessite aucun changement de code pour l’adopter. Voir https://nextpdf.dev/get-license/?intent=laravel-signing.

  • /integrations/laravel/quickstart/ — premier exemple minimal
  • /integrations/laravel/configuration/ — clés de file d’attente, de signature et de polices
  • /integrations/laravel/security-and-operations/ — modèle de menace et durcissement
  • /integrations/laravel/troubleshooting/ — échecs courants en production