跳转到内容

在生产环境运行 compat-legacy

此适配器可安全运行在 HTTP handler、队列工作进程和长时间运行的进程中。它比旧版 TCPDF 6.2.13 更安全,因为它移除了生产环境中最常见的两个风险:直接写入输出缓冲区,以及出错时调用 die()。本页说明如何正确使用它。

先决条件:先在 /integrations/tcpdf-compat/migration/ 中完成严格模式审计,并在部署时将严格模式关闭

旧版 TCPDF Output() 会直接输出到活跃的输出缓冲区。这会破坏 HTTP 框架中的响应,并使队列工作进程失效。此适配器改为通过安全的目标桥接器路由输出。

请根据调用方选择合适的目标:

场景目标原因
写入存储位置的队列工作进程Output($path, 'F')写入文件;返回空字符串。不会与缓冲区交互。
生成后再 attach/uploadOutput($name, 'S')返回 PDF 字节;由你控制其后续去向。
电子邮件附件Output($name, 'E')返回包含 Content-Type: application/pdf 的 base64 MIME 主体。
由你控制的 HTTP 响应Output($name, 'S')获取字节后,再设置你自己的标头和主体。
examples/production-worker.php
<?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',并自行设置标头:

examples/production-http.php
<?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 路径会产生不由框架管理的输出,这通常不是你希望在工作进程中发生的行为。
  • 对于大批量生成的情况,请对引擎进行性能分析,而非适配器。相较于渲染,适配器自身的每页额外开销预算很小。
  • 每个适配器实例都是独立的,并持有自己的文档状态。只要每个工作单元都使用自己的适配器实例,在进程或工作进程层级的并发就是安全的。
  • LegacyBootstrapLegacyDefaults 中的幂等性防护使用进程本地静态状态;它们在典型的 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/ — 上线生产前必须完成的审计