跳转到内容

契约 / 文档

文档域包含用于构建 PDF 的契约:负责内容的 PdfDocumentInterface、用于在 worker 中安全创建文档的 DocumentFactoryInterface、字体和图像注册表契约,以及三个输出与布局枚举。它们自 1.0.0 或 1.7.0 起均为 stable

Terminal window
composer require nextpdf/core:^3

PdfDocumentInterface 是主要的 API 接口面。它定义页面管理、字体选择、单元格与多单元格文本布局、HTML 渲染、图像嵌入以及最终输出。每个方法都返回 static,因此调用可以链式串联。Document::createStandalone() 返回一个满足该接口的具体实例。在你自己的服务中,请以该接口作为类型提示。这样,引擎内部实现就能保持可替换。

文档创建有两条路径。在传统 PHP-FPM 请求中,createStandalone() 会构建一个带私有注册表的自包含文档。长时间运行的 worker 则使用另一条路径,包括 RoadRunner、Swoole 和 Laravel Octane。在这类环境中,DocumentFactoryInterface::create() 返回一个全新的、用完即弃的 Document。该文档从进程生命周期级别的注册表读取数据,但绝不会修改它们。该工厂持有 FontRegistryInterfaceImageRegistryInterface 单例。每个文档都会获得自己的渲染上下文和写入器。这就形成了故障隔离:一个文档无法破坏另一个文档所依赖的共享状态。

注册表契约正是 worker 能保持高速的原因。FontRegistryInterface 只解析一次字体文件,并在整个进程生命周期内缓存解析后的元数据。它可以在预热后锁定,使生产流量无法再修改它。ImageRegistryInterface 在有界的最近最少使用策略下缓存解码后的图像二进制数据。即使二进制数据被逐出,图像元数据仍保持驻留。二者都公开 memoryUsage(),用于容量规划。ImageRegistryInterface 继承自 ResettableService。该契约会逐出缓存数据,但不会破坏结构性元数据。worker 可以在内存承压时丢弃图像缓存,并继续提供服务。

三个枚举补齐了该域。OutputDestination 用于选择内联显示、强制下载、写入文件系统或返回原始字符串。Orientation 用于选择纵向或横向。Alignment 用于选择文本的左对齐、居中、右对齐或两端对齐。每个枚举项的值都沿用旧版 TCPDF 代码。因此,compat-tcpdf 桥接能够干净地完成映射。这些枚举的向后兼容承诺是只增不减:不会移除任何枚举项,新的枚举项可能在次要版本中加入。

类型种类关键成员稳定性起始版本
PdfDocumentInterface接口addPage()setMargins()setFont()cell()multiCell()writeHtml()image()output()save()stable1.0.0
DocumentFactoryInterface接口create(?Config): Documentstable1.7.0
ResettableService接口reset(): voidstable1.7.0
FontRegistryInterface接口register()get()warmup()lock()isLocked()registerFromBinary()memoryUsage()stable1.7.0
ImageRegistryInterface接口load()loadFromString()getMetadata()memoryUsage() (继承自 ResettableService)stable2.0.0
OutputDestination枚举(字符串)InlineDownloadFileStringstable1.0.0
Orientation枚举(字符串)PortraitLandscapestable1.0.0
Alignment枚举(字符串)LeftCenterRightJustifystable1.0.0

FontRegistryInterfaceImageRegistryInterface 在字体排印页面中有完整说明;本页则介绍它们在创建生命周期中的作用。

examples/01-hello-world.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Hello World');
$doc->addPage();
$doc->setFont('helvetica', '', 24);
$doc->cell(0, 15, 'Hello, NextPDF!', newLine: true);
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'This is a minimal PDF generated with NextPDF.', newLine: true);
$doc->save(__DIR__ . '/output/01-hello-world.pdf');
examples/02-pdf-factory.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\PdfFactory;
use NextPDF\ValueObjects\{Margin, PageSize};
$factory = PdfFactory::new()
->withPageSize(PageSize::A4())
->withMargins(new Margin(15.0, 15.0, 15.0, 15.0))
->withCompress(true)
->withLang('en');
// The same configured factory creates independent documents.
$doc = $factory->create();
$doc->setTitle('PdfFactory Example');
$doc->setAuthor('NextPDF');
$doc->addPage();
$doc->setFont('helvetica', '', 16);
$doc->cell(0, 12, 'Created via PdfFactory', newLine: true);
$doc2 = $factory->create();
$doc2->addPage();
$doc2->setFont('helvetica', '', 12);
$doc2->cell(0, 10, 'Second document from the same factory.');
$doc->save(__DIR__ . '/output/02-pdf-factory.pdf');

PdfFactory 是不可变构建器;每个 with*() 都返回一个新实例。它在底层组合了一个 DocumentFactoryInterface,因此概述中的 worker 注册表模型无需额外装配即可适用。

  • createStandalone() 会构建私有注册表。在 worker 循环中,这会在每次请求时重新解析每种字体。应改用带共享注册表的 DocumentFactoryInterface
  • 从设计上讲,Document 是用完即弃的对象。在多个逻辑文档之间复用同一个实例会导致状态泄漏。应为每个文档调用一次 create(),并交给垃圾回收处理。
  • FontRegistryInterface::lock() 会使 register()addFontDirectory()warmup() 抛出 LogicException。应在预热之后锁定,绝不要在处理请求期间锁定。
  • OutputDestination::File 会写入服务器文件系统并返回原始字节。save() 是显式的文件路径方式。不要对同一个文档混用这两种方式。
  • cell() 的边框参数为了兼容 TCPDF 而接受 bool|string;空字符串与 false 并不相同。请传入与你意图一致且类型明确的值。

字体和图像注册表使文档域成为受内存上限约束的系统,而不是逐请求重复处理的系统。首次请求中的字体解析占主要开销。在 worker 示例中,三个文档合计的 performance_budget 为 1500 ms 实际耗时和 64 MB 峰值内存。这一预算几乎全部用于首次字体解析。预热之后,每个文档由这些契约引入的工作量为 O(1)——一次注册表查找和一次上下文分配。在任一注册表上调用 memoryUsage() 都会返回一个 MemoryReport,用于实时容量规划。ResettableService::reset() 在持续负载下限制峰值内存。

文档契约不涉及任何密码学层面,但存在两项运维风险。第一,image() 接受路径或 URL。对于不可信输入,应通过 ExternalResourcePolicyInterface(参见安全策略页面)约束远程获取,而不要直接传入用户可控的 URL。第二,writeHtml() 是 HTML 管道的入口点。不可信的标记在渲染之前必须先经过 HtmlSecurityPolicyInterface。文档层本身不做净化处理。这是安全策略域的职责;由于该策略是契约,你无需分叉即可提供更严格的实现。

文档契约按 ISO 32000-2 的定义实现 PDF 2.0 文档结构。输出、页面和字体处理按 ISO 32000-2 §7 生成间接对象和交叉引用流。内容按引擎层契约 (ADR-010) 通过写入器层发出。除结构合规性外,本页不提出任何条款级声明。PDF/A 和 PDF/UA 合规性在提取和无障碍页面中说明,这些页面包含规范性表格。