跳转到内容
getnextpdf.com

一个引擎,适配每一种框架

Spec: PSR-11 Container, §1.1.2Spec: PSR-4 Autoloader, §3

大多数不断成长的 PHP 体系,最终都会用上不止一种框架。NextPDF 是一个 PDF 引擎,它会按各框架自己的方式去与它们对接:为 Laravel、Symfony 和 CodeIgniter 提供地道的桥接,外加一条独立运行路径,供那些不在任何框架里运行的代码使用。文档模型是共享的。改变的只有你调用它的方式。

为每套技术栈配一个不同的 PDF 库,是一笔无声的税。每个库都有自己的怪癖、自己的字体处理方式、对“有效”的自有一套理解。一张能从 Laravel 服务正确渲染出来的发票,从 Symfony 工作进程渲染时可能会有细微差异,因为是另一个库画出来的。如今,你的归档目标、你的签名位置、你的无障碍标签,都取决于是哪个团队交付了这份文件。缺陷报告说“这份 PDF 不对”,而答案取决于是三个引擎中的哪一个产出了它。

标准化到一个引擎,能把这片面积塌缩掉。决定 PDF/A 配置文件的地方只有一处,需要认证的字体管线只有一条,需要信任的验证器只有一个。你恰好身处哪个框架,不再是文件是否正确的一个变量。

  • 核心引擎与框架无关。 nextpdf/core 对 HTTP、路由或容器装配一无所知。它是一个 PDF 2.0 引擎,仅此而已。
  • 每个桥接只做适配,而不是重新实现。 Laravel、Symfony 和 CodeIgniter 软件包给你一个 facade 或工厂、一个 HTTP 响应辅助器,以及一条排队或异步的生成路径——全都架在同一个引擎之上。
  • 桥接跟随的是你的框架,而非你的文档。 它改变的是你怎么调用引擎,而绝不是引擎产出什么。
  • 独立运行路径始终可用。 一个 CLI 工具、一个守护进程,或一个库,并没有框架可供桥接;它直接构造一份文档。
  • 同一个文档模型在四条路径上通行。 同样的值对象、枚举与输出契约处处出现,因此一份文档能在各调用点之间原样迁移。

这套架构是一道刻意为之的拆分。引擎是资产;桥接是一层薄薄的适配器,只说某一种框架的惯用语。一个桥接通过标准自动加载(Spec: PSR-4 Autoloader, §3)在共享核心之上注册一个小小的命名空间,并通过容器契约(Spec: PSR-11 Container, §1.1.2)交还一份文档。这个契约正是这里的无声功臣:它允许对同一个标识符的两次解析返回不同的实例,而这恰恰是桥接如何在每个请求里给你一份全新、用后即弃的文档,同时把已解析的字体注册表与图像缓存保持为进程级单例的方式。长生命周期的工作进程——Octane、RoadRunner、Swoole、Messenger——天然地获得摊销后的字体解析,且不会有跨请求的状态泄漏。

这四种惯用语只在表层有所不同:

  1. Core enginenextpdf/core — the framework-agnostic PDF 2.0 engine; the single shared document model, value objects, and output contract.
  2. Laravel bridgenextpdf/laravel — auto-discovered provider, a Pdf facade, a PdfResponse helper, and a queued GeneratePdfJob.
  3. Symfony bridgenextpdf/symfony — an auto-registered bundle, an injectable PdfFactory, a PdfResponse, and an optional Messenger handler.
  4. CodeIgniter bridgenextpdf/codeigniter — a service and pdf() helper, a Pdf library over a disposable Document, and a PdfResponse.
  5. StandaloneNo framework to bridge from — construct a Document directly in a CLI tool, daemon, or library.
一个与框架无关的核心引擎,通过四种地道的表层来触达:一个 Laravel facade、一个注入的 Symfony 工厂、一个 CodeIgniter 服务,或一份直接构造的独立文档——每一种都交还同一份用后即弃的 Document 模型。

从左到右读这张图,得到的教益就是这份对称性。每一种表层都解析到同一个 Document。Laravel facade、Symfony 工厂、CodeIgniter 服务,以及独立构造器,是通往同一个房间的四道门。

同样三行意图,在每一种惯用语里各自表达一遍。构建文档的主体——页面、字体、单元格、签名、符合性——在四者中完全一致,因为它就是同一个引擎。

<?php
declare(strict_types=1);
// Laravel — resolve a fresh document from the container.
use NextPDF\Contracts\PdfDocumentInterface;
$document = app(PdfDocumentInterface::class);
// Symfony — inject the factory, then ask it for a document.
use NextPDF\Symfony\Service\PdfFactory;
$document = $factory->create(); // PdfFactory injected into your service
// CodeIgniter — pull it from the Services layer.
use NextPDF\CodeIgniter\Config\Services;
$document = Services::pdfDocument();
// Standalone — no framework; construct it directly.
use NextPDF\Core\Document;
$document = Document::createStandalone();
// From here, the code is identical regardless of how $document arrived.
$document->addPage();
$document->cell(0, 10, 'One engine, every framework', newLine: true);
$bytes = $document->getPdfData();

唯一的差别在最前面几行。之后的一切都是可移植的:把一个构建文档的服务从 Symfony 搬到一个独立工作进程,渲染代码不会改变,因为它所依赖的那个契约并没有改变。

常见的假设是:框架桥接会解锁能力——以为长期签名验证或结构化电子发票之所以到手,是因为你安装了 nextpdf/laravel,而非直接调用引擎。事实并非如此。桥接改变的是调用点,而绝不是引擎的能力范围。诸如 PDF/A 输出与 PAdES 基准签名这类核心能力是开源的,触达每一种表层;高级能力则由版本解锁,随后透过任何桥接或独立运行路径同等可用。选择一个框架集成,并不等于选择一组功能。

与之相对的误解是:以为“一个引擎”必然意味着对每一份文档只有一条渲染路径。事实并非如此。进程内引擎直接渲染 PDF;当一份文档确实需要浏览器级的排版引擎时,会由一个渲染器软件包来处理。渲染与调用是相互独立的两个维度——集成决策指南正是把它们对应起来的地方。

桥接并不会扩展引擎能渲染什么。这是诚实的限制,也正是要点所在:能力存在于核心与层级之中,而不在你借以触达它的那个适配器里。

Framework bridges over one engine — edition availability
EditionAvailability
Core

Every bridge (Laravel, Symfony, CodeIgniter) and the standalone path are Apache-2.0 and work against Core. They adapt or expose the engine; they do not gate features and do not change what it can produce.

Pro

Advanced capabilities such as long-term signature validation (PAdES B-LT and B-LTA) are unlocked by an edition, then reached identically through any bridge or standalone — never by switching framework. PDF/A archival output and PAdES baseline signing (B-B and B-T) are already in Core, available the same way through every surface.

Enterprise

Structured e-invoicing (EN 16931) and deeper compliance tooling are edition capabilities too, likewise the same whichever surface calls the engine, while conformance validation itself ships in Core.

还有两道边界值得明确说出来。第一,每个桥接都对应其框架的某个当前主版本——Laravel、Symfony 和 CodeIgniter 各自锁定一个受支持的范围,因此“每一种框架”指的是每一种框架的受支持版本,而非每一个历史发行版;请把各软件包自身的文档当作其 API 的权威依据。第二,这些桥接是框架适配器,而不是渲染后端。如果一份文档需要一个完整的浏览器排版引擎,那是一个与“是哪个框架调用了引擎”相互独立的渲染器选择。

  • 集成决策指南 — 用例到软件包的对应图,涵盖各渲染器与 Connect 服务表层,供你需要做决策而非标准化时使用。
  • 开放核心,不被锁定 — 为什么引擎是资产、桥接是轻薄的,所以标准化并不会困住你。
  • HTML 管线 — 进程内引擎覆盖了什么,让你知道何时浏览器渲染器才是另一个独立的问题。
  • PHP 8.4 基础 — 每个桥接与独立运行路径共享的运行时底线。
  • 核心引擎(Core engine)nextpdf/core,与框架无关的 PDF 2.0 引擎,每个桥接与独立运行路径都建立在其上。
  • 框架桥接(Framework bridge) — 一个集成软件包(Laravel、Symfony、CodeIgniter),把引擎适配到某框架的惯用语——facade、工厂、响应、排队任务——而不改变其能力。
  • 独立运行路径(Standalone path) — 直接使用核心引擎,不依赖任何框架,由你自己构造一份 Document;这是 CLI 工具、守护进程与库所走的路线。
  • 用后即弃的文档(Disposable document) — 用一次的 Document 契约:构建、输出、丢弃。每一次容器解析都返回一份全新的,因此在长生命周期的工作进程中,不会有状态在各请求之间泄漏。
  • PAdES — PDF Advanced Electronic Signatures(PDF 高级电子签名),即用于 PDF 签名的 ETSI 配置文件系列。基准签名(B-B 与 B-T)在 Core 中;长期验证(B-LT 与 B-LTA)是高级版本的能力。无论哪一项,都透过任意一种表层触达,并在签名相关页面中详述。