Przejdź do głównej zawartości
getnextpdf.com

Generowanie pliku PDF w zadaniu z kolejki

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.

Zainstaluj integrację, a następnie zależność kolejki wymaganą przez używany framework.

Okno terminala
composer require nextpdf/laravel
Okno terminala
composer require nextpdf/symfony symfony/messenger

CodeIgniter wymaga pakietu kolejki. Integracja deklaruje go tylko jako zależność deweloperską, dlatego dodaj go w aplikacji uruchamiającej procesy robocze.

Okno terminala
composer require nextpdf/codeigniter codeigniter4/queue

W 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.

Każda integracja stosuje ten sam wzorzec zgodnie z konwencjami swojego frameworka:

  • Laravel dostarcza NextPDF\Laravel\Jobs\GeneratePdfJob, zadanie typu ShouldQueue. 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, komunikat readonly wysyłany na szynie Messenger, oraz GeneratePdfHandler. Handler rozwiązuje konstruktor według nazwy klasy z lokalizatora usług PSR-11. Dla każdego typu dokumentu implementujesz NextPDF\Symfony\Message\PdfBuilderInterface.
  • CodeIgniter 4 dostarcza NextPDF\CodeIgniter\Jobs\GeneratePdfJob, zarejestrowany pod nazwą w Config\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 nazw App\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.

ZagadnienieLaravelSymfonyCodeIgniter 4
Jednostka kolejkiGeneratePdfJob (ShouldQueue)GeneratePdfMessage (DTO) + GeneratePdfHandlerGeneratePdfJob (handler kolejki)
ZlecanieGeneratePdfJob::dispatch($path, $builder, $onSuccess, $onFailure)MessageBusInterface::dispatch(new GeneratePdfMessage(...))service('queue')->push($queue, $name, $data)
Kształt konstruktoracallable(PdfDocumentInterface): PdfDocumentInterfacePdfBuilderInterface::build(Document, array): Documentstatic fn(Document, array): Document w przestrzeni nazw App\PdfBuilders
Zabezpieczenie ścieżki / danych wejściowychZadanie waliduje ścieżkę wyjściową w procesie roboczymDTO waliduje przy konstrukcji, handler waliduje ponownie podczas konsumpcjiZadanie ogranicza ścieżkę do WRITEPATH/pdfs/, dopuszcza tylko określoną przestrzeń nazw konstruktora
Obsługa niepowodzeńfailed() po wyczerpaniu tries; onFailure przy ostatecznym niepowodzeniuStrategia ponawiania Messengera; typowane błędy walidacjiInvalidArgumentException / QueueException

W każdym frameworku użyj takiego minimalnego zlecenia.

Laravel: dispatch GeneratePdfJob
<?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.

Symfony: dispatch GeneratePdfMessage from a controller
<?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);
}
}
CodeIgniter 4: push GeneratePdfJob by its registered name
<?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.

CodeIgniter 4: 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,
];
}

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.

Laravel: app/Jobs/DispatchMonthlyStatement.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
{
// 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.

Symfony: src/Pdf/InvoicePdfBuilder.php
<?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;
}
}
Symfony: config/services.yaml (builder locator)
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/.

CodeIgniter 4: app/PdfBuilders/InvoiceBuilder.php
<?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.

Okno terminala
php bin/console messenger:consume async --limit=200 --memory-limit=256M --time-limit=3600
Okno terminala
php spark queue:work pdf-queue

Odś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.

  • 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::class jako nazwę zadania, kolejka odrzuci je w momencie wypchnięcia. Zamiast tego wypchnij klucz jobHandlers.
  • 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 do GeneratePdfJob::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.

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.

  • 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.

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.