跳到內容

NextPDF Laravel 套件正式環境用法

在正式環境中,請透過建構式注入 resolve(解析)文件契約。針對 PDF 寫入失敗,請處理特定例外。將繁重或批次產生作業移至 GeneratePdfJob,並明確接上成功與失敗回呼。

Terminal window
composer require nextpdf/laravel
php artisan vendor:publish --tag=nextpdf-config

請在 config/nextpdf.php 中設定佇列連線。設定 queue.connectionqueue.queuequeue.timeout。接著確認已有 worker 正在處理已設定的連線。

容器將 NextPDF\Contracts\PdfDocumentInterface 公開為工廠(factory)繫結。每次解析都會產生一個全新的 NextPDF\Core\Document。PSR-11 允許容器依繫結策略,在連續的 get() 呼叫中回傳不同的值(PSR-11 §1.1.2)。本套件在這裡採用工廠繫結,讓以請求為作用範圍的可變狀態永遠不會跨請求外洩。字型與影像的登錄表則是單例(singleton)。如此既維持「繫結的識別碼會解析到其註冊項目」這項契約(PSR-11 §1.1.2),又能在整個 worker 生命週期中共用這些昂貴資源。

在正式環境程式碼中,請優先採用建構式注入,而非 facade。這讓相依關係更明確,也讓控制器在單元測試時無須啟動 facade 根節點。

以 DI 接線且具型別錯誤處理的控制器

標題為「以 DI 接線且具型別錯誤處理的控制器」的區段
resource: NextPDF\Contracts\PdfDocumentInterface + src/Laravel/Http/PdfResponse.php
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\Response;
use NextPDF\Contracts\PdfDocumentInterface;
use NextPDF\Laravel\Http\PdfResponse;
use Psr\Log\LoggerInterface;
use Throwable;
final class InvoiceController extends Controller
{
public function __construct(
private readonly PdfDocumentInterface $document,
private readonly LoggerInterface $logger,
) {}
public function show(int $invoiceId): Response
{
try {
$this->document->addPage();
$this->document->cell(0, 10, "Invoice #{$invoiceId}", newLine: true);
$this->document->cell(0, 10, 'Thank you for your business.');
return PdfResponse::download(
$this->document,
"invoice-{$invoiceId}.pdf",
);
} catch (Throwable $exception) {
// Rethrow as an HTTP-meaningful failure; never swallow.
$this->logger->error('Invoice PDF generation failed', [
'invoice_id' => $invoiceId,
'exception' => $exception::class,
]);
return new Response('Could not generate the invoice PDF.', 500);
}
}
}

請注入 PdfDocumentInterface,而非具體的 Document,讓繫結在測試中仍可替換。容器每次具現化控制器時,都會回傳一份全新的文件。不要在同一個行程內,使用同一個控制器實例處理兩份不相關的文件。

catch 區塊會記錄例外類別,並回傳已定義的 HTTP 錯誤,而不會洩漏堆疊追蹤。請使用 Psr\Log\LoggerInterface;容器會將它解析為 Framework(框架)的記錄器。PSR-3 將 placeholder(佔位符)的跳脫交由實作者處理,並要求呼叫端不要預先跳脫情境值(PSR-3 §1.2)。請傳入結構化的情境資料,而非內插過的字串。

包含成功與失敗回呼的佇列產生流程

標題為「包含成功與失敗回呼的佇列產生流程」的區段

GeneratePdfJob 是一個 ShouldQueue 工作。它預設為三次嘗試、120 秒逾時,以及 10 秒退避。你可以透過 config/nextpdf.php 覆寫這三者。建構器(builder)閉包會收到由容器解析的文件,並且必須回傳一份設定完成的文件。

resource: src/Laravel/Jobs/GeneratePdfJob.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
{
// Dispatchable::dispatch() is `public static`: it constructs the
// job from the arguments it receives and returns a PendingDispatch.
// Pass every constructor argument — including the callbacks — to
// the static call. Building an instance and then calling
// `$job->dispatch(...)` would discard that instance (and its
// callbacks) and queue a different job from only the static args.
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,
]);
},
);
}
}

GeneratePdfJob::dispatch() 會把它的引數直接轉送給建構子 (string $outputPath, callable $builder, ?callable $onSuccess, ?callable $onFailure)。因此成功與失敗回呼會接到實際入列的那個工作上。這和 /integrations/laravel/quickstart/. 中 GeneratePdfJob::dispatch($path, $builder) 的位置引數形式一致。成功回呼會收到輸出路徑,失敗回呼則會收到 Throwable。這個工作也提供流暢式的 then()catch() 設定方法,回傳工作本身以便串接。只有在你保留並派送同一個實例時,才使用這些設定方法,例如透過 dispatch() 輔助函式。這個工作也提供 failed() 方法,佇列執行器會在最終失敗時呼叫它。回呼會封裝成可序列化的閉包,因此能在佇列傳輸中保留下來。

屬性預設值設定鍵
tries3非由組態驅動;以子類別變更
timeout120nextpdf.queue.timeout
backoff10非由組態驅動;以子類別變更
佇列名稱pdfnextpdf.queue.queue
連線預設nextpdf.queue.connection

triesbackoff 是從工作實例讀取的公開屬性。隨附的工作並不是透過組態驅動這兩者。若你的重試策略不同,需要覆寫它們,請建立 GeneratePdfJob 的子類別。

  • 建構器閉包必須回傳一個 PdfDocumentInterface。工作儲存的是這個回傳值,而非一開始解析出的實例。工作測試會明確斷言這項契約。
  • 解析 SignerInterface 會回傳 null,除非已啟用簽章、已設定憑證,且已安裝 nextpdf/premium。簽章前務必先做 null 檢查。
  • 長生命週期的 worker(Octane/RoadRunner/Swoole)會共用已鎖定的字型登錄表。請設定 preload_fonts,讓暖機在 worker 開機時發生一次,而不是落到第一個請求才進行。
  • 工作會在用盡 tries、失敗確定後呼叫 failed()。在佇列執行器宣告最終失敗之前,單次嘗試失敗並不會呼叫 onFailure

在控制器中同步產生 PDF,會在整個建構期間阻塞請求。對於多頁或批次輸出,請派送 GeneratePdfJob 並立即回傳。單例登錄表會把字型剖析與影像解碼分攤到整個 worker 生命週期。如此一來,每個請求的成本就只限於文件建構與內容輸出。

DI 控制器只記錄例外類別,不記錄其訊息或追蹤資訊,以免將內部細節洩漏到日誌中。GeneratePdfJob 會在 worker 端驗證輸出路徑,以降低佇列傳輸中遭竄改的序列化承載所帶來的風險。完整內容請見 /integrations/laravel/security-and-operations/.

主張來源條款參考 ID
繫結的識別碼會解析到其註冊項目PSR-11 Container(容器)§1.1.2
連續解析的結果可能因繫結策略而不同(工廠繫結)PSR-11 Container(容器)§1.1.2

PSR-3 的日誌指引載於 PSR-3 規範。該指引說明,佔位符跳脫由實作者負責,且呼叫端應傳入結構化的情境資料。請見文件 psr_3_logger §1.2。

透過 nextpdf/premium 產出的已簽署 PAdES B-B 輸出與 PDF/A 封存,使用的是同一套 DI 介面。這是一項選用的 Enterprise 能力。這裡所記載的 Core 套件,不需任何程式碼變更即可採用它。請見 https://nextpdf.dev/get-license/?intent=laravel-signing

  • /integrations/laravel/quickstart/ —— 最精簡的第一個範例
  • /integrations/laravel/configuration/ —— 佇列、簽章與字型相關的設定鍵
  • /integrations/laravel/security-and-operations/ —— 威脅模型與強化
  • /integrations/laravel/troubleshooting/ —— 常見的正式環境故障