NextPDF Laravel 套件疑難排解
本頁會將套件中每一種可觀察的故障模式,對應到原始碼裡經查證的根本原因。每個條目都會列出症狀、原因與修正方式。
composer require nextpdf/laravelphp artisan vendor:publish --tag=nextpdf-config概念總覽
標題為「概念總覽」的區段多數回報的問題可歸為五類:探索、容器 resolve(解析)、簽章、佇列任務與 HTTP 檔名。此套件刻意設計成會明確報錯。未設定的選用功能會回傳 null,不安全的輸入則會丟出具型別的例外。因此,症狀通常會直接指向原因。
API 介面 — 症狀對應原因
標題為「API 介面 — 症狀對應原因」的區段探索與啟動
標題為「探索與啟動」的區段| 症狀 | 經查證的原因 | 修正 |
|---|---|---|
| 安裝後 provider 未註冊 | 應用程式透過 extra.laravel.dont-discover 選擇退出自動探索 | 從 dont-discover 中移除此套件,或手動將 NextPdfServiceProvider 註冊於 bootstrap/providers.php |
config('nextpdf') 是空的 | 因為尚未解析任何已宣告的繫結(延遲載入的 provider),組態尚未被合併 | 解析任一個 provides() 項目,或用以下指令確認探索狀態:php artisan package:discover --ansi |
config/nextpdf.php 未被 publish 建立 | publish 標籤不符 | 使用完全相符的標籤:php artisan vendor:publish --tag=nextpdf-config |
| RuntimeException:「NextPDF requires the ext-mbstring/ext-zlib PHP extension」(執行階段擲出例外,表示缺少必要的 PHP 擴充功能) | 執行階段缺少必要的 PHP 擴充功能 | 安裝或啟用 mbstring 與 zlib,並在 php.ini 中設定 |
容器解析
標題為「容器解析」的區段| 症狀 | 經查證的原因 | 修正 |
|---|---|---|
app(SignerInterface::class) 回傳 null | 簽章被停用,或 nextpdf.signature 中的憑證為空 | 設定 signature.enabled = true 並提供有效的 signature.certificate;安裝 nextpdf/premium 以取得簽章器的具體實作 |
app(TsaClient::class) 回傳 null | nextpdf.tsa.url 是空的 | 設定 tsa.url(並視需要設定 credentials/pins) |
| 某個 PDF/A 版本型別找不到類別(Class-not-found) | nextpdf.pdfa 非 null,但未安裝 nextpdf/premium 套件 | 安裝 nextpdf/premium,或將 pdfa 設回 null |
| 解析某個電子發票合約時找不到類別(Class-not-found) | 繫結雖已註冊,但 Premium 的具體實作不存在 | 安裝 nextpdf/premium;電子發票合約會延遲解析,只有在缺少 Premium 且首次解析時才會報錯 |
| 同一份文件在兩個邏輯操作之間被變動 | 文件繫結是一個工廠(factory);你重複使用了同一個已解析的實例 | 為每份文件解析一個全新的 PdfDocumentInterface 實例 |
當容器沒有對應項目時,會在呼叫 get() 時丟出 not-found 例外(PSR-11 §1.1.2)。電子發票合約是已繫結的,所以容器的 has() 會回傳 true。錯誤會在建構時因缺少 Premium 具體實作而浮現,而不是來自容器本身。
佇列任務
標題為「佇列任務」的區段| 症狀 | 經查證的原因 | 修正 |
|---|---|---|
InvalidArgumentException: Path traversal sequences are not allowed | 輸出路徑含有 .. 片段 | 在你的儲存目錄下,使用絕對且不含路徑穿越的路徑 |
InvalidArgumentException: Stream wrappers are not allowed | 路徑符合協定前綴,例如 php:// | 使用單純的檔案系統路徑 |
InvalidArgumentException: Output path contains null bytes | 路徑含有一個 \0 位元組 | 在 dispatch 之前先清理路徑 |
InvalidArgumentException: Output path must end with .pdf extension | 路徑結尾並非 .pdf(不分大小寫) | 使用 .pdf(或 .PDF)作為副檔名 |
| 任務已執行,但檔案為空或內容錯誤 | builder 閉包未回傳已設定好的文件 | 從 builder 回傳該文件;回傳值就是會被儲存的內容 |
| 任務使用了錯誤的佇列或逾時設定 | nextpdf.queue.* 未依預期設定 | 設定 queue.queue、queue.connection、queue.timeout;tries 與 backoff 則需要透過子類別覆寫 |
這些路徑檢查會在 worker 上的 handle() 內執行,所以錯誤路徑會在執行時失敗,而不是在 dispatch 時失敗。這是刻意設計:佇列傳輸中的序列化負載會在被消費的位置進行驗證。
HTTP 回應與檔名
標題為「HTTP 回應與檔名」的區段| 症狀 | 經查證的原因 | 修正 |
|---|---|---|
下載檔名意外變成 document.pdf | 傳入空白檔名;工廠(factory)會套用這個預設值 | 請傳入一個非空白的檔名 |
| 檔名遺失了路徑或特殊字元 | 檔名清理器會去除路徑分隔字元、控制字元與 null 位元組 | 只傳入基底檔名;這是預期內的強化措施 |
| 非 ASCII 檔名在某些用戶端會顯示為亂碼 | 對於非 ASCII 名稱會輸出 RFC 5987 的 filename*=;舊版用戶端則會讀取 ASCII 後備值 | 這是預期行為;若特定舊版用戶端必須完全相符,請提供一個 ASCII 安全的名稱 |
串流回應沒有 Content-Length | 串流回應依設計省略 Content-Length(分塊輸出) | 這是預期行為;若必須有長度標頭,請改用非串流的 inline()/download(),即可取得該標頭 |
程式碼範例 — 診斷
標題為「程式碼範例 — 診斷」的區段# Confirm the provider is discoveredphp artisan package:discover --ansi
# Inspect merged configurationphp artisan tinker --execute="dump(config('nextpdf.queue'));"<?php
declare(strict_types=1);
use NextPDF\Contracts\SignerInterface;
$signer = app(SignerInterface::class);
if ($signer === null) { // Signing not configured, or nextpdf/premium not installed. // Continue without a signature, or fail with a clear message.}邊界情況與陷阱
標題為「邊界情況與陷阱」的區段- 延遲載入的 provider 代表全新安裝在第一次進行相關解析前,可能看起來像是「壞掉了」。正確的成功訊號是
package:discover列出了此套件。 image_cache_mb = null會退回 50 MB;只有0才會停用快取。回報「快取停用無效」的情況通常都是使用了null。signature.level = null會靜默退回為 PAdES B-B。回報「出現非預期的 B-B」的情況通常都是未設定 level。
如果長時間執行的 worker 在前幾個請求特別慢,代表字型登錄表正在隨需剖析。請填入 nextpdf.preload_fonts,讓暖機只在 worker 啟動時執行一次。詳情請見 /integrations/laravel/configuration/ 與 /integrations/laravel/boot-and-discovery/ 兩節。
安全性備註
標題為「安全性備註」的區段系統拒絕特定路徑與檔名是安全控制,並非錯誤。請勿透過預先解碼或放寬檢查來繞過它們。請改為將檔案輸出導向受控的儲存路徑。請見 /integrations/laravel/security-and-operations/。
符合性
標題為「符合性」的區段| 主張 | 來源 | 條款 | 參考 ID |
|---|---|---|---|
| 缺少容器項目時,會在 get() 時丟出 not-found | PSR-11 容器 | §1.1.2 |
另請參閱
標題為「另請參閱」的區段- /integrations/laravel/install/ — 探索與 publish 步驟
- /integrations/laravel/configuration/ — 每個鍵與其預設值
- /integrations/laravel/production-usage/ — DI 與佇列模式
- /integrations/laravel/security-and-operations/ — 這些路徑檢查存在的原因