Salta ai contenuti

Uso in produzione del pacchetto NextPDF per Laravel

In produzione, risolvere il contratto del documento tramite constructor injection. Gestire gli errori di scrittura del PDF con un’eccezione specifica. Delegare la generazione pesante o in batch a GeneratePdfJob e associare callback esplicite di successo e di errore.

Terminal window
composer require nextpdf/laravel
php artisan vendor:publish --tag=nextpdf-config

Configurare la connessione alla coda in config/nextpdf.php. Impostare queue.connection, queue.queue e queue.timeout. Verificare quindi che sia in esecuzione un worker per la connessione configurata.

Il container espone NextPDF\Contracts\PdfDocumentInterface come factory binding. Ogni risoluzione produce un nuovo NextPDF\Core\Document. PSR-11 consente a un container di restituire valori diversi da chiamate get() successive, a seconda della strategia di binding (PSR-11 §1.1.2). Qui il pacchetto utilizza un factory binding affinché lo stato mutabile con ambito di richiesta non passi mai da una richiesta all’altra. I registry di font e immagini sono singleton. In questo modo si mantiene il contratto secondo cui un identificatore associato si risolve nella voce registrata (PSR-11 §1.1.2), pur condividendo le risorse costose per l’intero worker.

Nel codice di produzione, preferire la constructor injection alla facade. In questo modo la dipendenza diventa esplicita e il controller resta testabile a livello unitario senza avviare la facade root.

Controller collegato tramite DI con gestione tipizzata degli errori

Sezione intitolata “Controller collegato tramite DI con gestione tipizzata degli errori”
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);
}
}
}

Iniettare PdfDocumentInterface, non il Document concreto, così che il binding rimanga sostituibile nei test. Il container restituisce un nuovo documento per ogni istanziazione del controller. Non riutilizzare la stessa istanza del controller per due documenti non correlati all’interno dello stesso processo.

Il blocco catch registra nel log la classe dell’eccezione e restituisce un errore HTTP definito, anziché esporre uno stack trace. Usare Psr\Log\LoggerInterface, che il container risolve nel logger del framework. PSR-3 lascia l’escaping dei segnaposto all’implementatore e indica ai chiamanti di non applicare escaping preventivo ai valori di contesto (PSR-3 §1.2). Passare un contesto strutturato, non stringhe interpolate.

Generazione in coda con callback di successo e di errore

Sezione intitolata “Generazione in coda con callback di successo e di errore”

GeneratePdfJob è un job ShouldQueue. Per impostazione predefinita prevede tre tentativi, un timeout di 120 secondi e un backoff di 10 secondi. È possibile sovrascrivere tutti e tre i valori tramite config/nextpdf.php. La closure builder riceve il documento risolto dal container e deve restituire il documento configurato.

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() inoltra i propri argomenti direttamente al costruttore (string $outputPath, callable $builder, ?callable $onSuccess, ?callable $onFailure). In questo modo le callback di successo e di errore vengono collegate allo stesso job messo in coda. È la stessa forma posizionale di GeneratePdfJob::dispatch($path, $builder) in /integrations/laravel/quickstart/. La callback di successo riceve il percorso di output, mentre la callback di errore riceve il Throwable. Il job espone inoltre setter fluent then() e catch() che restituiscono il job per consentire il concatenamento. Usare tali setter solo quando si conserva e si invia in coda quella stessa istanza, ad esempio tramite l’helper dispatch(). Il job espone anche un metodo failed(), che il queue runner richiama in caso di errore terminale. Le callback sono incapsulate in closure serializzabili in modo da sopravvivere al trasporto della coda.

ProprietàValore predefinitoChiave di configurazione
tries3non guidato dalla configurazione; creare una sottoclasse per modificarlo
timeout120nextpdf.queue.timeout
backoff10non guidato dalla configurazione; creare una sottoclasse per modificarlo
nome della codapdfnextpdf.queue.queue
connessionedefaultnextpdf.queue.connection

tries e backoff sono proprietà pubbliche lette dall’istanza del job. Il job fornito non ne deriva i valori dalla configurazione. Per sovrascriverli quando la policy di retry deve differire, creare una sottoclasse di GeneratePdfJob.

  • La closure builder deve restituire un PdfDocumentInterface. Il job salva quel valore di ritorno, non l’istanza risolta in origine. Il test del job verifica esplicitamente questo contratto.
  • La risoluzione di SignerInterface restituisce null a meno che la firma non sia abilitata, un certificato non sia configurato e nextpdf/premium non sia installato. Verificare sempre la presenza di null prima di firmare.
  • I worker di lunga durata (Octane/RoadRunner/Swoole) condividono il registry dei font bloccato. Configurare preload_fonts affinché il warmup avvenga una sola volta all’avvio del worker, anziché alla prima richiesta.
  • Un job fallito richiama failed() dopo aver esaurito i tries. L’errore del singolo tentativo non richiama onFailure finché il queue runner non dichiara un errore terminale.

La generazione sincrona nel controller blocca la richiesta per l’intera creazione del PDF. Per output su più pagine o in batch, inviare GeneratePdfJob alla coda e restituire immediatamente il controllo. I registry singleton ammortizzano il parsing dei font e la decodifica delle immagini lungo l’intero ciclo di vita del worker. Il costo per richiesta è quindi limitato alla costruzione del documento e all’emissione del contenuto.

Il controller con DI registra nel log la classe dell’eccezione, non il relativo messaggio o trace, per evitare di esporre dettagli interni nei log. GeneratePdfJob convalida il percorso di output sul worker per mitigare payload serializzati manomessi durante il trasporto della coda. La trattazione completa è in /integrations/laravel/security-and-operations/.

DichiarazioneFonteClausolareference_id
Un identificatore associato si risolve nella voce registrataPSR-11 Container§1.1.2
Le risoluzioni successive possono variare in base alla strategia di binding (factory binding)PSR-11 Container§1.1.2

Le linee guida di logging di PSR-3 sono documentate nella specifica PSR-3. Queste linee guida stabiliscono che l’escaping dei segnaposto è responsabilità dell’implementatore e che i chiamanti passano un contesto strutturato. Vedere il documento psr_3_logger §1.2.

L’output firmato PAdES B-B e l’archiviazione PDF/A tramite nextpdf/premium utilizzano la stessa superficie di DI. Si tratta di una funzionalità Enterprise opzionale. Il pacchetto Core qui documentato non richiede alcuna modifica al codice per adottarla. Vedere https://nextpdf.dev/get-license/?intent=laravel-signing.

  • /integrations/laravel/quickstart/ — primo esempio minimo
  • /integrations/laravel/configuration/ — chiavi di coda, firma e font
  • /integrations/laravel/security-and-operations/ — modello delle minacce e hardening
  • /integrations/laravel/troubleshooting/ — errori comuni in produzione