在生产环境运行 compat-legacy
此适配器可安全运行在 HTTP handler、队列工作进程和长时间运行的进程中。它比旧版 TCPDF 6.2.13 更安全,因为它移除了生产环境中最常见的两个风险:直接写入输出缓冲区,以及出错时调用 die()。本页说明如何正确使用它。
先决条件:先在 /integrations/tcpdf-compat/migration/ 中完成严格模式审计,并在部署时将严格模式关闭。
工作进程与 handler 中的输出处理
标题为“工作进程与 handler 中的输出处理”的章节旧版 TCPDF Output() 会直接输出到活跃的输出缓冲区。这会破坏 HTTP 框架中的响应,并使队列工作进程失效。此适配器改为通过安全的目标桥接器路由输出。
请根据调用方选择合适的目标:
| 场景 | 目标 | 原因 |
|---|---|---|
| 写入存储位置的队列工作进程 | Output($path, 'F') | 写入文件;返回空字符串。不会与缓冲区交互。 |
| 生成后再 attach/upload | Output($name, 'S') | 返回 PDF 字节;由你控制其后续去向。 |
| 电子邮件附件 | Output($name, 'E') | 返回包含 Content-Type: application/pdf 的 base64 MIME 主体。 |
| 由你控制的 HTTP 响应 | Output($name, 'S') | 获取字节后,再设置你自己的标头和主体。 |
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\Exception\TcpdfNotImplementedException;use NextPDF\Compat\Tcpdf\TCPDF;
/** * Render an invoice in a queue worker. Returns the storage path. * * @throws \RuntimeException on a render failure (Error() throws, not die()). */function renderInvoiceJob(array $invoice, string $storageDir): string{ $pdf = new TCPDF('P', 'mm', 'A4'); $pdf->SetFont('helvetica', '', 12); $pdf->AddPage(); $pdf->Cell(0, 10, 'Invoice ' . $invoice['number'], 0, 1);
$path = $storageDir . '/invoice-' . $invoice['number'] . '.pdf';
try { $pdf->Output($path, 'F'); // writes file, no buffer pollution } catch (TcpdfNotImplementedException $e) { // Only reachable if strict mode is on — it must NOT be in production. throw new \RuntimeException('Adapter strict-mode gap in production: ' . $e->getMessage(), 0, $e); } catch (\RuntimeException $e) { // Error() throws RuntimeException instead of die(). throw new \RuntimeException('PDF render failed: ' . $e->getMessage(), 0, $e); }
return $path;}对于 HTTP handler,建议优先使用 'S',并自行设置标头:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF();$pdf->AddPage();$pdf->SetFont('helvetica', '', 12);$pdf->Cell(0, 10, 'Report');
$bytes = $pdf->Output('report.pdf', 'S');
header('Content-Type: application/pdf');header('Content-Length: ' . strlen($bytes));header('Content-Disposition: inline; filename="report.pdf"');echo $bytes;失败处理
标题为“失败处理”的章节Error() 会抛出 RuntimeException;它绝不会调用 die()。与旧版 TCPDF 相比,这是最重要的单项运维变化。
- 将每个渲染入口点都包在
try/catch中。 - 将异常映射到你应用的错误契约(HTTP 5xx、任务失败、重试、死信队列)。
- 不要假设渲染失败时进程会退出——它不会。
如果你定期运行严格模式 CI 任务(建议这样做),其中出现的 TcpdfNotImplementedException 代表一项真实发现:某条代码路径依赖了一个不支持的 TCPDF 参数。请将其视为迁移工作项,而不是不稳定测试。
生命周期与资源处理
标题为“生命周期与资源处理”的章节- 文档会在第一次输出调用时才延迟构建其 PDF 字节。
Close()为可选;调用它会缓存字节。Open()是一个安全的空操作。 endPage()不会执行任何操作——NextPDF 会管理页面生命周期。请将它从高频循环中移除;它没有任何作用。- 让 PHP 在不同任务之间对该适配器进行垃圾回收。
_destroy()会重置该适配器的缓存数据,但在正常的工作进程循环中,你不需要显式调用它。 - 为每份文档创建一个全新的适配器。不要在长时间运行的工作进程中,在互不相关的文档之间复用同一个适配器实例;文档状态按实例保存。
性能指南
标题为“性能指南”的章节- 此适配器是一个轻量的委派层;成本主要取决于引擎,而非适配器。
- 在启动时一次性定义旧版常量。
LegacyDefaults::register()和LegacyBootstrap::enableAliases()都是幂等且受保护的,因此重复调用的成本很低。在每次请求中定义常量没有意义。 - 在非浏览器场景中,建议优先使用
Output(..., 'S')或'F',而非'I'/'D'。inline/download 路径会产生不由框架管理的输出,这通常不是你希望在工作进程中发生的行为。 - 对于大批量生成的情况,请对引擎进行性能分析,而非适配器。相较于渲染,适配器自身的每页额外开销预算很小。
- 每个适配器实例都是独立的,并持有自己的文档状态。只要每个工作单元都使用自己的适配器实例,在进程或工作进程层级的并发就是安全的。
LegacyBootstrap和LegacyDefaults中的幂等性防护使用进程本地静态状态;它们在典型的 PHP per-request/per-worker 模型下是安全的。它们并非为在多个线程之间共享可变状态而设计。
上线前检查清单
标题为“上线前检查清单”的章节- 已完成严格模式审计;生产环境在严格模式关闭的状态下运行。
- 所有渲染入口点都已包在
try/catch中,用于捕获RuntimeException(不依赖die())。 - 工作进程使用
Output(..., 'F')或'S',绝不使用 inline 路径。 - 旧版常量在启动时、首次构造之前定义一次。
- 已建立定期的严格模式 CI 任务,用于捕获回归。
- 已重新建立字节级测试断言的基准(请参阅 /integrations/tcpdf-compat/migration/)。
另请参阅
标题为“另请参阅”的章节- /integrations/tcpdf-compat/security-and-operations/ — 加密、签名态势、加固
- /integrations/tcpdf-compat/troubleshooting/ — 生产环境失败模式和修复方法
- /integrations/tcpdf-compat/configuration/ — 严格模式和常量卫生
- /integrations/tcpdf-compat/migration/ — 上线生产前必须完成的审计