跳转到内容

将 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

在修改任何代码之前,先确认引擎依赖解析正常(nextpdf/core ^3.0),并且测试套件仍可运行。

适配器是一层兼容层,不是 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) —— SFEID

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()。签名需要使用商业版 NextPDF,并通过现代签名 API 实现。
  • 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 功能完整性框架。本范例指南页仅重述其用法,并将这些引用留待该页处理。