跳转到内容

Exception:类型化异常层级

NextPDF 抛出的每个异常都继承自同一个抽象基类 NextPdfException,因此单个 catch 就能处理任何引擎错误。每个领域异常都实现 ContextAwareExceptionInterface,并对外提供结构化诊断字段,让日志与 APM 不必解析消息字符串即可使用。

Terminal window
composer require nextpdf/core:^3

该层级分为三层:

RuntimeException (PHP SPL)
└── NextPdfException (abstract; implements ContextAwareExceptionInterface)
├── InvalidConfigException
├── FontNotFoundException
├── FontParsingException
├── ImageProcessingException
├── SignatureException
├── EncryptionException
├── WriterException
├── PageLayoutException
├── HtmlParsingException
├── CompressionException
├── NotImplementedException
├── … (23 domain exceptions total)
└── Strict\StrictModeViolation (abstract)
├── Strict\IncompatibleRenderingModeException
├── Strict\OracleConformanceFailure
└── Strict\UnregisteredCssDeviation

NextPdfException 继承自 SPL 的 RuntimeException。捕获 RuntimeException 也会一并捕获 NextPDF 错误;捕获 NextPdfException 则会把范围收窄到引擎错误。若要有针对性地恢复,请捕获具体的叶节点类。Strict\ 子树将符合性模式(conformance-mode)违规归到抽象的 StrictModeViolation 之下,而后者本身又继承自 NextPdfException

使用上下文,而不是字符串代码。 NextPDF 通过错误对应的 PHP 类型识别错误,而不是使用字符串错误代码。异常类中没有 NPDF-#### 这类代码常量。取而代之,ContextAwareExceptionInterface::getContext() 会返回一个 array<string, mixed>,其中包含 snake_case 的原始类型字段,可安全序列化到日志或 APM payload。NextPdfException::getContext() 默认返回 []。每个领域异常都会重写它,填入与该失败相关的字段。例如,FontNotFoundException::getContext() 会返回 font_namesearch_paths 以及 fallback_attemptedWriterException 会返回 output_pathwriter_stateInvalidConfigException 会返回 config_keygiven_value 以及 expected_type。另一组独立且稳定的 NEXTPDF_W_* 字符串标识符属于 Support 模块中非致命的 WarningCode 枚举,而不属于异常。

可采取的操作。 每个领域异常的类说明文档都会注明谁可以处理它:开发者、基础设施,或库的调用方。InvalidConfigException 属于开发者错误(请修正配置)。FontNotFoundException 属于开发者或基础设施错误(请确认路径或文件权限)。WriterException 属于基础设施错误(磁盘、权限、输出流)。NotImplementedException 属于调用方错误(请移除该调用,或锁定到实现了所指明后续功能的版本)。有几个异常带有具名构造方法,用来精确标示根本原因,例如 SignatureException::ltvCapabilityMissing()::tsaRequired() 等,让运维人员看到实际原因,而不是一条通用消息。

符号种类主要成员
NextPDF\Contracts\ContextAwareExceptionInterface接口getContext(): array<string, mixed>
NextPDF\Exception\NextPdfException抽象类继承 RuntimeExceptiongetContext()(默认 []
NextPDF\Exception\InvalidConfigExceptionfinal 类getConfigKey()getGivenValue()getExpectedType()getContext()
NextPDF\Exception\FontNotFoundExceptionfinal 类getFontName()getSearchPaths()wasFallbackAttempted()getContext()
NextPDF\Exception\SignatureExceptionfinal 类getCertInfo()getSignatureLevel()getDetail()getContext();具名构造方法 ltvCapabilityMissing()tsaRequired()httpClientMissing(),…
NextPDF\Exception\WriterExceptionfinal 类getOutputPath()getWriterState()getContext()
NextPDF\Exception\PageLayoutExceptionfinal 类getPageNumber()getContext()
NextPDF\Exception\NotImplementedExceptionfinal 类$feature$followUp
NextPDF\Exception\Strict\StrictModeViolation抽象继承 NextPdfException
NextPDF\Exception\Strict\IncompatibleRenderingModeExceptionfinal 类继承 StrictModeViolation

完整叶节点列表(23 个):BarcodeEncoderNotFoundExceptionBarcodeExceptionCompressionExceptionContentStreamBalanceExceptionCssParserLimitExceededExceptionCssResolutionBudgetExceededExceptionEncryptionExceptionFontNotFoundExceptionFontParsingExceptionGraphicsStateBalanceExceptionHtmlParsingExceptionImageProcessingExceptionInvalidConfigExceptionLinearizationInvariantExceptionLinearizationUnimplementedExceptionMissingShadingResourceExceptionNotImplementedExceptionPageLayoutExceptionPdfRViolationExceptionSignatureExceptionTemplateExceptionUnsupportedAlgorithmException,以及 WriterException

使用基类类型捕获任何引擎错误。

<?php
declare(strict_types=1);
use NextPDF\Core\Document;
use NextPDF\Exception\NextPdfException;
try {
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'Hello');
$doc->save('out.pdf');
} catch (NextPdfException $e) {
\error_log($e->getMessage());
}

该模式在 examples/15-exception-handling.php 中有实际演示。

在叶节点层级恢复,并将结构化上下文转发到日志管线。

<?php
declare(strict_types=1);
use NextPDF\Contracts\ContextAwareExceptionInterface;
use NextPDF\Core\Document;
use NextPDF\Exception\FontNotFoundException;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
function render(Document $doc, LoggerInterface $logger): void
{
try {
$doc->setFont('Brand-Sans', '', 12);
$doc->cell(0, 10, 'Invoice');
$doc->save('invoice.pdf');
} catch (FontNotFoundException $e) {
// Targeted recovery: fall back to a built-in font.
$logger->warning($e->getMessage(), $e->getContext());
$doc->setFont('helvetica', '', 12);
$doc->save('invoice.pdf');
} catch (NextPdfException $e) {
// Any other engine error: structured context, then rethrow.
$context = $e instanceof ContextAwareExceptionInterface
? $e->getContext()
: [];
$logger->error($e->getMessage(), $context);
throw $e;
}
}
  • 捕获顺序很重要。请把叶节点类放在 NextPdfException 之前。开头就使用 catch (NextPdfException) 会拦截所有子类。
  • getContext() 的键采用 snake_case,值为原始类型或原始类型的列表——没有嵌套对象——因此这份 payload 始终可以安全转换为 JSON。
  • 基类的 NextPdfException::getContext() 会返回 []。未重写该方法的子类不带任何结构化字段。此时请改用 getMessage()
  • NextPdfException 是抽象类——你无法直接将其实例化。请抛出一个具体的叶节点类。
  • NotImplementedException 被刻意设计得很醒目:它代表的是有意未实现的功能,而非一时的暂时性失败。请勿重试它。
  • Strict\* 违规代表的是符合性模式(conformance-mode)的合约违反,而不是可恢复的运行时错误。请把它们当成配置或输入上的缺陷来处理。
  • 没有字符串错误代码常量。请以异常类型来匹配。请把 getContext() 转发给机器端消费者。

构造异常只需分配一个对象,再执行一次用于创建消息的 sprintf;复杂度为 O(1)。getContext() 会返回一个由既有字段组成的小型关联数组,相对于字段数量也是 O(1)。异常属于失败路径,而不是热路径。与触发失败的那项工作相比,这点成本微不足道。本参考页面默认的 performance_budgetwall_ms: 1500peak_mb: 64

上下文字段可能带有来自文档的细节:FontNotFoundException 含有文件系统搜索路径、WriterException 含有输出路径、InvalidConfigException 含有所提供的值。在转发到低信任的日志输出端之前,请先清除上下文键或改用允许列表,因为这些路径和值可能泄漏部署配置或用户输入。异常消息是给人读的,可能包含相同的细节——在涉及安全性的上下文下,请勿把原始消息直接呈现给最终用户。SignatureException 会刻意把具体的根本原因(缺少包、空的 TSA URL)带入消息,让运维人员不必逐一搜索调用位置就能分类处理。这项细节面向运维人员,而不是最终用户。

本模块是引擎的错误模型,不包含任何规范性标准引用。因标准违规而抛出的异常——例如 PdfRViolationExceptionStrict\OracleConformanceFailure——相关规范条款请参考检测该违规的模块,而不是本页。

  • /modules/core/contracts/ —— ContextAwareExceptionInterface 的定义
  • /modules/core/observability/ —— 将 getContext() 转发到 APM
  • /modules/core/config/ —— InvalidConfigExceptionNotImplementedException
  • /modules/core/support/ —— DegradedExceptionWarningCodeNEXTPDF_W_*
  • /modules/core/event/ —— InvalidConfigException,来自 addListener()

词汇表:具上下文感知的异常 · 降级政策