架構說明¶
本頁從宏觀到微觀,完整說明 NextPDF 的架構設計決策。理解這些設計原則有助於 你正確整合套件、選擇適當的 API 入口,以及除錯複雜的生產問題。
生態系架構:Hub-Spoke 模型¶
NextPDF 採用 Hub-Spoke(輪轂輻條) 架構。nextpdf/core 是唯一的 Hub, 所有 11 個延伸套件均以 Spoke 方式依賴 Core,彼此之間不直接依賴(Enterprise 依賴 Pro 除外)。
graph TD
CORE["nextpdf/core<br/><small>Hub — LGPL-3.0</small>"]
subgraph Commercial["商業延伸"]
PRO["nextpdf/pro"]
ENT["nextpdf/enterprise"]
end
subgraph Renderers["渲染橋接"]
ART["nextpdf/artisan"]
GOT["nextpdf/gotenberg"]
CLO["nextpdf/cloudflare"]
end
subgraph Frameworks["框架整合"]
LAR["nextpdf/laravel"]
SYM["nextpdf/symfony"]
COI["nextpdf/codeigniter"]
end
subgraph Special["特殊整合"]
MCP["nextpdf/mcp-server"]
COM["nextpdf/tcpdf-compat"]
BCK["nextpdf/backport"]
end
CORE --> PRO
PRO --> ENT
CORE --> ART
CORE --> GOT
CORE --> CLO
CORE --> LAR
CORE --> SYM
CORE --> COI
CORE --> MCP
CORE --> COM
BCK -.->|"AST 降版替代"| CORE Hub-Spoke 的設計原則¶
- 單一真相來源:所有 PDF 生成邏輯集中於 Core,延伸套件僅負責框架整合或協議橋接
- 可替換性:延伸套件可以獨立升級或替換,不影響其他套件
- 最小公開介面:Core 僅暴露必要的
Contracts介面,延伸套件依賴介面而非實作 - 單向依賴:依賴流向永遠是 Extension → Core,禁止反向依賴
Core 模組結構(16 個模組)¶
nextpdf/core 的 src/ 目錄按領域分為 16 個模組,每個模組對應 PDF 規範的一個 或多個子領域:
src/
├── Accessibility/ # Tagged PDF, WCAG 2.1 AA, 結構樹
├── Accelerator/ # Spectrum FFI 橋接, SpectrumClient, 電路斷路器
├── Barcode/ # 32+ 條碼符號學, QR Code, PDF417, DataMatrix
├── Content/ # 頁面內容流, 運算子序列, 字符串
├── Contracts/ # 所有公開介面 (RendererInterface, SpectrumInterface...)
├── Core/ # Document, DocumentFactory, ProcessConfig
├── Form/ # AcroForm, 表單欄位, 簽章欄位
├── Graphics/ # 向量圖形, 路徑, 顏色空間, 圖像嵌入
├── Html/ # HTML → PDF AST 轉換器
├── Layout/ # 自動分頁, 表格, 流式排版
├── Navigation/ # 書籤, 目錄, 連結, 跳轉動作
├── Security/ # AES-256 加密, 權限控制, 密碼保護
├── Support/ # 日誌, 例外, 輔助類型, 服務容器
├── Typography/ # 字型載入, HarfBuzz 字形整形, 子集化, CJK
├── ValueObjects/ # PageSize, Color, FontSize, Margin (不可變)
└── Writer/ # PDF 串流產生器, xref 表, 線性化
跨模組依賴規則¶
graph LR
Writer --> Content
Writer --> ValueObjects
Content --> Typography
Content --> Graphics
Content --> Barcode
Layout --> Content
Layout --> ValueObjects
Form --> Content
Form --> Security
Accessibility --> Content
Html --> Layout
Html --> Typography
Accelerator --> Writer
Core --> Accelerator
Core --> Layout
Core --> Form
Core --> Navigation
Core --> Security
Core --> Accessibility
Contracts -.->|"implements"| Core
Support -.->|"used by all"| Core 禁止的依賴方向: - ValueObjects 不可依賴任何其他模組 - Contracts 不可依賴實作模組 - Writer 不可依賴 Layout(避免循環)
三層生命週期:Process → Factory → Document¶
這是 NextPDF 最重要的架構概念。理解三層生命週期是正確使用 API 的基礎。
sequenceDiagram
participant App as 應用程式
participant PC as ProcessConfig
participant FR as FontRegistry
participant IR as ImageRegistry
participant DF as DocumentFactory
participant Doc as Document
participant RC as RenderingContext
Note over PC,IR: 第一層:Process(進程生命週期)
App->>PC: new ProcessConfig(fontPath, spectrumSocket)
App->>FR: FontRegistry::create(config)
App->>IR: ImageRegistry::create(config)
Note over DF: 第二層:Factory(可共享,跨 Request)
App->>DF: new DocumentFactory(fontRegistry, imageRegistry)
Note over Doc,RC: 第三層:Document(單次請求,可丟棄)
App->>DF: factory->create()
DF-->>Doc: new Document(renderingContext)
Doc->>RC: new RenderingContext()
App->>Doc: addPage() / text() / image() / ...
App->>Doc: save() / output()
Doc-->>App: PDF 二進位資料
Note over Doc,RC: Document 完成後立即銷毀,RenderingContext 清除
Note over FR,IR: FontRegistry + ImageRegistry 持續存活,供下一個 Document 重用 第一層:Process(進程層)¶
生命週期:與 PHP 進程相同(FPM Worker 存活期間)
職責: - 初始化 FontRegistry(載入、解析、索引字型檔案) - 初始化 ImageRegistry(圖像快取管理) - 建立 ProcessConfig(設定字型路徑、Spectrum 連線、快取目錄)
關鍵特性:字型載入是昂貴操作(I/O + HarfBuzz 解析),因此 FontRegistry 在進程層初始化後,在整個進程生命週期中共享,避免重複載入。
第二層:Factory(工廠層)¶
生命週期:與 DI Container 相同(框架整合時,通常為單例)
職責: - 持有 FontRegistry 與 ImageRegistry 的引用 - 為每個 PDF 生成請求建立新的 Document 實例 - 在 Framework 環境中充當 DocumentFactory 的長駐服務
<?php
declare(strict_types=1);
use NextPDF\Core\Core\DocumentFactory;
use NextPDF\Core\Core\ProcessConfig;
// 第一層:進程啟動時初始化一次
$config = new ProcessConfig(fontPath: '/usr/share/nextpdf/fonts');
$factory = DocumentFactory::create(config: $config);
// 第二層:Factory 在整個進程中共享
// 框架整合(Laravel/Symfony/CodeIgniter)會自動處理此層
// 第三層:每個 PDF 請求建立新 Document
$doc1 = $factory->create(); // Document #1
$doc2 = $factory->create(); // Document #2(Font Registry 已在記憶體中,速度快)
第三層:Document(文件層)¶
生命週期:單次 PDF 生成(請求範疇或 Job 範疇)
職責: - 持有 RenderingContext(所有每文件的可變狀態) - 提供 PDF 生成的公開 API(addPage()、text()、image()、save() 等) - 完成後輸出 PDF 位元組流並銷毀
Document::createStandalone() 的用途:
// createStandalone() 是三層的快捷方式:
// 在單一呼叫中完成 Process + Factory + Document 初始化
// 適合 CLI 腳本、測試、Worker 環境(無框架 DI)
$doc = Document::createStandalone();
RenderingContext:每文件狀態容器¶
RenderingContext 集中管理所有每文件的可變狀態,以 public private(set) 屬性 暴露給外部讀取,但只有 Document 類別可以修改:
<?php
declare(strict_types=1);
namespace NextPDF\Core;
final class RenderingContext
{
/** @var list<PageContext> */
public private(set) array $pages = [];
public private(set) int $currentPage = 0;
public private(set) float $cursorX = 0.0;
public private(set) float $cursorY = 0.0;
public private(set) string $currentFont = 'NotoSans';
public private(set) float $fontSize = 12.0;
public private(set) bool $isFinalized = false;
// ... 其他狀態欄位
}
Backport 相容性:PHP 8.1 / 7.4 環境中,
public private(set)被降版為private屬性 +publicgetter 方法,行為語義完全相同。
Spectrum 加速器架構¶
Spectrum 是 NextPDF 的 Rust 原生效能加速引擎,以獨立 Sidecar 程序方式執行。
Mode A:Sidecar TCP 模式(生產環境推薦)¶
graph LR
subgraph PHP["PHP Process (FPM)"]
DOC["Document"]
SC["SpectrumClient"]
CB["Circuit Breaker<br/>threshold=3"]
FB["PHP Fallback"]
end
subgraph Sidecar["Spectrum Sidecar (Rust)"]
API["HTTP/2 API"]
SS["Font Subsetter"]
IC["Image Compressor"]
BG["Batch Generator"]
end
DOC -->|"需要加速"| SC
SC --> CB
CB -->|"健康"| API
CB -->|"連續 3 次失敗"| FB
API --> SS
API --> IC
API --> BG
SS -->|"子集化字型"| API
API -->|"結果"| SC
SC --> DOC 連線設定:
// 環境變數
SPECTRUM_ENABLED=true
SPECTRUM_SOCKET=tcp://spectrum:9000 // Docker 網路
// 或
SPECTRUM_SOCKET=unix:///var/run/nextpdf/spectrum.sock // Unix Socket(延遲更低)
Mode B:直接 FFI 模式(單機最低延遲)¶
graph LR
subgraph PHP["PHP Process"]
DOC["Document"]
FFI["PHP FFI Bridge"]
end
subgraph Rust["Rust (同進程)"]
LIB["libspectrum.so"]
SS["Font Subsetter"]
IC["Image Compressor"]
end
DOC --> FFI
FFI -->|"FFI 呼叫"| LIB
LIB --> SS
LIB --> IC
SS --> LIB
LIB -->|"結果"| FFI
FFI --> DOC FFI 模式需求: - PHP ext-ffi 擴充套件 - libspectrum.so(或 .dll)預先編譯並放置於系統路徑 - ffi.enable=true 在 php.ini 中設定
電路斷路器¶
SpectrumClient 內建電路斷路器,確保 Spectrum 不可用時不影響 PDF 生成:
正常狀態: PHP → Spectrum → 結果 (加速)
失敗計數: 連續失敗 < 3 次,繼續嘗試連線
斷路狀態: 連續失敗 ≥ 3 次,自動切換至 PHP 純實作 (降級)
恢復: 每 30 秒嘗試一次探測請求,成功後恢復
文字預處理管線(Text Preprocessing Pipeline)¶
NextPDF 在文字進入 PDF 串流之前,提供一個安全的攔截點,用於實作 PII 遮罩、 翻譯、文字替換等功能:
flowchart LR
APP["應用程式<br/>呼叫 text()"]
PRE["TextPreprocessor<br/>(攔截點)"]
LAY["排版引擎<br/>(Layout)"]
FNT["字型子集化<br/>(Typography)"]
STR["PDF 內容串流<br/>(Writer)"]
APP --> PRE
PRE -->|"安全文字"| LAY
LAY --> FNT
FNT --> STR
note1["⚠ 敏感文字永遠不進入<br/>PDF 串流、字型子集或<br/>ToUnicode CMap"]
PRE -.-> note1 <?php
declare(strict_types=1);
use NextPDF\Core\Contracts\TextPreprocessorInterface;
// 實作自訂文字預處理器(例如 PII 遮罩)
final class PiiMaskingPreprocessor implements TextPreprocessorInterface
{
public function process(string $text, array $context): string
{
// 遮罩身份證號碼
return preg_replace('/[A-Z]\d{9}/', '***-******', $text);
}
}
// 注入預處理器
$doc = Document::createStandalone();
$doc->setTextPreprocessor(new PiiMaskingPreprocessor());
跨套件介面契約¶
所有延伸套件與 Core 的互動均透過 NextPDF\Core\Contracts\ 命名空間下的介面:
| 介面 | 用途 |
|---|---|
RendererInterface | 渲染橋接套件(Artisan、Gotenberg、Cloudflare)的統一入口 |
SpectrumInterface | Spectrum 加速器的抽象(Mode A / Mode B 共用) |
PdfFactoryInterface | 框架整合套件的工廠抽象 |
TextPreprocessorInterface | 文字安全管線的攔截點 |
SignerInterface | Pro 數位簽章的抽象層 |
StorageAdapterInterface | PDF 輸出儲存的抽象(本地、S3、GCS 等) |
// 延伸套件依賴介面,而非實作
use NextPDF\Core\Contracts\PdfFactoryInterface;
final class NextPdfLaravelServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(
PdfFactoryInterface::class,
fn ($app) => new LaravelPdfFactory(
config: $app->make(ProcessConfig::class),
logger: $app->make(LoggerInterface::class),
),
);
}
}
Clean Architecture 分層¶
NextPDF Core 遵循 Clean Architecture 原則,從內到外分為四層:
┌─────────────────────────────────────────┐
│ Adapters / Framework │ ← 框架整合套件 (Laravel/Symfony/CI)
├─────────────────────────────────────────┤
│ Application │ ← Document, DocumentFactory
│ (Use Cases) │ orchestration logic
├─────────────────────────────────────────┤
│ Domain │ ← PDF 規範知識
│ (Business Logic) │ ValueObjects, Typography, Writer
├─────────────────────────────────────────┤
│ Infrastructure │ ← Spectrum FFI, FileSystem, Fonts
│ (External Services) │
└─────────────────────────────────────────┘
依賴方向:由外向內 →
核心規則: - Domain 層不依賴任何外部服務 - Application 層不依賴 Framework 層 - Infrastructure 細節透過 Contracts 介面注入至內層 - 沒有 God Class,沒有 Service Locator
下一步¶
- 快速開始(5 分鐘) — 立即動手實作
- 安裝指南 — 環境設定與認證
- PHP 相容性說明 — PHP 版本策略
- Core API 參考 — 完整 API 文件
- 加速器引擎文件 — Spectrum / Prisma 進階設定