為什麼你的 PDF 引擎屬於 PHP,而不是一個 sidecar
Spec: ISO/IEC 25010:2023, §3.7ISO/IEC 25010:2023 §3.7Spec: ISO 32000-2, §7ISO 32000-2 §7
一份 PDF 可以在兩個地方產出:在你的 PHP 行程內,或在某個你必須維運的別處。NextPDF 在行程內產出它。本頁就是為那項選擇所做的論證——為什麼一個行程內引擎通常是正確的預設,以及那個「某個別處」的模式一旦進入正式環境後究竟要付出什麼。
這是架構的角度,不是框架的角度。同一個引擎如何觸及 Laravel、Symfony、CodeIgniter 與獨立程式碼,是另一個故事,講在 一個引擎,每一個框架。
為什麼這很重要
標題為「為什麼這很重要」的區段一項 PDF 功能很少是以一個你要維運的系統起步的。它起步於控制器裡的一行:彩現這張發票、回傳那份報表。sidecar 模式把那一行變成基礎設施。為了繪製文件,你現在要執行第二個東西——一個外部執行檔、一個無頭瀏覽器、一個獨立的微服務——而那第二個東西所需要的一切也都變成你的問題:它的版本、它的記憶體、它的容器、它的網路、它的失效模式、它在凌晨兩點的待命呼叫。
那筆成本在展示時隱形,在正式環境中無可迴避。一個活在你行程裡的文件引擎,一項都沒有。問題不是「一個 sidecar 能不能做出一份 PDF」——它當然能。而是「為了走到那一步,你簽下了要維運什麼,而你需要它嗎」。
簡短版本
標題為「簡短版本」的區段- 行程內意味著沒有第二個執行階段。 NextPDF 在處理該請求的同一個 PHP 工作行程內繪製 PDF。沒有要孵生的子行程、沒有要部署的服務,也沒有額外要維持存活的東西。
- 一個 sidecar 增加了一塊你原本沒有的運維表面。 一個隨附的瀏覽器或外部執行檔帶來它自己的版本、它自己的安全足跡與它自己的容器——而這些如今全由你來修補與監控。
- 行程邊界是事情出錯之處。 冷啟動、逾時、脆弱的跨行程管線,以及離開你行程的資料,都是一個行程內呼叫單純就不會有的失效模式。
- 行程內可測試且具確定性。 引擎是你能單元測試、能模擬、能推理的具型別 PHP——而不是一個你只能靠執行它並查看輸出才能探查的不透明彩現器。
- 一個真正的瀏覽器仍有真正的用途。 對於任意現代網頁的像素忠實彩現,一個無頭瀏覽器是誠實的工具——而 NextPDF 能刻意地委派給它。它是一道接縫,不是預設。
NextPDF 的處理方式
標題為「NextPDF 的處理方式」的區段把兩種架構並排來看。行程內路徑是一次函式呼叫。sidecar 路徑是一個微縮的分散式系統——而它的方框之間每一個箭頭,都是一個獨立於你程式碼之外會失效的地方。
- In-process: call the enginewriteHtml() or the document API runs inside the current PHP worker — no subprocess, no socket.
- In-process: receive PDF bytesThe engine returns native PDF content directly; nothing left the process.
- Sidecar: serialize and shipMarkup or a request is marshalled out of your process to a binary, browser, or remote service.
- Sidecar: cross the boundaryA process spawn or network hop — with a cold start, a timeout, and an IPC contract that can break.
- Sidecar: run a second runtimeAn external renderer with its own version, memory profile, and security surface to operate and patch.
- Sidecar: deserialize backMarshal the result back in and translate the renderer’s errors into yours.
沒有第二個執行階段要維運。 sidecar 模式是兩個系統穿著一項功能的戲服。一個隨附的 wkhtmltopdf、一個無頭 Chromium 服務、一個獨立的彩現微服務——每一個都是一個帶有自身發布節奏與自身臭蟲的執行階段。你全部繼承下來。行程內引擎以一個 Composer 相依套件的形式出貨;它的升級方式,與你 composer.json 裡其他每一個函式庫一樣,不會在你的部署中加上任何常駐程式、映像檔或 socket。
版本漂移與更寬的安全表面。 一個隨附的瀏覽器是一個龐大、快速移動的程式碼庫,帶有源源不絕的安全公告。釘住它,它會腐朽;追蹤它,它會翻攪。無論哪一種,它都是一整套彩現器的網頁平台,坐在你的供應鏈裡,只為了餵一份文件。一個行程內的 PHP 引擎是一個你能閱讀的、聚焦的程式碼函式庫;它的安全表面,是你早已執行的那套 PHP,而不是一個你現在還得額外執行的第二個平台。
資料留在你的行程邊界內。 當你外溢時,文件內容——而那往往恰恰就是一份 PDF 之所以存在要承載的敏感資料——會跨越一道邊界。它被寫進一個管線、一個引數、一個暫存檔,或一個通往某服務的網路 socket。其中每一個都是一個會洩漏、會意外被記錄,或會留下殘跡的地方。在行程內,資料永遠不會離開擁有它的那個工作行程。爆炸半徑是一個行程,而不是一整支機隊。
脆弱的管線、冷啟動與逾時。 跨行程與網路呼叫會以函式呼叫不可能的方式失效:沒有啟動的子行程、卡住的 socket、你猜錯的逾時、流量尖峰下的冷啟動。每一項都需要一套重試策略、一個斷路器與一份預算。一次行程內彩現要嘛回傳位元組,要嘛拋出一個你在下一行就能接住的具型別例外。沒有需要去調和的部分網路狀態。
可觀測性與測試跨越邊界後變得更難。 sidecar 裡的一次失效會以一個退出碼、一行被截斷的記錄,或一個來自你無法掌控之服務的 500 抵達。重現它意味著重現那整套環境。一個行程內引擎用你早已使用的工具就能觀測——一段堆疊追蹤、一個除錯器、一個剖析器——而它的可測試方式,就和你其餘的 PHP 一樣。那份可測試性是一項具名的軟體品質特性:ISO/IEC 25010 把它歸在可維護性之下(Spec: ISO/IEC 25010:2023, §3.7ISO/IEC 25010:2023 §3.7),而一個行程內函式庫滿足它的方式,遠比一個你只能靠啟動它才能演練的彩現器更為直接。
那些測試所斷言的 PDF,是一個已定義的結構,不是一個黑盒子。一個 PDF 檔案有一套指定的物件與檔案佈局(Spec: ISO 32000-2, §7ISO 32000-2 §7),而一個行程內引擎從你能閱讀的程式碼輸出那個結構——因此一次黃金檔案或結構性測試所檢查的,是一個已知函式所產生的位元組,而不是一個你只能觀察的外部程式的輸出。
實務範例
標題為「實務範例」的區段整個重點裝得進寥寥幾行。沒有客戶端、沒有基底 URL、沒有健康檢查,也沒有重試策略——因為沒有第二個系統。
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
// The engine runs inside this very process. No subprocess is spawned,// no socket is opened, and the report data never leaves the worker.$document = Document::createStandalone();$document->setTitle('Quarterly Report');$document->addPage();
$html = <<<'HTML'<h1 style="color: #1E3A8A;">Quarterly Report</h1><p>Rendered <strong>in-process</strong> by PHP — no browser, no sidecar.</p>HTML;
$document->writeHtml($html);
// PDF bytes are returned directly. There is no boundary to marshal across,// so there is no timeout, cold start, or deserialization step to handle.$bytes = $document->getPdfData();對照 sidecar 版本的形狀——不是它的程式碼,而是它的運維形狀。它需要一個執行檔或服務被安裝且可觸及、一個請求被序列化並送出、一個被選定的逾時、一條當彩現器是冷的或掛掉時的失效路徑,以及一個被封送回來的結果。上面的片段裡沒有任何一項,因為當引擎是一個函式庫時,這些都不存在。
常見誤解
標題為「常見誤解」的區段常見的假設是:「真正的」PDF 彩現必定意味著一個瀏覽器,所以行程內想必是玩具版本。那把取捨弄反了。當你需要對任意現代網頁內容進行精確、像素忠實的彩現時,瀏覽器才是正確的工具。對大多數團隊實際在做的文件形狀的工作——發票、報表、對帳單、合約——它是錯誤的預設,因為在那些情況下,版面是已知的、資料是你自己的,而正確性是由一個驗證器、而非靠肉眼檢查。對那種工作而言,一個 sidecar 的運維重量買不到任何行程內引擎尚未給你的東西,卻在上述各節中讓你付出一切。
那面鏡像的誤解,正是本頁謹慎不去犯的:聲稱一個行程內引擎能像瀏覽器一樣彩現「整個網頁」。它不能,而 NextPDF 也不假裝它能。它的行程內 HTML 管線是一個與規格對齊、聚焦於文件版面的子集,具備已記載的邊界——誠實的範圍攤陳在 HTML 管線。當你真的需要完整的瀏覽器保真度時,那是一次刻意、需主動啟用的委派,而不是無聲的退路。
限制與邊界
標題為「限制與邊界」的區段行程內是正確的預設。它不是一個「子行程永不正當」的全稱主張。當一份文件確實需要對行程內引擎未涵蓋的任意現代 CSS 進行精確彩現時,委派給一個無頭瀏覽器才是正確的選擇——而 NextPDF 刻意支援那條路徑,並限制其網路存取,作為一道接縫而非預設。兩者不是對手;它們是針對不同工作的不同工具。
本頁論證的是架構,不是一份 CSS 支援對照表。行程內管線確切涵蓋哪些 HTML 與 CSS,是由引擎的程式碼及其符合性測試定義,並隨那條管線一同記載——而不是在此承諾。「行程內」描述的是預設的彩現路徑;它不是一個「每一條可能的路徑都避開子行程」的主張。
能力表面維持簡單:行程內引擎屬於 Core,而瀏覽器委派路徑是一個選用擴充功能,與版本無關。
| Edition | Availability |
|---|---|
| Core | Core renders PDF in-process in PHP — no subprocess, binary, or sidecar by default. |
| Pro | The headless-browser delegation path is an optional add-on extension, independent of edition tier. |
| Enterprise | The headless-browser delegation path is an optional add-on extension, independent of edition tier. |
相關文件
標題為「相關文件」的區段- HTML 管線——行程內引擎誠實的範圍,以及究竟何時委派給瀏覽器才正確。
- 一個引擎,每一個框架——互補的那條軸線:同一個行程內引擎如何觸及每一個 PHP 框架,而不必每個技術堆疊配一個不同的函式庫。
- 在正式環境中營運 NextPDF——營運一個行程內引擎在日常中是什麼樣子,沒有額外的執行階段要維運。
- 記憶體與串流——引擎如何讓行程內產生在負載下維持有界。
詞彙表
標題為「詞彙表」的區段- 行程內產生——在處理該請求的同一個 PHP 工作行程內產出 PDF,不使用子行程、socket 或外部服務。
- Sidecar——一個與你的應用程式並行執行、只做一件事的獨立執行階段;在此指一個在你行程外彩現 PDF 的外部執行檔、無頭瀏覽器或微服務。
- 冷啟動——一個子行程或服務必須從零啟動才能服務第一個請求時所招致的延遲與資源尖峰。
- IPC——跨行程通訊:用以把資料傳入與傳出一個獨立行程的管線、socket、暫存檔或網路呼叫,也是脆弱、難以除錯之失效的一個常見來源。
- 瀏覽器委派接縫——那條選用、需主動啟用的路徑,把一次彩現交給一個無頭瀏覽器以求精確保真度,並封鎖子資源的網路存取;一項刻意的選擇,而非預設。