跳转到内容

管线模型

Spec: ISO 32000-2, §7.5 Evidence: Code-backed

一份 NextPDF 文档并不是通过一个不透明的单一步骤生成的。它会经过几个明确阶段:记录意图的 facade、将意图转换为模型的内容层,以及把该模型序列化为合规 PDF 的 writer。本页说明这一结构,以及它为什么会这样设计。

PDF 文件格式本身就是一个分层结构——一个文件头、一段对象主体、一张交叉引用表,以及一个 trailer——writer 必须以一致的方式组装这一切。如果构建它的引擎是一段纠缠在一起的单体程序,那么每一次变更都会危及每一份输出。这样一来,要获得信心的唯一办法就是渲染整份文档并人工检查,而这既缓慢,又为时太晚,也缺乏说服力。

一条边界清晰的管线会改变这个局面。每个阶段只有一项职责和一道类型化边界,因此你可以围绕某项变更进行推理,并在它触及的阶段进行测试,而不必只针对文件的最终结果测试。这套架构首先是一项关于可测试性与可扩展性的决策,然后才是其他东西。

  • 公开入口点是 Document facade。它是一个流畅、一次性使用、worker 安全的构建器,记录你想要的是什么,而不是它如何被序列化。
  • 这个 facade 会委派给约二十多个各司其职的 concern trait(文本输出、绘图、页面、安全性、导航等)——每一个只负责一项职责,而不是一个庞大的类。
  • 内容通过两条路径之一进入:直接绘图(图形基本元素)或 HTML/CSS 引擎。两者都会产生相同的内部文档模型。
  • 专责的 PDF writer 会序列化该模型,并选择 PDF 1.4 / 1.7 / 2.0 的策略。有效的文件结构只在这里产生,别无他处。
  • 长期存在的状态(字体与图像注册表)属于进程范围且共享;每次请求的状态(文档)则是全新创建、绝不重复使用。这道边界很明确,而这正是让 worker 运行环境安全的原因。

理解这个模型最清楚的方式,是跟着一份文档从调用一路走到字节。

  1. Document facade Fluent, use-once builder; records intent via concern traits.
  2. Content production Direct drawing or the HTML/CSS engine — both build one document model.
  3. Document model Accumulated pages, content, and resources held as typed state.
  4. PDF writer Serialises the model; selects a PDF 1.4 / 1.7 / 2.0 strategy.
  5. Conforming PDF Header, object body, cross-reference table, trailer.
一份文档流经 NextPDF 的路径:每个阶段只有一项职责与一道类型化的边界,因此可以被独立推理与测试。

有两项设计决策,让它不只是一张图。

这个 facade 是组合而成的,而非单体式的。 Document 并不是自己实现每一项功能;它把每个领域委派给一个专责的 concern trait——文本输出、绘图、页面、安全性、字体排印、导航、事务等。新的文档方法应归属于拥有对应领域的那个 trait,而不是放在 facade 本身上。你所调用的那个类保持精简,各项职责也保持彼此分离。

writer 独占地拥有文件结构。 内容生成决定存在哪些标记与对象;writer 则决定它们如何变成一个有效的 PDF 文件,包括适用哪一种版本策略。这项分离以架构规则的形式强制执行:布局与内容代码不会生成最终的文件结构,而 writer 也不做布局决策。好处在于「输出是否是有效的 PDF?」这个问题恰好只有一个地方需要被测试。

生命周期边界是这个模型的一部分,而不是事后才补上的。字体与图像注册表贯穿整个进程的生命周期,并在多个请求之间共享;文档、其渲染上下文,以及 writer 则是每次请求各自创建并于用后释放。在 worker 运行环境中,这一区分正是安全重用与跨请求污染之间的分界。正因如此,这一点被明确写入架构,而不是依赖自觉。

本页为 Evidence: Code-backed 。这些阶段对应 core 仓库中的实际结构:

  • facade 及其委派对应 src/Core/Document.php,以及 src/Core/Concerns/ 中的 concern trait(文本输出、输出、绘图、页面、安全性、字体排印、导航、事务等——每一个都只负责一项职责)。
  • 两条内容路径分别是 HTML/CSS 引擎(src/Html/)与直接绘图(src/Graphics/),两者都将数据送入内部模型。
  • 序列化与 PDF 版本策略位于 src/Writer/PdfWriter.php,搭配明确的 PDF 1.4 / 1.7 / 2.0 策略类)。
  • 进程生命周期与每次请求之间的边界,就是架构总览中记载、并由随附 worker-factory 示例实际演练的 worker 安全设计;该示例在多个请求之间共享同一个 FontRegistryImageRegistry,同时为每次请求各自全新创建一个 Document

输出目标由格式决定。writer 的输出必须是一个文件头、一段对象主体、一张交叉引用表,以及一个 trailer, Spec: ISO 32000-2, §7.5 。将这项义务集中在单一阶段,正是让引擎的其余部分能够持续专注于内容、而非组装文件结构的原因。

facade 的作用,是让意图读起来就像意图本身。内容路径与 writer 对调用方保持隐形:

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone(); // facade
$doc->setTitle('Quarterly Report'); // metadata concern
$doc->addPage(); // pages concern
$doc->setFont('helvetica', 'B', 16); // typography concern
$doc->cell(0, 12, 'Summary', newLine: true); // text-output concern
$doc->writeHtml('<p>Generated in-process.</p>'); // HTML content path
$doc->save(__DIR__ . '/report.pdf'); // writer stage

每一次调用都落在不同的 concern 中。两条不同的内容路径会送入同一个模型。只有一个阶段——save()——负责把模型转换成文件字节。调用方不需要知道交叉引用表是如何构建的。

常见误解是把「管线」理解成流式 push API,认为你需要像 Unix pipe 那样,把一个阶段接到下一个阶段。事实并非如此。这里的管线是一种架构性拆解:各个阶段具有单一职责与类型化边界。你仍然是针对一个流畅的 facade 进行开发。这些阶段是引擎被构建与测试的方式,而不是一条需要你手动组装的传输通道。

另一个相关误解,是假设 facade 就是引擎。它只是入口点。真正的工作分散在 concern trait、两条内容路径,以及一个 writer 之间。正是这种分散,让一项功能变更不会危及每一份输出。

本页描述的是这条管线的结构,而不是任何单一阶段的内部 API。确切的 concern-trait 清单、writer 策略选择规则,以及内容模型字段,由代码与参考文档定义,而不是由本说明定义。确切的 trait 数量属于实现细节,可以在不改变模型的情况下变动。本页不涵盖 HTML 引擎的内部阶段(属于另一主题),也不涵盖 writer 的流式与内存行为(同样另成一篇)。这些结构性陈述截至本页的审阅日期是准确的;权威来源是 core 仓库的 src/Core/src/Html/src/Graphics/,以及 src/Writer/

管线模型在各版本之间完全相同;各版本是在既有阶段内增添能力,而不是新增阶段:

Pipeline model — edition availability
Edition Availability
Core Core 实现完整的 facade → 内容 → writer 管线。
Pro Pro 在既有阶段内增添能力,而非新增阶段。
Enterprise Enterprise 在既有阶段内增添能力,而非新增阶段。
  • Facade——对外公开的 Document 入口点:一个流畅、一次性使用的构建器,记录意图并委派给 concern trait。
  • Concern trait——由 facade 组合进来、各司其职的 PHP trait,每一个都拥有单一功能领域(文本输出、绘图、页面、安全性等)。
  • Content path——内容进入模型的两种方式之一:直接绘图或 HTML/CSS 引擎。
  • Document model——引擎在序列化之前累积的页面、内容与资源的内部类型化状态。
  • Writer stage——负责将模型序列化为有效 PDF 的组件,并选择 PDF 1.4 / 1.7 / 2.0 的策略。
  • Worker-safe——这种设计允许安全共享进程生命周期状态,而每次请求的状态则会全新创建、绝不重复使用。