NextPDF Symfony 正式環境部署
重點摘要
標題為「重點摘要」的區段此套件設計用於長時間執行的 PHP runtime。 Document 不共用、字型登錄表(font registry)在暖機後鎖定,影像快取則會在每次請求之間重置。 大型 PDF 請採用串流,並將繁重工作卸載到 Messenger worker。
worker 安全的服務生命週期
標題為「worker 安全的服務生命週期」的區段長時間執行的 runtime 會讓 container(容器)跨請求保持存活,因此每次請求的狀態都不可外洩。 FrankenPHP、RoadRunner 與 Messenger worker 的運作方式皆是如此。 此套件的 services.php 定義了以下生命週期,並已依服務定義完成驗證:
- Document — 不共用。
nextpdf.document(以及PdfDocumentInterface/Document別名)每次都會 resolve(解析)為全新的實例。 依 PSR-11,container 每次針對同一個 id 呼叫get()時,都可以合法回傳不同的值(PSR-11 §1.1.2)。 每次請求都會解析出各自的 document。 絕不要跨請求持有同一份 document。 - FontRegistry — 共用且鎖定。 此登錄表是整個 process 生命週期內的單例(singleton)。 在
warmup()之後(當preload_fonts非空時),編譯器階段會呼叫lock()。 這道鎖可避免執行期變動,進而避免跨請求的字型狀態污染。 - ImageRegistry — 共用,每次請求重置。 這個有上限的最近最少使用(LRU)影像快取是共用的,但標記為
kernel.reset並帶有reset方法,因此在遵守kernel.reset的 runtime 下,Symfony 會在請求之間清除它。 - EInvoice 合約 — 不共用。 當存在 Premium 實作時,embedder、validator、profile 與 schematron 服務都會註冊為不共用。 每次呼叫所用的 parser(剖析器)context 絕不會跨請求外洩。
建議的注入模式
標題為「建議的注入模式」的區段請注入 PdfFactory(共用、無狀態的組態持有者),並在每次請求中呼叫 create():
public function __construct(private readonly PdfFactory $pdf) {}
public function action(): Response{ $doc = $this->pdf->create(); // fresh, disposable // ... build ... return PdfResponse::inline($doc, 'document.pdf');}不要將 Document 或 nextpdf.document 注入本身為共用、且會跨請求持有狀態的服務。 請改在請求範圍內的方法中解析它。
串流大型 document
標題為「串流大型 document」的區段PdfResponse::streamDownload() 與 streamInline() 會回傳一個 StreamedResponse。 它的 callback 會以 64 KB 為單位輸出 PDF 主體,並在每個區塊後 flush。 這會將大型 document 的回應緩衝區維持在可控範圍內。 以下兩種取捨都已對照 PdfResponse 驗證:
- 串流版本刻意 省略
Content-Length(回應物件事先並不知道主體大小)。 下載進度列與某些 proxy 偏好可預先得知的長度。 當 document 小到足以保存在記憶體中、且需要 content length 時,請改用非串流的download()或inline()。 - 串流版本會送出與緩衝版本相同的安全標頭,以及相同的
Cache-Control: private, max-age=0, must-revalidate。
對於數 MB 的報表與批次匯出,請選擇串流版本。 對於小型、對延遲敏感的回應,則選擇緩衝版本。
大規模的非同步產生
標題為「大規模的非同步產生」的區段當請求必須快速回應,或繪製過程耗用大量 CPU 時,請將產生工作卸載到 Messenger。
- 為每一種 document 類型實作
PdfBuilderInterface。 - 在
container.service_locator中註冊 builder,並將它連接到GeneratePdfHandler的$builderLocator。 - 把
GeneratePdfMessage路由到耐久的 transport。 - 以有界的生命週期執行 worker。
有界的 worker 生命週期
標題為「有界的 worker 生命週期」的區段回收 worker,避免第三方相依套件外洩的已配置記憶體無限制成長:
php bin/console messenger:consume async \ --limit=200 \ --memory-limit=256M \ --time-limit=3600此套件的 messenger.timeout 與 messenger.retries 組態鍵記錄了預期的每則訊息逾時與重試額度。 請透過 Symfony 的重試策略與 worker 旗標落實對應行為。
worker 中的輸出路徑安全
標題為「worker 中的輸出路徑安全」的區段GeneratePdfMessage 在建構時驗證輸出路徑。 接著 GeneratePdfHandler 會在執行期間、寫入磁碟之前再次驗證它。 這種兩階段檢查對非同步工作很重要。 訊息可能在派發與消費之間停留在佇列裡,因此 handler 不會盲目信任佇列中的路徑。 請將 worker 的檔案系統權限限制在預定的輸出目錄,作為縱深防禦。
可觀測性
標題為「可觀測性」的區段其中的 FontRegistry 與 ImageRegistry 服務接受一個選用的 Psr\Log\LoggerInterface(以 nullOnInvalid() 繫結)。 當應用程式提供 logger 時,這些登錄表可以透過它輸出診斷資訊。 在 PSR-3 logger 合約(PSR-3)之下,logger 是選用、可替換的協作者。 若要取得請求層級的可見度,請在你的應用程式碼中,於 PdfFactory::create() 與 Messenger handler 周圍記錄日誌。 事故分流時請使用 messenger:consume -vv。
部署檢查清單
標題為「部署檢查清單」的區段- 在應用程式的
composer.json中只釘選單一個nextpdf/core主版本 (此套件接受^3.0 || ^5.2)。 - 確保部署的 PHP 映像中已啟用
ext-mbstring與ext-zlib(否則此套件會在啟動時快速失敗)。 - 為你的 document 所用的字型預先填入
preload_fonts,讓登錄表在啟動時暖機並鎖定,而非在首次請求時才進行。 - 若你仰賴跨部署保留的快取產物,請將
cache_path指向可寫入、可持久化的位置。 否則使用%kernel.cache_dir%預設值即可。 - 請在部署期間執行
php bin/console cache:warmup,讓編譯後的 container 本體(含選用擴充功能的探測)在流量到來前先建置完成。 - 正式環境的非同步工作請使用耐久的 Messenger transport(而非
sync),並以--limit/--memory-limit/--time-limit回收 worker。
邊緣情況與陷阱
標題為「邊緣情況與陷阱」的區段- 串流回應位於緩衝型 proxy 之後 —— 若 proxy 會緩衝整個主體,就會抵銷記憶體上的好處。 請設定 proxy 以串流方式處理 PDF 回應,或在該處改用緩衝回應。
kernel.reset未被遵守 —— 在不會呼叫kernel.reset的 runtime 下,影像快取會受image_cache_mb限制,但不會在請求之間清除;請據此設定上限大小。- 跨請求持有 document —— 從先前請求擷取並保留的
Document會攜帶過時狀態。 請一律透過PdfFactory在每次請求時解析。
符合性
標題為「符合性」的區段每一列都是本頁提出的一項規範性主張,並釘選到一個來自受控 SDO 語料庫的完整 64 位十六進位 reference_id。 來源(provenance,即語料庫 manifest 與檢索 transport)記錄於 _sidecars/rag-citations.yaml。
| 規格 | 條款 | 參考 ID(reference_id) | 主張 |
|---|---|---|---|
| PSR-11 | psr_11_container#1.1.2.p3.b | 不共用服務:每次解析得到不同的值 | |
| PSR-3 | psr_3_logger#x3.p17 | 選用的 logger 協作者 |
另請參閱
標題為「另請參閱」的區段- /integrations/symfony/configuration/ —— 服務生命週期與參數。
- /integrations/symfony/security-and-operations/ —— 回應標頭、路徑驗證、金鑰處理。
- /integrations/symfony/troubleshooting/ —— 啟動與執行期診斷。
- /integrations/symfony/quickstart/ —— 最小化的非同步設定。