跳轉到

Queue Job

GeneratePdfJob 將 PDF 生成任務推入 Laravel Queue 系統,讓耗時的 PDF 生成不阻塞 HTTP 請求週期。Job 完成後可透過 Laravel Notification 或自訂 Callback 通知使用者。

PHP Compatibility

This example uses PHP 8.5 syntax. If your environment runs PHP 8.1 or 7.4, use NextPDF Backport for a backward-compatible build.

基本用法

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Jobs\GenerateInvoicePdfJob;
use Illuminate\Http\JsonResponse;

final class InvoiceController extends Controller
{
    public function generate(int $invoiceId): JsonResponse
    {
        GenerateInvoicePdfJob::dispatch($invoiceId)
            ->onQueue('pdf-generation')
            ->delay(now()->addSeconds(5));

        return response()->json(['status' => 'queued']);
    }
}

建立自訂 PDF Job

繼承 GeneratePdfJob 抽象基礎類別以實作業務邏輯:

<?php

declare(strict_types=1);

namespace App\Jobs;

use NextPDF\Laravel\Jobs\GeneratePdfJob;
use NextPDF\Core\Contracts\DocumentFactoryInterface;

final class GenerateInvoicePdfJob extends GeneratePdfJob
{
    public function __construct(
        private readonly int $invoiceId,
    ) {
        parent::__construct();
    }

    protected function build(DocumentFactoryInterface $factory): string
    {
        $invoice = Invoice::findOrFail($this->invoiceId);

        $document = $factory->create(title: "Invoice #{$this->invoiceId}");
        $document->addPage();
        $document->text($invoice->customerName, x: 20, y: 30);

        return $document->output(); // 回傳 PDF bytes
    }

    protected function onSuccess(string $pdfBytes): void
    {
        // 儲存到 Storage 並通知使用者
        $path = "invoices/{$this->invoiceId}.pdf";
        \Storage::disk('s3')->put($path, $pdfBytes);

        Invoice::find($this->invoiceId)?->user->notify(
            new InvoicePdfReadyNotification(downloadUrl: \Storage::temporaryUrl($path, now()->addHour())),
        );
    }
}

重試與失敗處理

final class GenerateInvoicePdfJob extends GeneratePdfJob
{
    // Job 最多重試次數
    public int $tries = 3;

    // 最大執行時間(秒)
    public int $timeout = 120;

    // 重試間隔(秒)
    public array $backoff = [10, 30, 60];

    // 唯一鎖:防止同一份 Invoice 同時被多個 Worker 處理
    public function uniqueId(): string
    {
        return "invoice-pdf-{$this->invoiceId}";
    }

    public function failed(\Throwable $exception): void
    {
        \Log::error("PDF generation failed for invoice {$this->invoiceId}", [
            'error' => $exception->getMessage(),
        ]);

        Invoice::find($this->invoiceId)?->user->notify(
            new InvoicePdfFailedNotification(),
        );
    }
}

進度追蹤

對於大型批次 PDF 生成,可啟用進度回報:

final class GenerateBatchReportJob extends GeneratePdfJob
{
    use \Illuminate\Bus\Batchable;

    protected function build(DocumentFactoryInterface $factory): string
    {
        $this->batch()?->add([]);

        // 分頁生成,每頁後回報進度
        $document = $factory->create();
        foreach ($this->pageData as $index => $data) {
            $document->addPage();
            $document->text($data['content'], x: 20, y: 30);

            // 回報進度(0–100)
            $this->reportProgress(
                processed: $index + 1,
                total: count($this->pageData),
            );
        }

        return $document->output();
    }
}

Laravel Horizon 整合

// config/horizon.php — 為 PDF 生成設定專屬 Worker pool
'environments' => [
    'production' => [
        'pdf-supervisor' => [
            'connection' => 'redis',
            'queue'      => ['pdf-generation'],
            'balance'    => 'auto',
            'minProcesses' => 1,
            'maxProcesses' => 4,
            'memory'     => 512,   // PDF 生成記憶體需求較高
            'timeout'    => 120,
        ],
    ],
],

測試 Queue Job

use Illuminate\Support\Facades\Queue;
use App\Jobs\GenerateInvoicePdfJob;

it('dispatches pdf generation job', function () {
    Queue::fake();

    $this->post('/invoices/1/generate');

    Queue::assertPushed(GenerateInvoicePdfJob::class, fn ($job) => $job->invoiceId === 1);
    Queue::assertPushedOn('pdf-generation', GenerateInvoicePdfJob::class);
});

it('executes pdf job successfully', function () {
    $job = new GenerateInvoicePdfJob(invoiceId: 1);
    $job->handle(app(DocumentFactoryInterface::class));

    Storage::assertExists('invoices/1.pdf');
});

參見