跳轉到

Laravel 快速開始

本指南說明如何在 Laravel 12 專案中整合 nextpdf/laravel,包含同步與非同步(Queue) 兩種 PDF 生成模式。

前置條件: - Laravel 12.x 專案 - PHP 8.5+ - 已完成 安裝指南 中的基本設定


Backport 相容性說明

PHP 8.5 語法需求nextpdf/laravel 使用 PHP 8.5 語法特性。

若你的 Laravel 應用程式執行於 PHP 8.1,請改用 nextpdf/backport 套件, 並參閱 PHP 相容性說明 了解 Backport 與 Laravel 的整合方式。


步驟一:安裝套件

composer require nextpdf/laravel

nextpdf/laravel 透過 Laravel 的 Package Auto-Discovery 機制自動註冊 Service Provider, 無需手動修改 config/app.php


步驟二:發佈設定檔(選用)

php artisan vendor:publish --tag=nextpdf-config

這會建立 config/nextpdf.php

<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Default Font Directory
    |--------------------------------------------------------------------------
    | Path to custom font directory. NextPDF will search this directory
    | in addition to its built-in font registry.
    */
    'fonts' => [
        'path' => storage_path('fonts'),
        'cache' => storage_path('framework/cache/nextpdf/fonts'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Spectrum Accelerator
    |--------------------------------------------------------------------------
    */
    'spectrum' => [
        'enabled' => env('SPECTRUM_ENABLED', false),
        'socket'  => env('SPECTRUM_SOCKET', 'tcp://localhost:9000'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Default PDF Metadata
    |--------------------------------------------------------------------------
    */
    'metadata' => [
        'creator' => env('APP_NAME', 'NextPDF'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Storage Disk
    |--------------------------------------------------------------------------
    | Laravel filesystem disk used for PDF output.
    */
    'disk' => env('NEXTPDF_DISK', 'local'),
];

步驟三:使用 Facade

nextpdf/laravel 提供 Pdf Facade,封裝 DocumentFactory,支援跨 Request 的 Font Registry 共享。

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\Http\Response;
use NextPDF\Laravel\Facades\Pdf;

final class InvoiceController extends Controller
{
    public function download(int $invoiceId): Response
    {
        // 建立文件
        $document = Pdf::create()
            ->addPage()
            ->setFont(family: 'NotoSans', size: 12)
            ->text("發票編號:INV-{$invoiceId}", x: 20, y: 30)
            ->text('金額:NT$ 1,000', x: 20, y: 45)
            ->text('日期:' . now()->format('Y-m-d'), x: 20, y: 60);

        // 以 PdfResponse 回傳(自動設定正確的 Content-Type 標頭)
        return $document->toPdfResponse(
            filename: "invoice-{$invoiceId}.pdf",
            disposition: 'inline',  // 或 'attachment' 強制下載
        );
    }
}

步驟四:使用 PdfFactory(依賴注入)

在需要更精細控制或進行單元測試時,建議透過 DI Container 注入 PdfFactory

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\Http\Response;
use NextPDF\Laravel\Contracts\PdfFactory;

final class ReportController extends Controller
{
    public function __construct(
        private readonly PdfFactory $pdfFactory,
    ) {}

    public function generate(): Response
    {
        $document = $this->pdfFactory->create();

        $document->addPage()
            ->setFont(family: 'NotoSans', size: 16)
            ->text('月度報表', x: 20, y: 30);

        // 儲存至 Laravel Storage
        $path = 'pdfs/report-' . now()->format('Ym') . '.pdf';
        $document->saveToStorage(disk: 'local', path: $path);

        return response()->json(['path' => $path]);
    }
}

步驟五:Blade View 整合

使用 Blade 模板作為 PDF 內容來源(透過 Artisan 套件的 Chrome CDP 渲染):

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\Http\Response;
use NextPDF\Laravel\Facades\Pdf;

final class ContractController extends Controller
{
    public function preview(int $contractId): Response
    {
        $data = [
            'contract' => Contract::findOrFail($contractId),
            'company'  => config('app.name'),
        ];

        // 使用 Blade 模板渲染(需安裝 nextpdf/artisan)
        return Pdf::view('pdfs.contract', $data)
            ->toPdfResponse(filename: "contract-{$contractId}.pdf");
    }
}

對應的 Blade 模板 resources/views/pdfs/contract.blade.php

<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <style>
        body { font-family: 'Noto Sans TC', sans-serif; margin: 40px; }
        h1   { color: #1E3A8A; }
    </style>
</head>
<body>
    <h1>合約編號:{{ $contract->id }}</h1>
    <p>甲方:{{ $company }}</p>
    <p>乙方:{{ $contract->client_name }}</p>
    <!-- PLACEHOLDER:CONTENT:contract-template-body — Full contract template content -->
</body>
</html>

注意:Blade 渲染模式需安裝 nextpdf/artisan 並設定 Chrome 執行環境。


步驟六:Queue Job 非同步生成

對於大型文件或批次生成,建議使用 Laravel Queue:

<?php

declare(strict_types=1);

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use NextPDF\Laravel\Contracts\PdfFactory;

final class GenerateMonthlyReportJob implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    /**
     * Maximum number of retry attempts.
     */
    public int $tries = 3;

    /**
     * Timeout in seconds.
     */
    public int $timeout = 120;

    public function __construct(
        private readonly int $userId,
        private readonly string $reportMonth, // Format: 'YYYY-MM'
    ) {}

    public function handle(PdfFactory $factory): void
    {
        $document = $factory->create();

        $document->addPage()
            ->setFont(family: 'NotoSans', size: 20)
            ->text("月度報表 {$this->reportMonth}", x: 20, y: 30);

        // <!-- PLACEHOLDER:CONTENT:report-content — Report page building logic -->

        $path = "reports/user-{$this->userId}/{$this->reportMonth}.pdf";
        $document->saveToStorage(disk: 's3', path: $path);

        // 通知使用者(依需求實作)
        // ReportGeneratedNotification::dispatch($this->userId, $path);
    }

    public function failed(\Throwable $exception): void
    {
        // 記錄失敗原因
        logger()->error('PDF 生成失敗', [
            'user_id'      => $this->userId,
            'report_month' => $this->reportMonth,
            'error'        => $exception->getMessage(),
        ]);
    }
}

分派 Job:

// 立即分派至預設 Queue
GenerateMonthlyReportJob::dispatch(
    userId: auth()->id(),
    reportMonth: now()->format('Y-m'),
);

// 延遲分派(3 秒後執行)
GenerateMonthlyReportJob::dispatch(userId: 1, reportMonth: '2026-03')
    ->delay(now()->addSeconds(3));

// 指定 Queue 名稱(高優先度)
GenerateMonthlyReportJob::dispatch(userId: 1, reportMonth: '2026-03')
    ->onQueue('pdf-high');

路由設定範例

// routes/web.php
use App\Http\Controllers\InvoiceController;
use App\Http\Controllers\ReportController;

Route::middleware(['auth'])->group(function () {
    Route::get('/invoices/{invoice}/pdf', [InvoiceController::class, 'download'])
        ->name('invoices.pdf');

    Route::post('/reports/generate', [ReportController::class, 'generate'])
        ->name('reports.generate');
});

測試

nextpdf/laravel 提供 Fake 實作,方便在不實際生成 PDF 的情況下測試業務邏輯:

<?php

declare(strict_types=1);

namespace Tests\Feature;

use NextPDF\Laravel\Testing\PdfFake;
use NextPDF\Laravel\Facades\Pdf;
use Tests\TestCase;

final class InvoiceControllerTest extends TestCase
{
    public function test_invoice_pdf_download_returns_pdf_response(): void
    {
        Pdf::fake();

        $response = $this->actingAs($this->user())
            ->get(route('invoices.pdf', ['invoice' => 1]));

        $response->assertOk();
        $response->assertHeader('Content-Type', 'application/pdf');

        Pdf::assertCreated();
        Pdf::assertPageCount(1);
    }
}

下一步