Menghasilkan PDF dalam job yang diantrekan
Sekilas pandang
Bagian berjudul “Sekilas pandang”Pembuatan PDF yang berat tidak boleh berjalan di thread permintaan. Setiap integrasi framework menyediakan API pembuatan yang diantrekan untuk membangun dan menyimpan PDF di worker. Permintaan HTTP dapat langsung merespons setelah Anda mengirim pekerjaan tersebut ke antrean. Panduan ini membahas jalur yang diantrekan untuk Laravel (GeneratePdfJob), Symfony (GeneratePdfMessage melalui Messenger), dan CodeIgniter 4 (GeneratePdfJob melalui codeigniter4/queue).
Prasyaratnya adalah:
- NextPDF core dan satu integrasi framework sudah terpasang.
- Transport worker sudah dikonfigurasi: koneksi antrean Laravel, transport Symfony Messenger, atau antrean CodeIgniter 4 dengan
codeigniter4/queueterpasang. - Proses worker sedang berjalan untuk transport tersebut.
Panduan ini mengasumsikan aplikasi Anda sudah memiliki antrean. Untuk menyiapkan antrean atau Messenger, rujuk dokumentasi framework Anda.
Instalasi
Bagian berjudul “Instalasi”Pasang integrasinya, lalu pasang dependensi antrean yang diperlukan framework Anda.
composer require nextpdf/laravelcomposer require nextpdf/symfony symfony/messengerCodeIgniter memerlukan paket antrean. Integrasi ini mendeklarasikannya sebagai dependensi khusus pengembangan, jadi pasang dengan require pada aplikasi yang menjalankan worker.
composer require nextpdf/codeigniter codeigniter4/queueUntuk Laravel, konfigurasikan koneksi antrean di config/nextpdf.php (queue.connection, queue.queue, queue.timeout), lalu jalankan worker untuk koneksi itu.
Gambaran konseptual
Bagian berjudul “Gambaran konseptual”Setiap integrasi mengikuti pola yang sama, dengan gaya framework-nya masing-masing:
- Laravel menyertakan
NextPDF\Laravel\Jobs\GeneratePdfJob, sebuah jobShouldQueue. Anda mengirimnya ke antrean bersama jalur keluaran dan closure builder. Closure tersebut menerima dokumen yang di-resolve oleh container dan mengembalikan dokumen yang telah dikonfigurasi. Di worker, job menyimpan dokumen yang dikembalikan ke jalur tersebut. Job ini juga menerima callback keberhasilan dan kegagalan yang opsional. - Symfony menyertakan
NextPDF\Symfony\Message\GeneratePdfMessage, sebuah pesanreadonlyyang dikirim ke bus Messenger, besertaGeneratePdfHandler. Handler ini me-resolve builder berdasarkan nama kelas dari service locator PSR-11. Anda mengimplementasikanNextPDF\Symfony\Message\PdfBuilderInterfaceuntuk setiap jenis dokumen. - CodeIgniter 4 menyertakan
NextPDF\CodeIgniter\Jobs\GeneratePdfJob, yang didaftarkan dengan sebuah kunci nama dalamConfig\Queue::$jobHandlers. Anda mendorong job berdasarkan nama terdaftarnya, disertai referensi builder, jalur keluaran, dan sebuah array konteks. Builder adalah metode statis yang dibatasi ke namespaceApp\PdfBuilders.
Ketiga integrasi tersebut memiliki pendekatan keamanan yang sama: mereka memvalidasi jalur keluaran. Symfony dan CodeIgniter memvalidasinya lagi saat dikonsumsi, karena payload dapat berada di antrean antara pengiriman dan eksekusi. Builder berjalan pada dokumen baru di worker, sehingga job yang berjalan bersamaan tidak pernah berbagi state dokumen.
Permukaan API
Bagian berjudul “Permukaan API”| Aspek | Laravel | Symfony | CodeIgniter 4 |
|---|---|---|---|
| Unit yang diantrekan | GeneratePdfJob (ShouldQueue) | GeneratePdfMessage (DTO) + GeneratePdfHandler | GeneratePdfJob (handler antrean) |
| Pengiriman | GeneratePdfJob::dispatch($path, $builder, $onSuccess, $onFailure) | MessageBusInterface::dispatch(new GeneratePdfMessage(...)) | service('queue')->push($queue, $name, $data) |
| Bentuk builder | callable(PdfDocumentInterface): PdfDocumentInterface | PdfBuilderInterface::build(Document, array): Document | static fn(Document, array): Document di bawah App\PdfBuilders |
| Proteksi jalur / masukan | Job memvalidasi jalur keluaran di worker | DTO memvalidasi saat konstruksi; handler memvalidasi ulang saat konsumsi | Job membatasi jalur ke WRITEPATH/pdfs/ dan hanya mengizinkan namespace builder lewat allowlist |
| Permukaan kegagalan | failed() setelah tries; onFailure pada kegagalan terminal | Strategi percobaan ulang Messenger; kesalahan validasi bertipe | InvalidArgumentException / QueueException |
Contoh kode — Mulai cepat
Bagian berjudul “Contoh kode — Mulai cepat”Gunakan pola pengiriman minimal ini di setiap 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),);Jalur keluaran harus diakhiri dengan .pdf; job memvalidasi jalur di worker sebelum menulis berkas.
<?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]); }}Di CodeIgniter, dorong kunci jobHandlers ('generate-pdf'), bukan string kelas job. Daftarkan handler lebih dahulu di 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, ];}Contoh kode — Produksi
Bagian berjudul “Contoh kode — Produksi”Pada pengiriman produksi, hubungkan callback keberhasilan dan kegagalan (Laravel), atau builder yang didaftarkan secara eksplisit beserta handler bertipe (Symfony), ke logger PSR-3. Contoh Laravel berikut melakukan dispatch dengan kedua callback tersebut.
<?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, ]); }, ); }}Callback keberhasilan menerima jalur keluaran. Callback kegagalan menerima Throwable. Job menghabiskan tries (standar 3) sebelum menjalankan jalur kegagalan. Atur timeout melalui nextpdf.queue.timeout. Nilai tries dan backoff adalah properti publik, jadi buat subkelas GeneratePdfJob untuk mengubahnya.
Untuk Symfony, implementasikan builder dan daftarkan di service locator. Ini menjaga handler tetap terbatas pada builder yang terdaftar.
<?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'Untuk CodeIgniter, implementasikan builder sebagai metode statis di bawah App\PdfBuilders. Job menolak setiap referensi builder di luar namespace tersebut dan setiap jalur keluaran di luar 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; }}Jalankan worker untuk setiap framework.
php bin/console messenger:consume async --limit=200 --memory-limit=256M --time-limit=3600php spark queue:work pdf-queueDaur ulang worker Laravel dan Symfony dengan masa hidup terbatas (--limit / --memory-limit / --time-limit) agar alokasi yang bocor pada suatu dependensi tidak dapat tumbuh tanpa batas.
Kasus tepi & jebakan
Bagian berjudul “Kasus tepi & jebakan”- Yang disimpan adalah nilai kembalian builder. Pada setiap integrasi, worker menyimpan dokumen yang dikembalikan oleh builder, bukan instance yang awalnya di-resolve. Selalu kembalikan dokumen yang telah dikonfigurasi dari builder.
- Validasi jalur dijalankan di worker. Symfony memvalidasi jalur keluaran saat konstruksi dan sekali lagi saat dikonsumsi. CodeIgniter membatasi jalur ke
WRITEPATH/pdfs/dan menolak jalur traversal serta jalur dengan prefiks saudara. Jalur yang aman saat pengiriman tetapi tidak aman saat dikonsumsi tetap ditolak. - CodeIgniter menggunakan nama, bukan kelasnya. Jika Anda mendorong
GeneratePdfJob::classsebagai nama job, antrean menolaknya pada saat push. Sebagai gantinya, dorong kuncijobHandlers. - Callback Laravel harus diteruskan ke dispatch statis. Jika Anda membangun sebuah instance job lalu memanggil
$job->dispatch(...), panggilan itu membuang instance beserta callback-nya. Teruskan callback keGeneratePdfJob::dispatch(...). - Registri yang aman untuk worker. Registri fon adalah singleton terkunci dengan masa hidup proses, dan registri gambar adalah cache yang dibatasi. Dokumen selalu baru untuk setiap job. Jangan meminta dokumen bersama di worker.
- Penandatanganan di worker. Keluaran yang ditandatangani atau PDF/A dalam sebuah job antrean memerlukan edisi komersial NextPDF yang terpasang di lingkungan worker. Tanpa edisi tersebut, layanan penandatanganan di-resolve menjadi
null. Lakukan pemeriksaan null sebelum menandatangani.
Performa
Bagian berjudul “Performa”Memindahkan proses pembuatan ke job yang diantrekan mengeluarkan seluruh waktu pembangunan PDF dari permintaan HTTP. Permintaan merespons begitu pekerjaan dikirim ke antrean. Registri fon dan gambar mengamortisasi biaya penyiapannya sepanjang masa hidup worker, sehingga biaya per job hanya terbatas pada konstruksi dokumen dan emisi konten. Sesuaikan jumlah job yang sedang berjalan dengan kumpulan worker Anda, dan isi preload_fonts di awal (Laravel, Symfony) agar pemanasan fon terjadi sekali saat worker booting, bukan pada job pertama.
Catatan keamanan
Bagian berjudul “Catatan keamanan”- Payload antrean dapat dipengaruhi penyerang jika broker dapat dijangkau, jadi perlakukan jalur keluaran dan referensi builder dalam payload sebagai tidak tepercaya. Integrasi menegakkannya dengan validasi jalur dan, di CodeIgniter, sebuah allowlist namespace builder.
- Batasi izin sistem berkas worker hanya pada direktori keluaran yang dituju sebagai pertahanan berlapis. Jika sebuah jalur yang dimanipulasi entah bagaimana lolos validasi, jalur itu tetap tidak dapat keluar dari direktori.
- Catat kelas exception dan sebuah pengenal korelasi dalam callback kegagalan; jangan pernah mencatat pesan atau jejaknya.
- Jangan pernah menulis blok
catchyang kosong. Setiap callback kegagalan di sini mencatat log dan menyertakan konteks.
Halaman keamanan dan operasi setiap integrasi membahas model ancaman antrean secara lengkap: validasi payload, allowlist callable, dan pembatasan jalur.
Konformitas
Bagian berjudul “Konformitas”Panduan ini tidak membuat klaim standar normatif apa pun. Setiap panggilan API yang ditampilkan adalah permukaan publik terverifikasi dari integrasi yang disebutkan. Jalur yang diantrekan bergantung pada jaminan pengikatan container: dokumen baru untuk setiap resolusi dan registri fon yang terkunci. Halaman penggunaan produksi hulu yang ditautkan di bawah Lihat juga mendokumentasikan jaminan tersebut beserta kutipan PSR-nya. Halaman cookbook ini menyatakan ulang penggunaannya dan menyerahkan kutipan kepada halaman-halaman tersebut.
Lihat juga
Bagian berjudul “Lihat juga”- Mengembalikan PDF yang dihasilkan dari controller — padanan sinkronnya.
- Penggunaan produksi Laravel —
GeneratePdfJob, callback, dan tabel pengaturan antrean. - Penggunaan produksi Symfony — keamanan worker Messenger dan locator builder.
- Penggunaan produksi CodeIgniter —
GeneratePdfJob,jobHandlers, dan pembatasan jalur.