PDF in einem Queue-Job erzeugen
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Aufwendige PDF-Generierung gehört nicht in den Request-Thread. Jede Framework-Integration stellt eine Queue-Generierungsschnittstelle bereit, die ein PDF auf einem Worker erstellt und speichert. Dadurch kann der HTTP-Request zurückkehren, sobald die Arbeit dispatcht wurde. Diese Anleitung beschreibt den Queue-Pfad für Laravel (GeneratePdfJob), Symfony (GeneratePdfMessage über Messenger) und CodeIgniter 4 (GeneratePdfJob über codeigniter4/queue).
Die Voraussetzungen sind:
- NextPDF core und eine Framework-Integration sind installiert.
- Ein Worker-Transport ist konfiguriert: eine Laravel-Queue-Verbindung, ein Symfony-Messenger-Transport oder eine CodeIgniter-4-Queue mit installiertem
codeigniter4/queue. - Ein Worker-Prozess läuft für diesen Transport.
Diese Anleitung setzt eine Anwendung voraus, die bereits eine Queue nutzt. Für die Einrichtung der Queue oder von Messenger selbst lesen Sie die Dokumentation Ihres Frameworks.
Installation
Abschnitt betitelt „Installation“Installieren Sie die Integration und anschließend die Queue-Abhängigkeit, die Ihr Framework benötigt.
composer require nextpdf/laravelcomposer require nextpdf/symfony symfony/messengerCodeIgniter benötigt das Queue-Paket. Die Integration deklariert es nur als Entwicklungsabhängigkeit; installieren Sie es daher direkt in der Anwendung, die Worker ausführt.
composer require nextpdf/codeigniter codeigniter4/queueKonfigurieren Sie bei Laravel die Queue-Verbindung in config/nextpdf.php (queue.connection, queue.queue, queue.timeout) und betreiben Sie einen Worker für diese Verbindung.
Konzeptioneller Überblick
Abschnitt betitelt „Konzeptioneller Überblick“Jede Integration setzt dieselbe Idee auf eigene Weise um:
- Laravel liefert
NextPDF\Laravel\Jobs\GeneratePdfJob, einenShouldQueue-Job. Er wird mit einem Ausgabepfad und einer Builder-Closure dispatcht. Die Closure erhält ein vom Container aufgelöstes Dokument und gibt das konfigurierte Dokument zurück. Der Job speichert dieses zurückgegebene Dokument auf dem Worker unter dem Pfad. Außerdem akzeptiert er optionale Erfolgs- und Fehler-Callbacks. - Symfony liefert
NextPDF\Symfony\Message\GeneratePdfMessage, einereadonly-Message, die auf dem Messenger-Bus dispatcht wird, sowieGeneratePdfHandler, der einen Builder über den Klassennamen aus einem PSR-11-Service-Locator auflöst. Implementieren SieNextPDF\Symfony\Message\PdfBuilderInterfacefür jeden Dokumenttyp. - CodeIgniter 4 liefert
NextPDF\CodeIgniter\Jobs\GeneratePdfJob, registriert unter einem Namensschlüssel inConfig\Queue::$jobHandlers. Der Job wird über seinen registrierten Namen mit einer Builder-Referenz, einem Ausgabepfad und einem Kontext-Array gepusht. Der Builder ist eine statische Methode, die auf den NamespaceApp\PdfBuildersbeschränkt ist.
Alle drei verfolgen dasselbe Sicherheitsmodell: Der Ausgabepfad wird validiert. Symfony und CodeIgniter validieren ihn bei der Verarbeitung erneut, weil eine Payload zwischen Dispatch und Ausführung in einer Queue liegen kann. Der Builder arbeitet auf dem Worker mit einem frischen Dokument, sodass parallele Jobs niemals Dokumentzustand teilen.
API-Oberfläche
Abschnitt betitelt „API-Oberfläche“| Aspekt | Laravel | Symfony | CodeIgniter 4 |
|---|---|---|---|
| Queue-Einheit | 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-Form | callable(PdfDocumentInterface): PdfDocumentInterface | PdfBuilderInterface::build(Document, array): Document | static fn(Document, array): Document unter App\PdfBuilders |
| Pfad-/Eingabe-Schutz | Job validiert den Ausgabepfad auf dem Worker | DTO validiert bei der Konstruktion, Handler validiert bei der Verarbeitung erneut | Job beschränkt den Pfad auf WRITEPATH/pdfs/, setzt den Builder-Namespace auf die Allowlist |
| Fehler-Oberfläche | failed() nach tries; onFailure bei endgültigem Fehlschlag | Messenger-Retry-Strategie; typisierte Validierungsfehler | InvalidArgumentException / QueueException |
Codebeispiel – Schnellstart
Abschnitt betitelt „Codebeispiel – Schnellstart“Der minimale Dispatch-Aufruf in jedem 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),);Der Ausgabepfad muss auf .pdf enden; der Job validiert den Pfad auf dem Worker, bevor er schreibt.
<?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]); }}In CodeIgniter pushen Sie den jobHandlers-Schlüssel ('generate-pdf'), nicht den Job-Klassenstring. Registrieren Sie den Handler zuerst 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, ];}Codebeispiel – Produktion
Abschnitt betitelt „Codebeispiel – Produktion“Ein Produktions-Dispatch bindet Erfolgs- und Fehler-Callbacks (Laravel) oder einen explizit registrierten Builder und einen typisierten Handler (Symfony) ein und protokolliert über einen PSR-3-Logger. Die folgende Laravel-Variante dispatcht mit beiden 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, ]); }, ); }}Der Erfolgs-Callback erhält den Ausgabepfad; der Fehler-Callback erhält das Throwable. Der Job nutzt tries (Standardwert 3) vollständig, bevor der Fehlerpfad ausgeführt wird. Stellen Sie timeout über nextpdf.queue.timeout ein. Die Werte tries und backoff sind öffentliche Eigenschaften; leiten Sie also eine Unterklasse von GeneratePdfJob ab, um sie zu ändern.
Implementieren Sie bei Symfony den Builder und registrieren Sie ihn in einem Service-Locator, sodass der Handler nur auf registrierte Builder zugreifen kann.
<?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'Implementieren Sie bei CodeIgniter den Builder als statische Methode unter App\PdfBuilders. Der Job lehnt jede Builder-Referenz außerhalb dieses Namespace und jeden Ausgabepfad außerhalb von WRITEPATH/pdfs/ ab.
<?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; }}Betreiben Sie den Worker für jedes Framework.
php bin/console messenger:consume async --limit=200 --memory-limit=256M --time-limit=3600php spark queue:work pdf-queueStarten Sie Laravel- und Symfony-Worker mit begrenzten Lebensdauern (--limit / --memory-limit / --time-limit) regelmäßig neu, damit ein Speicherleck in einer Abhängigkeit nicht unbegrenzt anwachsen kann.
Sonderfälle & Fallstricke
Abschnitt betitelt „Sonderfälle & Fallstricke“- Der Rückgabewert des Builders wird gespeichert. In jeder Integration speichert der Worker das Dokument, das der Builder zurückgibt, nicht die ursprünglich aufgelöste Instanz. Geben Sie aus dem Builder immer das konfigurierte Dokument zurück.
- Die Pfad-Validierung läuft auf dem Worker. Symfony validiert den Ausgabepfad bei der Konstruktion und erneut bei der Verarbeitung. CodeIgniter beschränkt den Pfad auf
WRITEPATH/pdfs/und lehnt Traversal- und Geschwister-Präfix-Pfade ab. Ein Pfad, der beim Dispatch sicher war, bei der Verarbeitung aber unsicher ist, wird trotzdem abgelehnt. - CodeIgniter pusht den Namen, nicht die Klasse. Das Pushen von
GeneratePdfJob::classals Job-Name wird von der Queue beim Pushen abgelehnt. Pushen Sie stattdessen denjobHandlers-Schlüssel. - Laravel-Callbacks müssen an den statischen Dispatch-Aufruf übergeben werden. Eine Job-Instanz zu bauen und dann
$job->dispatch(...)aufzurufen, verwirft diese Instanz und ihre Callbacks. Übergeben Sie die Callbacks anGeneratePdfJob::dispatch(...). - Worker-sichere Registries. Die Font-Registry ist ein gesperrtes Singleton mit Prozesslebensdauer, die Image-Registry ein begrenzter Cache. Dokumente sind pro Job frisch. Fordern Sie auf dem Worker kein gemeinsam genutztes Dokument an.
- Signieren in Workern. Signierte oder PDF/A-Ausgabe in einem Queue-Job erfordert eine kommerzielle NextPDF-Edition, die in der Worker-Umgebung installiert ist; ohne sie löst der Signatur-Service zu
nullauf. Prüfen Sie vor dem Signieren auf null.
Performance
Abschnitt betitelt „Performance“Die Verlagerung der Generierung in einen Queue-Job entfernt die gesamte PDF-Build-Zeit aus dem HTTP-Request: Der Request kehrt zurück, sobald die Arbeit dispatcht wurde. Die Font- und Image-Registries amortisieren ihre Einrichtungskosten über die Worker-Lebensdauer, sodass die Kosten pro Job auf die Dokumenterstellung und die Inhaltsausgabe beschränkt sind. Dimensionieren Sie die Anzahl der laufenden Jobs anhand Ihres Worker-Pools und befüllen Sie preload_fonts (Laravel, Symfony) vorab, damit das Font-Warmup bereits beim Worker-Boot stattfindet und nicht erst beim ersten Job.
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“- Queue-Payloads sind von Angreifern beeinflussbar, wenn der Broker erreichbar ist; behandeln Sie den Ausgabepfad und die Builder-Referenz in einer Payload daher als nicht vertrauenswürdig. Die Integrationen erzwingen das per Pfad-Validierung und, in CodeIgniter, per Allowlist für den Builder-Namespace.
- Beschränken Sie die Dateisystem-Berechtigungen des Workers als Verteidigung in der Tiefe auf das vorgesehene Ausgabeverzeichnis, sodass ein manipulierter Pfad, der die Validierung irgendwie passiert, das Verzeichnis trotzdem nicht verlassen kann.
- Protokollieren Sie im Fehler-Callback die Exception-Klasse und einen Korrelations-Identifier, niemals die Nachricht oder den Trace.
- Schreiben Sie niemals einen leeren
catch-Block. Jeder Fehler-Callback hier protokolliert und trägt Kontext mit.
Das vollständige Queue-Bedrohungsmodell – Payload-Validierung, Callable-Allowlists und Pfad-Beschränkung – steht auf der Seite zu Sicherheit und Betrieb jeder Integration.
Konformität
Abschnitt betitelt „Konformität“Diese Anleitung erhebt keinen normativen Standards-Anspruch. Jeder gezeigte API-Aufruf ist Teil der verifizierten öffentlichen Oberfläche der genannten Integration. Die Container-Binding-Garantien, auf die sich der Queue-Pfad stützt (ein frisches Dokument pro Auflösung, die gesperrte Font-Registry), sind mit ihren PSR-Quellenangaben auf den vorgelagerten Produktionsnutzungsseiten dokumentiert, die unter „Siehe auch“ verlinkt sind. Diese Cookbook-Seite gibt die Verwendung wieder und überlässt die Quellenangaben diesen Seiten.
Siehe auch
Abschnitt betitelt „Siehe auch“- Ein generiertes PDF aus einem Controller zurückgeben – das synchrone Gegenstück.
- Laravel-Produktionsnutzung –
GeneratePdfJob, Callbacks und die Queue-Tuning-Tabelle. - Symfony-Produktionsnutzung – Messenger-Worker-Sicherheit und der Builder-Locator.
- CodeIgniter-Produktionsnutzung –
GeneratePdfJob,jobHandlersund Pfad-Beschränkung.