PDF genereren in een queued job
In één oogopslag
Sectie met titel “In één oogopslag”Zware PDF-generatie hoort niet op de request-thread te draaien. Elke framework-integratie biedt een API voor queued generatie waarmee een worker een PDF bouwt en opslaat. De HTTP-request kan terugkeren zodra je het werk dispatcht. Deze handleiding behandelt het queued-pad voor Laravel (GeneratePdfJob), Symfony (GeneratePdfMessage via Messenger) en CodeIgniter 4 (GeneratePdfJob via codeigniter4/queue).
De vereisten zijn:
- NextPDF-core en één framework-integratie zijn geïnstalleerd.
- Er is een worker-transport geconfigureerd: een Laravel-queueverbinding, een Symfony-Messenger-transport of een CodeIgniter 4-queue met
codeigniter4/queuegeïnstalleerd. - Er draait een worker-proces voor dat transport.
Deze handleiding gaat ervan uit dat je applicatie al een queue heeft. Gebruik voor het opzetten van de queue of Messenger de documentatie van je eigen framework.
Installatie
Sectie met titel “Installatie”Installeer de integratie en daarna de queue-afhankelijkheid die je framework nodig heeft.
composer require nextpdf/laravelcomposer require nextpdf/symfony symfony/messengerCodeIgniter heeft het queue-pakket nodig. De integratie declareert dit als een development-only-afhankelijkheid, dus voeg het toe aan de applicatie die workers draait.
composer require nextpdf/codeigniter codeigniter4/queueConfigureer in Laravel de queueverbinding in config/nextpdf.php (queue.connection, queue.queue, queue.timeout) en draai daarna een worker voor die verbinding.
Conceptueel overzicht
Sectie met titel “Conceptueel overzicht”Elke integratie gebruikt hetzelfde patroon, telkens in de stijl van het framework:
- Laravel levert
NextPDF\Laravel\Jobs\GeneratePdfJob, eenShouldQueue-job. Je dispatcht die met een uitvoerpad en een builder-closure. De closure krijgt een door de container opgelost document en retourneert het geconfigureerde document. Op de worker slaat de job het geretourneerde document op naar het pad. Hij accepteert ook optionele success- en failure-callbacks. - Symfony levert
NextPDF\Symfony\Message\GeneratePdfMessage, eenreadonly-message die op de Messenger-bus wordt gedispatcht, plusGeneratePdfHandler. De handler lost een builder op basis van de klassenaam op uit een PSR-11-servicelocator. Je implementeertNextPDF\Symfony\Message\PdfBuilderInterfacevoor elk documenttype. - CodeIgniter 4 levert
NextPDF\CodeIgniter\Jobs\GeneratePdfJob, geregistreerd onder een naamsleutel inConfig\Queue::$jobHandlers. Je pusht de job met de geregistreerde naam, samen met een builder-referentie, een uitvoerpad en een context-array. De builder is een statische methode die beperkt is tot deApp\PdfBuilders-namespace.
Alle drie de integraties hanteren dezelfde beveiligingshouding: ze valideren het uitvoerpad. Symfony en CodeIgniter hervalideren het op het moment van consumptie, omdat een payload tussen dispatch en uitvoering in een queue kan wachten. De builder draait op de worker tegen een vers document, zodat gelijktijdige jobs nooit document-state delen.
API-oppervlak
Sectie met titel “API-oppervlak”| Aspect | Laravel | Symfony | CodeIgniter 4 |
|---|---|---|---|
| Queued eenheid | GeneratePdfJob (ShouldQueue) | GeneratePdfMessage (DTO) + GeneratePdfHandler | GeneratePdfJob (queue-handler) |
| Dispatch | GeneratePdfJob::dispatch($path, $builder, $onSuccess, $onFailure) | MessageBusInterface::dispatch(new GeneratePdfMessage(...)) | service('queue')->push($queue, $name, $data) |
| Builder-vorm | callable(PdfDocumentInterface): PdfDocumentInterface | PdfBuilderInterface::build(Document, array): Document | static fn(Document, array): Document onder App\PdfBuilders |
| Pad-/invoerbescherming | Job valideert het uitvoerpad op de worker | DTO valideert bij constructie, handler hervalideert bij consumptie | Job beperkt het pad tot WRITEPATH/pdfs/, plaatst de builder-namespace op een allowlist |
| Foutoppervlak | failed() na tries; onFailure bij definitieve mislukking | Messenger-retrystrategie; getypeerde validatiefouten | InvalidArgumentException / QueueException |
Codevoorbeeld — snelstart
Sectie met titel “Codevoorbeeld — snelstart”Gebruik deze minimale dispatch in elk framework.
<?php
declare(strict_types=1);
use NextPDF\Contracts\PdfDocumentInterface;use NextPDF\Laravel\Jobs\GeneratePdfJob;
GeneratePdfJob::dispatch( storage_path('app/reports/january-2026.pdf'), static fn (PdfDocumentInterface $document): PdfDocumentInterface => $document ->addPage() ->cell(0, 10, 'January report', newLine: true),);Het uitvoerpad moet eindigen op .pdf; de job valideert het pad op de worker voordat hij het bestand schrijft.
<?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); }}<?php
declare(strict_types=1);
namespace App\Controllers;
use CodeIgniter\HTTP\ResponseInterface;
final class InvoiceController extends BaseController{ public function queueInvoice(int $id): ResponseInterface { service('queue')->push('pdf-queue', 'generate-pdf', [ 'builder' => 'App\\PdfBuilders\\InvoiceBuilder::build', 'outputPath' => WRITEPATH . 'pdfs/invoice-' . $id . '.pdf', 'context' => ['invoice_id' => $id], ]);
return $this->response ->setStatusCode(ResponseInterface::HTTP_ACCEPTED) ->setJSON(['status' => 'queued', 'invoice_id' => $id]); }}Push in CodeIgniter de jobHandlers-sleutel ('generate-pdf'), niet de job-klassestring. Registreer de handler eerst in app/Config/Queue.php.
<?php
declare(strict_types=1);
namespace Config;
use CodeIgniter\Queue\Config\Queue as BaseQueue;use NextPDF\CodeIgniter\Jobs\GeneratePdfJob;
final class Queue extends BaseQueue{ /** @var array<string, class-string> */ public array $jobHandlers = [ 'generate-pdf' => GeneratePdfJob::class, ];}Codevoorbeeld — productie
Sectie met titel “Codevoorbeeld — productie”Een productie-dispatch koppelt success- en failure-callbacks (Laravel), of een expliciet geregistreerde builder en een getypeerde handler (Symfony), aan een PSR-3-logger. Het Laravel-voorbeeld hieronder dispatcht met beide callbacks.
<?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 { // dispatch() is public static: it constructs the job from the // arguments it receives. Pass every argument — including the // callbacks — to the static call, not to a separately built instance. 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, ]); }, ); }}De success-callback ontvangt het uitvoerpad. De failure-callback ontvangt de Throwable. De job laat tries (standaard 3) verstrijken voordat het failure-pad draait. Stem timeout af via nextpdf.queue.timeout. De waarden tries en backoff zijn publieke properties, dus maak een subclass van GeneratePdfJob om ze te wijzigen.
Implementeer voor Symfony de builder en registreer die in een servicelocator. Zo blijft de handler beperkt tot geregistreerde builders.
<?php
declare(strict_types=1);
namespace App\Pdf;
use NextPDF\Core\Document;use NextPDF\Symfony\Message\PdfBuilderInterface;
final class InvoicePdfBuilder implements PdfBuilderInterface{ /** @param array<string, mixed> $context */ public function build(Document $document, array $context): Document { $document->addPage(); $document->setFont('dejavusans', '', 12); $document->cell(0, 10, 'Invoice #' . $context['invoice_id']);
return $document; }}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'Implementeer voor CodeIgniter de builder als statische methode onder App\PdfBuilders. De job weigert elke builder-referentie buiten die namespace en elk uitvoerpad buiten WRITEPATH/pdfs/.
<?php
declare(strict_types=1);
namespace App\PdfBuilders;
use NextPDF\Core\Document;
final class InvoiceBuilder{ /** @param array<string, mixed> $context */ public static function build(Document $document, array $context): Document { $invoiceId = (int) ($context['invoice_id'] ?? 0);
$document->addPage(); $document->cell(0, 10, "Invoice #{$invoiceId}");
return $document; }}Draai de worker voor elk framework.
php bin/console messenger:consume async --limit=200 --memory-limit=256M --time-limit=3600php spark queue:work pdf-queueRecycle Laravel- en Symfony-workers met begrensde levensduren (--limit / --memory-limit / --time-limit) zodat een gelekte allocatie in een afhankelijkheid niet onbegrensd kan groeien.
Randgevallen & valkuilen
Sectie met titel “Randgevallen & valkuilen”- De geretourneerde waarde van de builder is wat wordt opgeslagen. In elke integratie slaat de worker het document op dat de builder retourneert, niet de instantie die hij aanvankelijk heeft opgelost. Retourneer altijd het geconfigureerde document vanuit de builder.
- Padvalidatie draait op de worker. Symfony valideert het uitvoerpad bij constructie en opnieuw op het moment van consumptie. CodeIgniter beperkt het pad tot
WRITEPATH/pdfs/en weigert traversal- en sibling-prefix-paden. Een pad dat veilig was bij dispatch maar onveilig is bij consumptie, wordt nog steeds geweigerd. - CodeIgniter pusht de naam, niet de klasse. Als je
GeneratePdfJob::classals job-naam pusht, weigert de queue die op het moment van pushen. Push in plaats daarvan dejobHandlers-sleutel. - Laravel-callbacks moeten worden meegegeven aan de statische dispatch. Als je een job-instantie bouwt en vervolgens
$job->dispatch(...)aanroept, verwerpt die aanroep de instantie en de bijbehorende callbacks. Geef de callbacks mee aanGeneratePdfJob::dispatch(...). - Worker-veilige registries. De font-registry is een vergrendelde singleton met proceslevensduur en de image-registry is een begrensde cache. Documenten zijn per job vers. Vraag op de worker geen gedeeld document aan.
- Ondertekenen in workers. Ondertekende of PDF/A-uitvoer in een queue-job vereist een commerciële NextPDF-editie die in de worker-omgeving is geïnstalleerd. Zonder die editie wordt de signing-service opgelost naar
null. Voer een null-check uit voordat je ondertekent.
Prestaties
Sectie met titel “Prestaties”Door generatie naar een queued job te verplaatsen, verdwijnt de volledige PDF-bouwtijd uit de HTTP-request. De request keert terug zodra het werk is gedispatcht. De font- en image-registries spreiden hun opzetkosten over de levensduur van de worker, zodat de kosten per job beperkt blijven tot documentconstructie en content-emissie. Stem het aantal jobs in behandeling af op je worker-pool en configureer preload_fonts vooraf (Laravel, Symfony), zodat de font-warmup één keer bij het opstarten van de worker plaatsvindt in plaats van bij de eerste job.
Beveiligingsnotities
Sectie met titel “Beveiligingsnotities”- Queue-payloads kunnen door aanvallers worden beïnvloed wanneer de broker bereikbaar is, dus behandel het uitvoerpad en de builder-referentie in een payload als niet-vertrouwd. De integraties dwingen dit af met padvalidatie en, in CodeIgniter, een allowlist voor de builder-namespace.
- Beperk de bestandssysteemrechten van de worker tot de beoogde uitvoermap als verdediging in de diepte. Als een gemanipuleerd pad op de een of andere manier de validatie passeert, kan het nog steeds niet aan de map ontsnappen.
- Log in de failure-callback de exceptie-klasse en een correlatie-identifier, nooit het bericht of de trace.
- Schrijf nooit een leeg
catch-blok. Elke failure-callback hier logt en draagt context mee.
De security-and-operations-pagina van elke integratie behandelt het volledige queue-dreigingsmodel: payloadvalidatie, callable-allowlists en padbeperking.
Conformiteit
Sectie met titel “Conformiteit”Deze handleiding doet geen normatieve claim over standaarden. Elke getoonde API-aanroep behoort tot het geverifieerde publieke oppervlak van de genoemde integratie. Het queued-pad steunt op container-binding-garanties: een vers document per resolutie en de vergrendelde font-registry. De upstream production-usage-pagina’s die onder Zie ook zijn gelinkt, documenteren die garanties met hun PSR-citaties. Deze cookbook-pagina herhaalt het gebruik en laat de citaties over aan die pagina’s.
Zie ook
Sectie met titel “Zie ook”- Een gegenereerde PDF terugsturen vanuit een controller — de synchrone tegenhanger.
- Laravel production usage —
GeneratePdfJob, callbacks en de queue-tuningtabel. - Symfony production usage — Messenger-worker-veiligheid en de builder-locator.
- CodeIgniter production usage —
GeneratePdfJob,jobHandlersen padbeperking.