跳到內容

將 TCPDF 6.x 程式碼遷移至 NextPDF

nextpdf/compat-legacy 套件建構在 NextPDF 核心引擎之上,透過 NextPDF\Compat\Tcpdf\TCPDF 配接器,重現 TCPDF 6.x 的公開方法名稱、參數順序與預設值。請依這個順序遷移:先以最小改動接上引擎,確認哪些功能已經可用,再開啟嚴格模式列出哪些功能不可用,逐一修正各個呼叫點,最後讓程式改用現代 API、不再依賴配接器。配接器是這趟遷移的鷹架,不是終點。

先談前置條件:

  • 已安裝 NextPDF 核心與 nextpdf/compat-legacy
  • 你有一份附帶測試套件的現有 TCPDF 6.x 程式碼。這套測試是下面每個階段的安全網。

這是一份操作指南。若想了解單一 TCPDF 呼叫在各方法上的行為,請看方法覆蓋頁;若想了解逐檔的完整策略與程式碼,請看上游遷移頁。兩者都連結在「另請參閱」一節中。

將配接器與核心一起安裝。先別移除真正的 TCPDF 函式庫——兩者並存,讓你能在遷移期間比對輸出。

Terminal window
composer require nextpdf/compat-legacy

在修改任何程式碼之前,先確認引擎相依性可以 resolve(解析)(nextpdf/core ^3.0),而且測試套件仍能執行。

配接器是一層 shim(相容層),不是 TCPDF 的分支,也不是逐位元組相同的複製品。在約 120 個調查過的 TCPDF 6.x 公開方法中,約有 94 個直接對映到某個 NextPDF\Core\Document 操作,並針對文件化的參數呈現相容行為。另有一小部分方法,或是接受了引擎並不採用的舊參數(靜默忽略),或是完全不產生任何輸出(未實作或不適用)。權威且經測試驗證的覆蓋矩陣,位於套件儲存庫的 docs/TCPDF_COVERAGE.md。當本指南與該矩陣不一致時,以矩陣為準。

有兩個事實決定了整趟遷移:

  • 輸出位元組不同。 引擎是一套獨立的 PDF 2.0 實作,因此即使可見結果看起來相同,產生的位元組仍與 TCPDF 輸出不同。凡是斷言精確 PDF 位元組的測試,都需要改以繪製內容或結構性屬性為基準。
  • 嚴格模式是你的稽核工具。 嚴格模式關閉時(預設),無法重現 TCPDF 行為的方法會靜默降級。嚴格模式開啟時,這些呼叫會擲出 TcpdfNotImplementedException,明確指出被忽略的參數以及一則遷移提示。請在專屬的稽核回合中執行嚴格模式,切勿在正式環境中使用。

配接器也透過 getDocument() 公開被包裝的引擎文件,它會回傳 NextPDF\Core\Document。這就是退場路徑:逐一將各個呼叫點遷移到現代 API,直到你能移除配接器為止。

關注項目介面
建構new NextPDF\Compat\Tcpdf\TCPDF('P', 'mm', 'A4')
選用的全域別名NextPDF\Compat\Tcpdf\LegacyBootstrap::enableAliases()
啟用稽核TCPDF::setStrictMode(true)
稽核例外NextPDF\Compat\Tcpdf\Exception\TcpdfNotImplementedException
通往現代 API 的逃生口TCPDF::getDocument(): NextPDF\Core\Document
輸出TCPDF::Output(string $name, string $dest)S, F, E, I, D

LegacyBootstrap::enableAliases() 具有冪等性。它只會在 \TCPDF\TCPDF_STATIC\TCPDF_FONTS\TCPDF_COLORS\TCPDF_IMAGES 這些類別尚不存在時註冊它們。完整的逐方法覆蓋與輸出目的地行為,都在「另請參閱」連結的方法覆蓋頁與快速入門頁上。

換掉 import、保留 TCPDF 風格的呼叫,就能產生一份 PDF。

quickstart-first.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF('P', 'mm', 'A4');
$pdf->SetCreator('Quickstart');
$pdf->SetTitle('First Document');
$pdf->SetFont('helvetica', '', 12);
$pdf->AddPage();
$pdf->Cell(0, 10, 'Hello from the NextPDF engine', 1, 1, 'C');
$pdf->Output(__DIR__ . '/quickstart.pdf', 'F');

Output($name, 'F') 會寫入檔案並回傳空字串。與舊版 TCPDF 不同,配接器的 Output() 不會把內容寫進當前的輸出緩衝區,因此你可以安心地在佇列 worker 或自行掌控回應的 HTTP 處理器中呼叫它。

如果無法改動那些以全域命名空間建構 new \TCPDF(...) 的呼叫點,請在啟動時啟用一次這組選用別名。

quickstart-alias.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\LegacyBootstrap;
LegacyBootstrap::enableAliases();
// Legacy code now resolves \TCPDF to the adapter:
$pdf = new \TCPDF('P', 'mm', 'A4');
$pdf->AddPage();
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 10, 'Legacy call site, modern engine');
$pdf->Output(__DIR__ . '/aliased.pdf', 'F');

當真正的 TCPDF 函式庫仍可被自動載入時,請勿啟用別名。當 \TCPDF 類別已存在時,別名會被略過,因此你可能在毫無察覺下仍在使用舊版 TCPDF。遷移期間,請優先採用逐檔的 import。

安全遷移的關鍵步驟是嚴格模式稽核。在嚴格模式開啟時,執行一條具代表性的正式環境路徑或整個測試套件,並蒐集每一個 TcpdfNotImplementedException。每一個例外都是一個工作項目:它會指出方法、被忽略的參數以及一則提示。

migration-audit.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\Exception\TcpdfNotImplementedException;
use NextPDF\Compat\Tcpdf\TCPDF;
function renderInvoice(TCPDF $pdf): void
{
// ... your existing rendering code, unchanged ...
}
$pdf = new TCPDF('P', 'mm', 'A4');
$pdf->setStrictMode(true);
try {
renderInvoice($pdf);
$pdf->Output(__DIR__ . '/audit.pdf', 'F');
} catch (TcpdfNotImplementedException $exception) {
// Each message names the method, the ignored parameters, and a hint.
fwrite(STDERR, 'MIGRATION GAP: ' . $exception->getMessage() . "\n");
}

針對每個差異,挑選成本最低且正確的修法:移除一個你從未依賴的參數,或透過 getDocument() 以現代 API 重新表達原本的意圖。凡是 TCPDF 介面無法表達的,都由這個逃生口處理。

migration-escape-hatch.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF();
$pdf->AddPage();
// Legacy path stays for the parts that already work:
$pdf->SetFont('helvetica', '', 12);
$pdf->Cell(0, 10, 'Header line', 0, 1);
// Modern path for what the TCPDF surface cannot express here —
// for example a clickable image (the legacy Image() link parameter
// is one of the silently ignored parameters):
$document = $pdf->getDocument();
$document->image('logo.png', 10, 30, 40, 0);
$document->link(10, 30, 40, 20, 'https://example.com');

請將嚴格模式作為專屬 CI 工作執行,跑完後關閉它,再部署這條經過稽核的程式碼路徑。保留一個定期執行的嚴格模式 CI 工作,以便在你重構時抓出退化。

  • MultiCell() 回傳 1Write() 回傳 0 這些是相容性佔位值,不是計算得出的值。請調整任何依這些回傳值分支的程式碼。
  • Error() 會擲出例外,而不是呼叫 die() 配接器會擲出 RuntimeException。凡是依賴行程終止的程式碼,都必須改為捕捉這個例外。
  • 靜默忽略的參數。 Image()writeHTML()SetProtection()Bookmark() 等方法會接受被忽略的舊參數。請用嚴格模式把它們找出來。若要做出可點擊的影像,先繪製影像,再在同一個矩形範圍上加入 Document::link()
  • 未實作的方法。 setSignature()addEmptySignatureAppearance()endPage() 都是無作用方法,在嚴格模式下會擲出例外;Open() 則是一個永不擲出例外的安全無作用方法。請移除 endPage()Open()。簽章需要透過現代簽章 API、使用商業版 NextPDF。
  • PDF 版本是固定的。 setPDFVersion() 無法向下指定較舊的 PDF 版本;輸出一律是 PDF 2.0。setUserRights() 在 PDF 2.0 中已棄用,會被忽略並附上一則通知。
  • 別名衝突。 在你移除 tecnickcom/tcpdf 之後,若仍有任何東西解析到真正的 TCPDF 類別,就表示前述別名注意事項生效了——請在那些呼叫點明確 import 配接器。

配接器會委派給引擎;文件建構成本隨內容而變,而不是隨配接器這一層而變。由於配接器的 Output() 不會寫入輸出緩衝區,因此它在佇列 worker 中是安全的——你可以像處理任何 NextPDF 產生作業那樣,把繁重的 TCPDF 風格產生作業移出請求執行緒。將位元組層級的測試重新改以繪製內容為基準,是一次性的成本,且能換來日後引擎升級時仍可維護的測試。

  • 加密。 SetProtection() 會忽略舊版的 modepubkeys 參數;引擎在標準處理常式中使用 AES-256。若要做憑證式加密,請使用配接器上公開的現代公鑰加密進入點,而不是舊參數。
  • 簽章受到把關。 基準簽章支援是一項商業版功能,須透過現代簽章 API 搭配憑證值物件才能取得;舊版的 setSignature() 是個無作用方法。本指南不對任何版本的長期驗證或時間戳記簽章設定檔做出任何宣稱。
  • 在稽核期間明確失敗。 嚴格模式讓靜默的參數遺失變得可見,因此呼叫端能得知自己的意圖未被採用。請把蒐集到的例外當成遷移工作清單,而不是正式環境的行為。
  • 切勿寫出空的 catch 區塊。 該稽核範例會捕捉 TcpdfNotImplementedException,並寫出一行明確的工作項目。

遷移期間完整的加密與簽章姿態,記載於 compat-legacy 的安全性與維運頁上。

本指南本身不提出任何規範性標準宣稱。配接器寫出 PDF 2.0(ISO 32000-2)輸出,且無法向下指定較舊的版本。該行為及其條款釘選在上游方法覆蓋頁上,該頁也記載了嚴格模式背後的 OWASP「明確失敗」原則,以及覆蓋稽核所依循的 ISO/IEC 25023 功能完整性 Framework(框架)。本範例指南頁重述其用法,並把那些引用延後到該頁處理。