Laravel 開發者指南
Laravel 套件讓 NextPDF 順應 Laravel 慣例,而不改動核心文件生命週期。容器(container)擁有共用的登錄表(registry)與工廠(factory)。每份 PDF 文件都屬於用完即拋,只應被建構、回傳、串流或儲存一次。
當你設計以 nextpdf/laravel 為核心的應用程式服務、佇列工作(queue job)、回應流程或測試覆蓋率時,請使用本指南。
架構邊界
標題為「架構邊界」的區段| 層級 | 擁有者 | 職責 | 不要放在這裡 |
|---|---|---|---|
| 控制器(controller) | 應用程式 | 對請求授權、選擇文件建構器(builder),並回傳回應。 | 跨使用情境共用的 PDF 版面規則。 |
| 應用程式服務 | 應用程式 | 收集領域資料,並呼叫用來建構文件的程式碼。 | 容器啟動邏輯或套件組態。 |
| 文件建構器(builder) | 應用程式 | 將領域資料轉換成 NextPDF 文件呼叫。 | 請求物件、Eloquent 查詢邏輯,或佇列傳輸(transport)細節。 |
| Laravel 整合 | nextpdf/laravel | 綁定工廠、登錄表、簽章器(signer)、TSA 用戶端、facade、回應與佇列工作。 | 業務特定的儲存路徑或租戶政策。 |
| 核心引擎 | nextpdf/nextpdf | 建構並序列化 PDF。 | Laravel 回應、佇列或檔案系統政策。 |
執行期生命週期
標題為「執行期生命週期」的區段| 階段 | 行為 | 開發者動作 |
|---|---|---|
| 服務提供者註冊 | NextPdfServiceProvider::register() 會註冊共用登錄表、文件工廠、文件綁定、HTTP 用戶端、TSA 用戶端、簽章器,以及選用的電子發票契約。 | 在進入正式環境前,請先發佈並檢視 config/nextpdf.php。 |
| resolve(解析)文件 | Pdf facade 與 PdfDocumentInterface 綁定會透過 DocumentFactoryInterface 解析出一份全新文件。 | 每個請求、命令或佇列工作只解析一次文件。 |
| 撰寫 | 應用程式程式碼會透過 facade 或注入的文件,呼叫核心文件 API。 | 請將領域資料擷取留在文件建構器之外。 |
| 終端輸出 | PdfResponse 會輸出 HTTP 內容,或將文件儲存到磁碟。 | 每份文件只選擇一條終端輸出路徑。 |
| 佇列執行 | GeneratePdfJob 會在 worker 內重新建構文件,並再次驗證輸出路徑。 | 傳入純量情境資料,並讓回呼(callback)保持冪等。 |
建議的應用程式結構
標題為「建議的應用程式結構」的區段| 路徑 | 用途 |
|---|---|
app/Pdf/Builders/* | 純文件建構器。它們接收資料並回傳一份已完成的文件。 |
app/Pdf/Data/* | 承載已授權文件輸入的小型 DTO。 |
app/Services/* | 應用程式編排、查詢、授權交接,以及儲存路徑選擇。 |
app/Jobs/* | 當應用程式需要具名工作時,用來包裝 GeneratePdfJob 的選用包裝器。 |
tests/Feature/Pdf/* | HTTP 回應、佇列派送與授權測試。 |
tests/Unit/Pdf/* | 以小型、具決定性輸入進行的建構器測試。 |
讓建構器與 Laravel 請求物件保持獨立。建構器應該能以相同輸入,由控制器、命令、測試與佇列 worker 呼叫。
<?php
namespace App\Pdf\Builders;
use App\Pdf\Data\InvoicePdfData;use NextPDF\Contracts\PdfDocumentInterface;
final readonly class InvoicePdfBuilder{ public function build(PdfDocumentInterface $pdf, InvoicePdfData $data): PdfDocumentInterface { $pdf->setTitle($data->title) ->addPage() ->setFont('dejavusans', '', 12) ->writeHtml($data->html);
return $pdf; }}同步回應模式
標題為「同步回應模式」的區段當 PDF 流程屬於應用程式邏輯的一部分時,請使用建構函式注入。只有在短小的控制器流程中,且靜態寫法能提升可讀性時,才使用 facade。
<?php
namespace App\Http\Controllers;
use App\Pdf\Builders\InvoicePdfBuilder;use App\Pdf\Data\InvoicePdfData;use NextPDF\Contracts\PdfDocumentInterface;use NextPDF\Laravel\Http\PdfResponse;
final readonly class DownloadInvoiceController{ public function __invoke( PdfDocumentInterface $pdf, InvoicePdfBuilder $builder, ) { $document = $builder->build( $pdf, InvoicePdfData::fromInvoiceId(1234), );
return PdfResponse::download($document, 'invoice-1234.pdf'); }}回應輔助程式會在建構 Laravel 回應之前,先將文件位元組具現化。它們是回應輔助程式,不是背景 renderer(渲染器)。
佇列模式
標題為「佇列模式」的區段GeneratePdfJob 接受一個建構器可呼叫項目與輸出路徑。這個工作會在執行期驗證不安全的路徑。應用程式程式碼仍應在派送前,先選定一個租戶安全的儲存根目錄。
<?php
use App\Pdf\Builders\QueuedInvoiceBuilder;use NextPDF\Laravel\Jobs\GeneratePdfJob;
GeneratePdfJob::dispatch( outputPath: storage_path('app/pdfs/invoice-1234.pdf'), builder: [QueuedInvoiceBuilder::class, 'build'],)->onQueue(config('nextpdf.queue.queue', 'pdf'));佇列回呼應該保持精簡。比起把複雜的閉包(closure)存進佇列酬載,建議改由應用程式的工作監聽器寫入持久狀態。
擴充點
標題為「擴充點」的區段| 擴充點 | 用途 | 限制 |
|---|---|---|
PdfDocumentInterface 綁定 | 替換或裝飾文件建立流程,以套用整個應用程式的預設值。 | 必須回傳一個全新的文件實例。 |
DocumentFactoryInterface | 在服務與測試中明確建立全新文件。 | 不要快取回傳的文件。 |
config/nextpdf.php | 預設值、佇列設定、Chrome renderer(渲染器)設定、簽署掛鉤、TSA、OCSP 快取。 | 請把環境變數當成部署組態,而不是請求輸入。 |
GeneratePdfJob 建構器 | 以非同步方式建構文件。 | 可呼叫項目必須能由 Laravel 的佇列傳輸序列化。 |
| 成功/失敗回呼 | 產生後的通知或清理。 | 讓回呼保持冪等並留意副作用。 |
| 選用的 Premium 契約 | 電子發票嵌入器、驗證器、設定檔與 Schematron 執行器。 | 只在選用套件已安裝且已授權的地方解析。 |
開發工作流程
標題為「開發工作流程」的區段- 先在控制器或功能測試中同步建構第一份文件。
- 把版面程式碼搬進
app/Pdf/Builders底下的建構器類別。 - 把查詢與授權邏輯搬進應用程式服務。
- 為
PdfResponse加入標頭與檔名的測試。 - 把緩慢或大量的產生作業移到
GeneratePdfJob。 - 為序列化情境、輸出路徑政策與失敗處理加上佇列測試。
- 使用具代表性的正式環境資料測量記憶體與 render 時間。
失敗處理
標題為「失敗處理」的區段| 失敗 | 應該在哪裡處理 | 建議的回應 |
|---|---|---|
| 無效的請求或未授權的文件 | 控制器或政策。 | 回傳應用程式慣用的授權或驗證回應。 |
| 缺少字型或無效影像 | 建構器測試與應用程式記錄。 | 讓請求或工作失敗;不要輸出不完整的 PDF。 |
| 不安全的輸出路徑 | 應用程式儲存服務與 GeneratePdfJob。 | 在派送前就拒絕,並以 worker 端驗證作為縱深防禦。 |
| 簽署或 TSA 失敗 | 簽署服務邊界。 | 決定文件是否允許未簽署;對受監管的文件,預設採用 fail closed(失敗即拒)。 |
| 佇列逾時 | 佇列 worker 組態與可觀測性。 | 只有在建構器具決定性、且輸出路徑可安全覆寫時,才重試。 |
安全的預設值
標題為「安全的預設值」的區段| 關注點 | 預設值 | 何時覆寫 |
|---|---|---|
| 佇列名稱 | pdf | 當產生作業會與面向使用者的工作競爭資源時,請使用專用佇列。 |
| 工作逾時 | 120 秒 | 只有在測量過文件大小與 worker 容量後,才調高。 |
| 回應檔名 | document.pdf | 使用經過淨化的商業識別碼。 |
| 字型登錄表 | 共用,並在暖機後鎖定。 | 為熱路徑上使用的字型加入 preload_fonts。 |
| 影像登錄表 | 共用的有界快取。 | 在記憶體受限的 worker 上調低 image_cache_mb。 |
| 串流回應分塊 | 64 KB 的區塊。 | 不要依賴區塊邊界;那只是輸出細節。 |
測試檢查清單
標題為「測試檢查清單」的區段- 控制器測試要斷言
Content-Type、Content-Disposition與防禦性標頭。 - 建構器測試使用具決定性的 DTO,且不查詢資料庫。
- 佇列測試要斷言建構器收到的是一份全新文件。
- 路徑測試涵蓋路徑遍歷、stream-wrapper、null 位元組,以及非
.pdf的拒絕。 - worker 測試要在與正式環境相同的記憶體上限下,render 具代表性的文件。
- 選用的簽署測試要涵蓋缺少憑證、密碼錯誤、TSA 無法使用,以及已設定的簽章層級。