コンテンツにスキップ

機能としてのエラー

Spec: ISO 9241-110, §5.6.4 Evidence: Code-backed

NextPDF は例外階層を API 面として扱い、例外をスローするメソッドそのものと同じ配慮で設計しています。失敗は具体的で、型付けされ、必要な粒度でキャッチでき、ログに使える構造化コンテキストを保持します。

このページでは、その面をエンジン自身のソースコードに基づいて示します。基底型、型付けされたサブクラス、根本原因をメッセージへ結び付ける名前付きコンストラクタ、そしてすべての NextPDF 例外が公開する構造化コンテキストを取り上げます。

エラーメッセージは、最悪のタイミング――本番環境、午前 2 a.m.、そして出荷されるはずだったドキュメント――に、エンジンがあなたへ語りかける手段です。そのときメッセージが何を伝えるかで、次にすることが修正なのか、それとも長い調査なのかが決まります。

汎用的な RuntimeException: something went wrong では、どこにも進めません。エンジンが失敗したことは分かっても、何が、どこで失敗したのかは分からず、まして何をすべきかは決して分かりません。ヒューマンファクターの指針は、この点について率直です。エラーは、それを修正することが明白な次のステップになるよう、調査プロジェクトにしなくても済むだけの説明を自ら備えているべきです( Spec: ISO 9241-110, §5.6.4.3 )。原因と対処を名指しする例外は、あれば嬉しい程度のものではありません。5 分で直せるか、5 時間かかるかの違いです。

  • すべての NextPDF の失敗は、ひとつの抽象基底 NextPdfException を継承するため、ライブラリ内のすべてのエラーを単一の型でキャッチできます。
  • その配下には 具体的で型付けされた サブクラスが並びます――見つからないフォント、無効な構成、失敗した署名操作――そのため、自分が対処できる失敗を正確にキャッチできます。
  • すべての NextPDF 例外は ContextAwareExceptionInterface を実装し、getContext() を公開します。これはログに安全に渡せる構造化マップであり、診断情報を取り出すためにメッセージ文字列を解析する必要は決してありません。
  • メッセージは 実行可能 です。名前付きコンストラクタが、汎用的なテンプレートではなく、実際の根本原因(そしてしばしば修正方法)をメッセージへ結び付けます。
  • 各例外クラスは 誰がそれに対処できるか――開発者、インフラ、あるいはライブラリの呼び出し元――を文書化するため、スタックトレースを読む前にトリアージを始められます。

この階層は浅く、意図的にそう設計されています。ひとつの基底、ドメイン固有型の層、そして全体が従うひとつの契約があります。

ひとつの基底、設計上のキャッチオール。 NextPdfException は抽象クラスであり、RuntimeException を継承し、ContextAwareExceptionInterface を実装します。

abstract class NextPdfException extends RuntimeException implements ContextAwareExceptionInterface
{
/** @return array<string, mixed> */
public function getContext(): array
{
return [];
}
}

抽象であること自体が、ひとつの設計判断です。曖昧な基底を誤ってキャッチすることは決してありません。直接スローされることがないからです。最後の砦としては意図的にキャッチし、具体的にできることがある場合には具体的なサブクラスをキャッチします。

具体的で型付けされたサブクラス。 見つからないフォントは汎用エラーではなく、FontNotFoundException であり、対処に必要なデータを保持します。

final class FontNotFoundException extends NextPdfException
{
public function __construct(
private readonly string $fontName,
private readonly array $searchPaths,
private readonly bool $fallbackAttempted,
?Throwable $previous = null,
) {
parent::__construct(
\sprintf('Font "%s" not found. Searched: [%s].', $fontName, \implode(', ', $searchPaths)),
0,
$previous,
);
}
// getFontName(), getSearchPaths(), wasFallbackAttempted(), getContext()
}

メッセージはフォント名と、検索された正確なパスを名指しします。どのディレクトリが欠けていたかを推測することはありません。例外がそれを教えてくれます。

文字列スクレイピングではなく、構造化されたコンテキスト。 すべての例外は、ログや APM ペイロードへそのままシリアライズしても安全な、snake_case のプリミティブのみで構成されたマップを返します。

public function getContext(): array
{
return [
'config_key' => $this->configKey,
'given_value' => $this->givenValue,
'expected_type' => $this->expectedType,
];
}

契約は、その理由を明示しています。ロギングミドルウェアは、メッセージを解析せずに、任意の NextPDF 例外に対して $logger->error($e->getMessage(), $e->getContext()) を呼び出せます。メッセージは人間のため、コンテキストは機械のためです。どちらかが他方の役割を兼ねる必要はありません。

名前付きコンストラクタによる実行可能なメッセージ。 ここでエラーは偶発的なものではなく、設計されたものになります。SignatureException は、単に「レベル B-LT で署名に失敗した」と言うだけにとどまりません。実際の根本原因と、多くの場合は正確な対処も、メッセージへ結び付ける名前付きコンストラクタを提供します。

public static function tsaUrlEmpty(string $signatureLevel): self
{
return new self('', $signatureLevel, null,
'TSA endpoint URL is empty: pass a non-empty `tsaUrl` to the TsaClient '
. 'constructor (e.g. "https://timestamp.example.com/tsa") or remove the '
. 'TSA client wiring if no timestamping is required at this signature level');
}

メッセージは、何が間違っているか そしてそれについて何をすべきか を述べます。欠落している機能パッケージ、存在しない HTTP クライアント、誤って選ばれたダイジェスト専用アルゴリズム、アルゴリズムと一致しない鍵の種類など、対応するコンストラクタが用意されています。そのひとつひとつが、ある種類の失敗を、開発者がエンジンのソースを読まずに対処できる一文へ変えます。

意図的に騒がしい失敗。 いくつかの例外は、まさに静かな欠落を騒がしいものにするために存在します。 NotImplementedException は、機械で grep 可能な feature ラベルと followUp 参照を保持します。

final class NotImplementedException extends NextPdfException
{
public function __construct(
public readonly string $feature,
public readonly string $followUp,
?Throwable $previous = null,
) {
parent::__construct(
\sprintf('%s is not implemented in this release. %s', $feature, $followUp),
0, $previous,
);
}
}

到達可能だが未配線のパスは、もっともらしい no-op を返す代わりに、これをスローします。同じ考えが StrictModeViolation にも反映されており、そのサブクラスは、逸脱した構成要素を示す短い grep 可能なラベルに加え、任意の位置情報と引用コンテキストを保持します。仕様からの逸脱は、静かに誤ったレンダリングではなく、型付けされた文脈付きの停止になります。

クラス自体に含まれるトリアージメタデータ。 各例外クラスは、その docblock で 誰がそれに対処できるか を名指しします。たとえば、FontNotFoundException は「開発者(フォントパスを確認)またはインフラ(ファイルのパーミッションを修正)」です。InvalidConfigException は「開発者(NextPDF を呼び出す前に構成を修正)」です。NotImplementedException は「ライブラリの呼び出し元――呼び出しを削除するか、将来のリリースに固定する」です。トリアージはスタックトレースより前に始まります。「これは自分の問題か、それとも運用側の問題か?」という問いに、すでに書き留められた答えがあるからです。

次の表は、この設計と、各プロパティがもたらすものをまとめたものです。

設計プロパティソース内もたらすもの
ひとつの抽象基底NextPdfException(抽象、コンテキストインターフェースを実装)すべてのライブラリエラーを単一の型でキャッチ。曖昧な基底の誤キャッチなし
具体的で型付けされたサブクラスFontNotFoundExceptionInvalidConfigExceptionSignatureException、…対処できる失敗の正確なキャッチ
構造化されたコンテキストgetContext() ―― snake_case のプリミティブのみメッセージ文字列を解析しないログ記録または APM 送信
実行可能なメッセージ名前付きコンストラクタが根本原因と対処を結び付けるテンプレートではなく、対処できる一文
意図的に騒がしいNotImplementedExceptionStrictModeViolation静かな欠落から、型付けされた grep 可能な停止へ
トリアージメタデータ各クラスの docblock 内の「Actionable by:」トレースを読む前の、誰の問題かの把握

このページは Evidence: Code-backed です。すべてのクラス、シグネチャ、メッセージの形は、言い換えではなく、エンジンの例外名前空間から直接引いています。

  • 抽象基底とその ContextAwareExceptionInterface 契約、型付けされたサブクラス、getContext() の形、そして SignatureException の名前付きコンストラクタは、ソースからそのまま引用されています。
  • 「Actionable by:」のトリアージ行は、それらと同じファイル内にあるクラス docblock の契約です。
  • ヒューマンファクターのアンカーは Spec: ISO 9241-110 です――§5.6.4.3、修正できる程度に自らを説明するエラーについて、そして §6 の使用エラーに対する堅牢性の原則です。エンジンは、開発者をユーザーとして、例外をそれらの条項を満たさなければならないインターフェースとして扱います。

最後の砦として広くキャッチし、対処できる箇所では具体的にキャッチし、構造化コンテキストをそのままロガーに渡します――メッセージ解析は不要です。

<?php
declare(strict_types=1);
use NextPDF\Core\Document;
use NextPDF\Exception\FontNotFoundException;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
function renderInvoice(LoggerInterface $logger): ?string
{
try {
$document = Document::createStandalone();
$document->setTitle('Invoice 2026-0042');
$document->addPage();
$document->setFont('BrandSans', '', 12);
$document->cell(0, 10, 'Thank you for your business.', newLine: true);
return $document->getPdfData();
} catch (FontNotFoundException $e) {
// Specific: we can recover — fall back to a built-in font.
// getContext() is log-safe structured data, not a parsed string.
$logger->warning($e->getMessage(), $e->getContext());
return null; // caller re-renders with 'helvetica'
} catch (NextPdfException $e) {
// Backstop: any other NextPDF failure, still with structured context.
$logger->error($e->getMessage(), $e->getContext());
return null;
}
}

具体的な catch が回復できるのは、例外の型が、その失敗は回復可能だと伝えているからです。最後の砦は、それ以外のすべてについて構造化コンテキストをログに記録します。何が起きたかを知るために、アプリケーションがメッセージを読むことは一切ありません。

よくある誤読は、深い例外ツリーは過剰設計であり、ひとつのエラー型のほうが単純だろう、というものです。それはエンジンにとっては単純になりますが、あなたにとっては状況が悪くなります。ひとつの型は、すべての失敗が汎用的なスタックトレースであり、回復ロジックが文字列マッチであることを意味します。そのマッチは脆く、次にメッセージが言い換えられただけで壊れます。小さく具体的な階層は、その知識を型システムへ移し、コンパイラとあなたの catch ブロックがそれを利用できるようにします。

第二の誤解は、メッセージとコンテキストは冗長だ、というものです。そうではありません。メッセージは、ログ行を読む人間のための散文です。コンテキストは、コードのルーティング、アラート、ダッシュボードのための型付けされたマップです。両者を混同することは、まさに getContext() 契約が取り除くために存在する、文字列解析の罠です。

この階層は意図的に浅くなっています。NextPDF は、考えうるあらゆる失敗に対して個別の例外クラスを作成することはしません。呼び出し元が その 失敗を具体的にキャッチするのが自然な場合に、ひとつ作成します。過剰な分割は、文字列解析の問題を、肥大化した catch リストの問題に置き換えてしまうでしょう。

getContext() はログと APM のために構造化されているため、契約として、ネストされたオブジェクトを含まず、プリミティブとプリミティブのリストのみを返します。それは診断コンテキストであり、エンジン内部のシリアライズされたスナップショットではありません。また、外部スキーマを構築するための安定したワイヤフォーマットでもありません。

このページは例外の 設計面 を説明します。例外の正確な集合とそのフィールドは、エンジンとともに進化します。ここで引用されているクラスと形は、このレビュー時点で最新のものであり、凍結されたカタログではなく、契約を例示するものです。契約――ひとつの基底、型付けされたサブクラス、構造化コンテキスト、実行可能なメッセージ――が安定した部分です。

  • 推測を拒む API ―― そもそもこれらの例外をスローするフェイルファストガード。
  • NextPDF の設計哲学 ―― なぜ「エラーは API 面である」が第一級の原則なのか。
  • パイプラインモデル ―― ドキュメントがエンジンを通過する際、これらの失敗がどこで表面化し、どのように観測されるか。
  • コードに裏付けられた(エビデンスレベル) ―― その主張がエンジン自身のソースに対して検証され、言い換えではなく引用されたページ。
  • コンテキスト対応例外 ―― NextPDF の例外で、ContextAwareExceptionInterface を実装し、getContext() を公開するもの。そのメソッドは、メッセージ文字列を解析することなくログや APM ペイロードへシリアライズしても安全な、プリミティブな診断フィールドを持つ snake_case マップを返します。
  • 名前付きコンストラクタ ―― 特定の根本原因、そしてしばしばその対処に結び付けられたメッセージを持つ例外を構築する静的ファクトリメソッド(たとえば SignatureException::tsaUrlEmpty())。
  • PAdES ―― PDF Advanced Electronic Signatures。PDF 署名のための ETSI プロファイルファミリ。初出時に正式名称を示します。署名関連ページで詳しく扱います。
  • TSA ―― Time-Stamping Authority。より高位の PAdES プロファイルで使用される RFC 3161 タイムスタンプを発行する信頼されたサービス。