Generowanie pliku PDF w zadaniu z kolejki
W skrócie
Dział zatytułowany „W skrócie”Intensywne generowanie plików PDF nie powinno odbywać się w wątku żądania. Każda integracja z frameworkiem udostępnia API generowania przez kolejkę, które buduje i zapisuje plik PDF w procesie roboczym. Żądanie HTTP może zakończyć się, gdy tylko zlecisz zadanie. Ten przewodnik omawia ścieżkę kolejkową dla Laravel (GeneratePdfJob), Symfony (GeneratePdfMessage przez Messenger) oraz CodeIgniter 4 (GeneratePdfJob poprzez codeigniter4/queue).
Wymagania wstępne to:
- Zainstalowano rdzeń NextPDF oraz jedną integrację z frameworkiem.
- Skonfigurowano transport obsługiwany przez proces roboczy: połączenie kolejki Laravel, transport Symfony Messenger lub kolejkę CodeIgniter 4 z zainstalowanym
codeigniter4/queue. - Działa proces roboczy dla tego transportu.
Ten przewodnik zakłada, że aplikacja ma już kolejkę. Konfigurację kolejki lub Messengera opisuje dokumentacja używanego frameworka.
Instalacja
Dział zatytułowany „Instalacja”Zainstaluj integrację, a następnie zależność kolejki wymaganą przez używany framework.
composer require nextpdf/laravelcomposer require nextpdf/symfony symfony/messengerCodeIgniter wymaga pakietu kolejki. Integracja deklaruje go tylko jako zależność deweloperską, dlatego dodaj go w aplikacji uruchamiającej procesy robocze.
composer require nextpdf/codeigniter codeigniter4/queueW Laravel skonfiguruj połączenie kolejki w config/nextpdf.php (queue.connection, queue.queue, queue.timeout), a następnie uruchom proces roboczy dla tego połączenia.
Przegląd koncepcji
Dział zatytułowany „Przegląd koncepcji”Każda integracja stosuje ten sam wzorzec zgodnie z konwencjami swojego frameworka:
- Laravel dostarcza
NextPDF\Laravel\Jobs\GeneratePdfJob, zadanie typuShouldQueue. Zlecasz je, przekazując ścieżkę wyjściową i domknięcie konstruktora. Domknięcie otrzymuje dokument rozwiązany przez kontener i zwraca skonfigurowany dokument. W procesie roboczym zadanie zapisuje zwrócony dokument pod tą ścieżką. Przyjmuje też opcjonalne wywołania zwrotne obsługi sukcesu i niepowodzenia. - Symfony dostarcza
NextPDF\Symfony\Message\GeneratePdfMessage, komunikatreadonlywysyłany na szynie Messenger, orazGeneratePdfHandler. Handler rozwiązuje konstruktor według nazwy klasy z lokalizatora usług PSR-11. Dla każdego typu dokumentu implementujeszNextPDF\Symfony\Message\PdfBuilderInterface. - CodeIgniter 4 dostarcza
NextPDF\CodeIgniter\Jobs\GeneratePdfJob, zarejestrowany pod nazwą wConfig\Queue::$jobHandlers. Wypychasz zadanie, używając zarejestrowanej nazwy oraz odwołania do konstruktora, ścieżki wyjściowej i tablicy kontekstu. Konstruktor to metoda statyczna ograniczona do przestrzeni nazwApp\PdfBuilders.
Wszystkie trzy integracje mają wspólne podejście do bezpieczeństwa: walidują ścieżkę wyjściową. Symfony i CodeIgniter robią to ponownie podczas konsumpcji, ponieważ ładunek może czekać w kolejce między zleceniem a wykonaniem. Konstruktor działa na świeżym dokumencie w procesie roboczym, więc równoległe zadania nigdy nie współdzielą stanu dokumentu.
Powierzchnia API
Dział zatytułowany „Powierzchnia API”| Zagadnienie | Laravel | Symfony | CodeIgniter 4 |
|---|---|---|---|
| Jednostka kolejki | GeneratePdfJob (ShouldQueue) | GeneratePdfMessage (DTO) + GeneratePdfHandler | GeneratePdfJob (handler kolejki) |
| Zlecanie | GeneratePdfJob::dispatch($path, $builder, $onSuccess, $onFailure) | MessageBusInterface::dispatch(new GeneratePdfMessage(...)) | service('queue')->push($queue, $name, $data) |
| Kształt konstruktora | callable(PdfDocumentInterface): PdfDocumentInterface | PdfBuilderInterface::build(Document, array): Document | static fn(Document, array): Document w przestrzeni nazw App\PdfBuilders |
| Zabezpieczenie ścieżki / danych wejściowych | Zadanie waliduje ścieżkę wyjściową w procesie roboczym | DTO waliduje przy konstrukcji, handler waliduje ponownie podczas konsumpcji | Zadanie ogranicza ścieżkę do WRITEPATH/pdfs/, dopuszcza tylko określoną przestrzeń nazw konstruktora |
| Obsługa niepowodzeń | failed() po wyczerpaniu tries; onFailure przy ostatecznym niepowodzeniu | Strategia ponawiania Messengera; typowane błędy walidacji | InvalidArgumentException / QueueException |
Przykład kodu — szybki start
Dział zatytułowany „Przykład kodu — szybki start”W każdym frameworku użyj takiego minimalnego zlecenia.
<?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),);Ścieżka wyjściowa musi kończyć się na .pdf; zadanie waliduje ścieżkę w procesie roboczym, zanim zapisze plik.
<?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]); }}W CodeIgniter wypychaj zadanie pod kluczem jobHandlers ('generate-pdf'), a nie jako łańcuch klasy zadania. Najpierw zarejestruj handler w 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, ];}Przykład kodu — środowisko produkcyjne
Dział zatytułowany „Przykład kodu — środowisko produkcyjne”Kod produkcyjny podłącza wywołania zwrotne sukcesu i niepowodzenia (Laravel) albo jawnie zarejestrowany konstruktor i typowany handler (Symfony) do loggera PSR-3. Poniższy przykład Laravel zleca zadanie z oboma wywołaniami zwrotnymi.
<?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, ]); }, ); }}Wywołanie zwrotne sukcesu otrzymuje ścieżkę wyjściową. Wywołanie zwrotne niepowodzenia otrzymuje Throwable. Zadanie wyczerpuje tries (domyślnie 3), zanim zostanie uruchomiona ścieżka niepowodzenia. Dostosuj timeout za pomocą nextpdf.queue.timeout. Wartości tries i backoff są właściwościami publicznymi, więc utwórz podklasę GeneratePdfJob, aby je zmienić.
W Symfony zaimplementuj konstruktor i zarejestruj go w lokalizatorze usług. Dzięki temu handler może korzystać wyłącznie z zarejestrowanych konstruktorów.
<?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'W CodeIgniter zaimplementuj konstruktor jako metodę statyczną w przestrzeni nazw App\PdfBuilders. Zadanie odrzuca każde odwołanie do konstruktora spoza tej przestrzeni nazw oraz każdą ścieżkę wyjściową spoza 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; }}Uruchom proces roboczy odpowiedni dla danego frameworka.
php bin/console messenger:consume async --limit=200 --memory-limit=256M --time-limit=3600php spark queue:work pdf-queueOdświeżaj procesy robocze Laravel i Symfony z ograniczonym czasem życia (--limit / --memory-limit / --time-limit), aby ewentualny wyciek pamięci w jednej z zależności nie mógł rosnąć bez końca.
Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- Proces roboczy zapisuje wartość zwróconą przez konstruktor. W każdej integracji proces roboczy zapisuje dokument zwrócony przez konstruktor, a nie instancję pierwotnie dla niego rozwiązaną. Zawsze zwracaj z konstruktora skonfigurowany dokument.
- Walidacja ścieżki działa w procesie roboczym. Symfony waliduje ścieżkę wyjściową przy konstrukcji i ponownie podczas konsumpcji. CodeIgniter ogranicza ścieżkę do
WRITEPATH/pdfs/i odrzuca ścieżki z przejściem katalogu oraz z prefiksem katalogu siostrzanego. Ścieżka, która była bezpieczna podczas zlecania, ale niebezpieczna podczas konsumpcji, nadal jest odrzucana. - W CodeIgniter wypychasz nazwę, a nie klasę. Jeśli wypchniesz
GeneratePdfJob::classjako nazwę zadania, kolejka odrzuci je w momencie wypchnięcia. Zamiast tego wypchnij kluczjobHandlers. - Wywołania zwrotne Laravel muszą być przekazane do statycznego wywołania zlecającego. Jeśli zbudujesz instancję zadania, a następnie wywołasz
$job->dispatch(...), to wywołanie pominie instancję i jej wywołania zwrotne. Przekaż wywołania zwrotne doGeneratePdfJob::dispatch(...). - Rejestry są bezpieczne dla procesów roboczych. Rejestr czcionek to zablokowany singleton o czasie życia procesu, a rejestr obrazów to ograniczony bufor. Dokumenty są tworzone od nowa dla każdego zadania. Nie pobieraj współdzielonego dokumentu w procesie roboczym.
- Podpisywanie w procesach roboczych. Generowanie podpisanego wyjścia lub PDF/A w zadaniu kolejkowym wymaga komercyjnej edycji NextPDF zainstalowanej w środowisku procesu roboczego. Bez niej usługa podpisywania rozwiązuje się do
null. Sprawdź, czy nie zwrócono wartości null przed podpisaniem.
Wydajność
Dział zatytułowany „Wydajność”Przeniesienie generowania do zadania kolejkowego usuwa cały czas budowania pliku PDF z żądania HTTP. Żądanie kończy się, gdy tylko zadanie zostanie zlecone. Rejestry czcionek i obrazów amortyzują koszt konfiguracji przez cały czas życia procesu roboczego, więc koszt pojedynczego zadania ogranicza się do konstrukcji dokumentu i emisji treści. Dopasuj liczbę zadań w toku do puli procesów roboczych i wstępnie wypełnij preload_fonts (Laravel, Symfony), aby rozgrzewanie czcionek następowało jednorazowo przy starcie procesu roboczego, a nie przy pierwszym zadaniu.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”- Gdy broker jest osiągalny, atakujący może wpływać na ładunki kolejki, dlatego traktuj ścieżkę wyjściową i odwołanie do konstruktora w ładunku jako niezaufane. Integracje egzekwują to za pomocą walidacji ścieżki oraz, w CodeIgniter, listy dozwolonych przestrzeni nazw konstruktora.
- Ogranicz uprawnienia systemu plików procesu roboczego do docelowego katalogu wyjściowego jako obronę w głąb. Jeśli zmodyfikowana ścieżka jakimś sposobem przejdzie walidację, nadal nie będzie mogła wyjść poza ten katalog.
- W wywołaniu zwrotnym niepowodzenia rejestruj klasę wyjątku oraz identyfikator korelacji, nigdy komunikat ani ślad stosu.
- Nigdy nie pisz pustego bloku
catch. Każde wywołanie zwrotne niepowodzenia w tym przewodniku rejestruje zdarzenie i przekazuje kontekst.
Strona dotycząca bezpieczeństwa i operacji każdej integracji omawia pełny model zagrożeń kolejki: walidację ładunku, listy dozwolonych obiektów wywoływalnych oraz ograniczenie ścieżki.
Zgodność
Dział zatytułowany „Zgodność”Ten przewodnik nie formułuje żadnego normatywnego twierdzenia o standardach. Każde przedstawione wywołanie API to zweryfikowana publiczna powierzchnia wskazanej integracji. Ścieżka kolejkowa opiera się na gwarancjach wiązania kontenera: świeżym dokumencie przy każdym rozwiązaniu i zablokowanym rejestrze czcionek. Strony nadrzędne dotyczące użycia produkcyjnego, do których odsyła sekcja Zobacz także, dokumentują te gwarancje wraz z ich cytowaniami PSR. Ta strona książki kucharskiej powtarza sposób użycia i odsyła do tamtych stron po cytowania.
Zobacz także
Dział zatytułowany „Zobacz także”- Zwracanie wygenerowanego pliku PDF z kontrolera — synchroniczny odpowiednik.
- Użycie produkcyjne Laravel —
GeneratePdfJob, wywołania zwrotne i tabela strojenia kolejki. - Użycie produkcyjne Symfony — bezpieczeństwo procesów roboczych Messengera i lokalizator konstruktorów.
- Użycie produkcyjne CodeIgniter —
GeneratePdfJob,jobHandlersi ograniczenie ścieżki.