跳转到内容

契约:41 个公开接口(SPI)

NextPDF\Contracts 是公开的 service-provider interface(SPI,服务提供者接口):src/Contracts/ 下共有 41 个接口与枚举,每个都带有明确的 @stability 标记和向后兼容承诺。扩展包、Framework(框架)桥接器,以及 Pro 与 Enterprise 版本都面向这些类型编写代码,绝不直接面向具体类。

Terminal window
composer require nextpdf/core:^3

引擎将两个接口层分开。src/Core/src/Html/src/Writer/ 下的具体类不附带任何兼容性承诺。它们可能在各个次要版本之间自由变动。Contracts 命名空间则恰好相反。它是一组精选类型,其签名会按各自声明的稳定性层级冻结。引擎之外的一切都只依赖这个命名空间,不再越过它访问更深层实现。这包括 Laravel、Symfony 与 CodeIgniter 桥接器、compat-tcpdf shim(兼容层)、NextPDF Server,以及 Pro 与 Enterprise 版本。

每个契约都会在其 PHPDoc 中声明四个层级之一。stable 契约在次要或补丁版本中不接受任何破坏性变更。新方法只会随默认实现一同加入。experimental 契约可能在次要版本中变更,但会附上弃用通知。deprecated 契约会指明其替代类型。少数类型是仅契约类型,例如 StreamingWriterInterfaceCursorInterface。这些类型已发布并冻结,但尚未随附任何生产环境实现。

权威的层级清单是 docs/extension-points.json(清单版本 3.0.0,覆盖 ContractsEvent 共 67 个已发布的扩展点)。一个机器可验证的测试 tests/Unit/Contracts/StabilityContractTest.php 会读取该清单。它会在五种情况下让构建失败。第一种是清单列出的类型缺失。第二种是反射取得的类型与清单不一致。第三种是 @stability PHPDoc 标记与清单发生偏移。第四种是 src/Contracts/ 下存在契约,却未出现在清单中。第五种是 @internal 类型泄漏到清单中。契约接口层一旦出现偏移,就一定会被检测到。

契约分为九个领域。每个领域都有专属页面:文件构建、签章、条码编码、字体排印、安全策略、提取、可观测性与流式处理。这样的划分对应整合者采用引擎的方式。你会依赖文件契约来生成 PDF。你会依赖签章契约来加入签章。你会依赖安全策略契约来约束不受信任的 HTML。

在整个引擎中,resolve(解析)可选实现都遵循同一种模式。Core 会以 class_exists() 检查具体类是否存在,再按对应契约使用它。LtvManagerInterfacePdfAManagerInterface 就是以这种方式解析其 Pro 实现。因此 Core 保持 Apache-2.0 授权,且不会强制依赖商业代码。

契约类型稳定性起始版本领域
PdfDocumentInterface接口稳定1.0.0文件
DocumentFactoryInterface接口稳定1.7.0文件
ResettableService接口稳定1.7.0文件
OutputDestination枚举稳定1.0.0文件
Orientation枚举稳定1.0.0文件
Alignment枚举稳定1.0.0文件
SignerInterface接口稳定1.0.0签章
HsmSignerInterface接口稳定1.0.0签章
DeferredSignerInterface接口实验性3.0.0签章
TimestampProviderInterface接口实验性3.0.0签章
LtvManagerInterface接口稳定1.10.0签章
CryptoPolicyInterface接口稳定1.9.0签章
Barcode1DEncoderInterface接口稳定1.0.0条码
Barcode2DEncoderInterface接口稳定1.0.0条码
BarcodeEncoderInterface接口稳定3.0.0条码
Gs1DataParserInterface接口稳定1.0.0条码
FontRegistryInterface接口稳定1.7.0字体排印
TextPreprocessorInterface接口稳定1.9.0字体排印
HtmlSecurityPolicyInterface接口稳定3.1.0安全策略
ExternalResourcePolicyInterface接口稳定4.0.0安全策略
InspectorInterface接口实验性2.2.0提取
EmbeddingServiceInterface接口实验性2.1.0提取
VectorIndexInterface接口实验性2.1.0提取
JobNotificationInterface接口实验性2.2.0可观测性
SpectrumInterface接口实验性2.1.0可观测性
StreamingWriterInterface接口实验性3.1.0流式处理
CursorInterface接口实验性3.1.0流式处理

此表列出主要契约。其余类型——value-object DTO(TextSegmentTextPreprocessResult)、EInvoice 子命名空间、行为枚举(DegradationPolicyUnderlineStyle),以及导入契约(ImportedFormObjectInterfaceEmbeddedPdfObjectInterfaceChromeRenderResultInterface)——都记录在 另见 所列的领域页面中。完整的机器可读清单是 docs/extension-points.json,并同步镜像到 .ai/contracts-map.md

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->save(__DIR__ . '/output/01-hello-world.pdf');

Document::createStandalone() 会返回一个具体的 Document,并满足 PdfDocumentInterface。请在自己的服务中以接口作为类型提示,让引擎内部保持可替换。

examples/14-worker-factory.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\DocumentFactory;
use NextPDF\Core\PdfFactory;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Typography\FontRegistry;
// Created once at process boot in a RoadRunner/Swoole/Octane worker.
$fontRegistry = new FontRegistry();
$imageRegistry = new ImageRegistry(maxCacheBytes: 50 * 1024 * 1024);
$documentFactory = new DocumentFactory($fontRegistry, $imageRegistry);
$factory = PdfFactory::new()
->withCompress(true)
->withDocumentFactory($documentFactory);
for ($request = 1; $request <= 3; $request++) {
$doc = $factory->create();
$doc->setTitle("Worker Request #{$request}");
$doc->addPage();
$doc->setFont('helvetica', 'B', 16);
$doc->cell(0, 12, "Worker Request #{$request}", newLine: true);
$doc->save(__DIR__ . "/output/14-worker-request-{$request}.pdf");
}

DocumentFactory 实现 DocumentFactoryInterface。它会持有进程生命周期级别的 FontRegistryInterfaceImageRegistryInterface 单例,并把它们注入每个可丢弃的 Document,因此一个 worker 在数千个请求中只需解析每个字体一次。

  • 仅契约类型可以通过编译,但没有任何运行时支持。new 无法用于 StreamingWriterInterfaceCursorInterface,因为目前还没有任何类实现它们。请把它们视为预先声明的 API。
  • 单个类型的真实来源是 @stability PHPDoc 标记。docs/extension-points.json 是整组类型的真实来源。当两者不一致时,StabilityContractTest 就会失败——不要只改其中一边来掩盖这种不一致。
  • experimental 在实践中并不代表不稳定,而是代表兼容性承诺较弱。在固定依赖某个契约之前,请先阅读其 bc_promise 字段(位于 .ai/contracts-map.md)。
  • 标示为 @internal 的类永远不是契约,即使其他包在技术上能够引用它。稳定性测试会拒绝任何出现在清单中的 @internal 类型。
  • stable 接口新增方法,对实现者而言是破坏性变更,除非该方法随附默认实现。引擎通过新增接口来扩展能力,而不是扩展既有接口。

面向 Contracts 编写代码不会增加任何可测量的运行时成本:接口类型提示在链接时解析,而不是每次调用时解析。本页 worker 示例的 performance_budget 为三份文件合计 1500 ms 墙钟时间与 64 MB 峰值内存。第一个请求的字体解析会主导这份预算。后续请求会重用 registry 缓存,使契约相关的工作降到个位数毫秒。成本模型在每次契约分派上为 O(1);真正的工作发生在具体实现中,并记录于各领域页面。

SPI 同时也是一道安全边界。HtmlSecurityPolicyInterfaceExternalResourcePolicyInterface 是默认拒绝的契约,会在不受信任的 HTML 抵达 renderer(渲染器)之前约束它能做的事。CryptoPolicyInterface 会把关签章与加密所用的算法和密钥强度选择。因为这些是契约,整合者可以提供更严格的策略,而无需分叉引擎。任何与安全相关的策略都请固定依赖 stable 层级。实验性策略契约可能在各个次要版本之间改变形态。签章与安全策略领域页面会说明完整的威胁模型与规范性参考。

本概览不作任何直接的规范性主张;每个领域页面都会提供自己的 citations 区块。签章契约对应到 ISO 32000-2 §12.8(数字签名)与 ETSI EN 319 142(PAdES 基准)。PDF/A 管理器对应到 ISO 19005-4。关于条款层级的符合性表格,请参阅签章、安全策略与提取页面。

Pro 与 Enterprise 版本实现了数个 Core 契约背后的生产环境代码:LtvManagerInterface(长期验证)、PdfAManagerInterface(PDF/A 强制)、Hardware Security Module(HSM,硬件安全模块)与延迟签署者、条码编码器,以及嵌入与向量 Index(索引)契约。Core 发布并冻结接口;Premium 套件则随附实现。这让开源引擎保持 Apache-2.0 授权,同时让商业部署享有可直接替换、且无需变更 API 的升级。