コンテンツにスキップ

NextPDF の例外階層でエラーを処理する

NextPDF は、例外的な状態に対して型付きの例外をスローします。エラーを falsenull の戻り値の背後に隠すことは決してありません。すべてのドメイン例外は、単一の抽象基底クラス NextPdfException を継承し、ContextAwareExceptionInterface を通じて構造化された診断コンテキストを公開します。このレシピでは、適切な粒度でキャッチする方法と、アプリケーションパフォーマンス監視(APM)パイプライン向けに構造化コンテキストをログへ記録する方法を示します。あわせて、単一の包括的なキャッチがどの失敗をカバーしないかも明確にします。

Terminal window
composer require nextpdf/core:^3

追加の拡張機能は必要ありません。

階層は次のとおりです。

RuntimeException
└── NextPdfException (abstract, implements ContextAwareExceptionInterface)
├── InvalidConfigException
├── FontNotFoundException
├── FontParsingException
├── ImageProcessingException
├── WriterException
├── SignatureException
├── EncryptionException
├── HtmlParsingException
├── … (every domain exception under NextPDF\Exception)
└── Strict\StrictModeViolation (abstract)
├── Strict\IncompatibleRenderingModeException
└── Strict\OracleConformanceFailure

この階層から、いずれもソースで検証済みの 2 つの実用的な結論が導けます。

  1. catch (NextPdfException $e) は、strict モード違反を含め、NextPDF\Exception 配下のすべての例外をキャッチします。 これらはすべて抽象基底クラスを継承しています。
  2. ただし、ライブラリがスローし得るものすべてをキャッチするわけではありません。 NextPDF\Support\DegradedException は、RuntimeException直接継承しており、NextPdfException は継承していません。そのため、catch (NextPdfException $e) は、デグラデーションポリシーによる拒否をキャッチしません。これを処理するには、DegradedException(またはより広範な RuntimeException)を明示的にキャッチします。このレシピでは、単一の包括的なキャッチがすべてをカバーするかのように扱わず、その境界を明確に示します。

NextPdfException::getContext() は、snake_case のキーと、プリミティブ(またはプリミティブのリスト)のみを値に持つ array<string, mixed> を返します。そのため、PSR-3 ロガーのコンテキスト配列へそのままシリアライズしても安全です。PSR-3 §1.3 では、例外を 'exception' コンテキストキーの下に置きます。NextPDF の getContext() は、例外オブジェクト自体ではなく、それに並ぶドメイン詳細を追加します。

この API サーフェスは、NextPDF\Exception\NextPdfExceptionNextPDF\Contracts\ContextAwareExceptionInterface、具体的なドメイン例外(たとえば NextPDF\Exception\FontNotFoundExceptiongetFontName() / getSearchPaths() / wasFallbackAttempted() を備えます)、および NextPDF\Support\DegradedExceptionCapabilityDegradationPolicy を保持します)の PHPDoc から生成されています。以下で使うメンバーは、NextPdfException::getContext() と各例外固有のアクセサーです。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
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(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');
} catch (NextPdfException $e) {
// Every NextPDF\Exception\* (and strict-mode violation) lands here.
// $e->getContext() is APM-safe structured detail.
error_log($e->getMessage());
}

完全な例では、粒度の細かいキャッチ、構造化コンテキストのログ記録、そして DegradedException の境界を示します。ハーネスの出力チャネルを尊重します。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Contracts\ContextAwareExceptionInterface;
use NextPDF\Exception\FontNotFoundException;
use NextPDF\Exception\NextPdfException;
use NextPDF\Support\DegradedException;
/**
* A minimal PSR-3-shaped sink. In production this is your real logger;
* the exception goes under the 'exception' key (PSR-3 §1.3) and the
* NextPDF structured context is merged in as domain detail.
*
* @param array<string, mixed> $context
*/
function logError(string $message, array $context): void
{
fwrite(STDERR, $message . ' ' . json_encode($context, JSON_THROW_ON_ERROR) . "\n");
}
$doc = Document::createStandalone();
$doc->setTitle('Exception handling patterns');
try {
$doc->addPage();
$doc->setFont('helvetica', 'B', 16);
$doc->cell(0, 12, 'Exception-aware error handling', newLine: true);
// This call succeeds; the catch blocks below show the SHAPE of handling.
$doc->setFont('helvetica', '', 11);
$doc->cell(0, 8, 'Catch specifically, then fall back to the base.', newLine: true);
} catch (FontNotFoundException $e) {
// Most specific first: actionable, typed accessors.
logError('Font missing — using a fallback face', [
'exception' => $e::class,
'font_name' => $e->getFontName(),
'searched' => $e->getSearchPaths(),
'fallback' => $e->wasFallbackAttempted(),
]);
} catch (NextPdfException $e) {
// Catch-all for every NextPDF\Exception\* including strict violations.
$context = ['exception' => $e::class];
if ($e instanceof ContextAwareExceptionInterface) {
$context += $e->getContext();
}
logError($e->getMessage(), $context);
} catch (DegradedException $e) {
// BOUNDARY: DegradedException extends RuntimeException directly, NOT
// NextPdfException. The catch above would NOT have caught it. This
// explicit block (or a broader RuntimeException) is required.
logError('Capability degraded under the active policy', [
'exception' => $e::class,
'capability' => $e->capability->id,
'policy' => $e->policy->value,
]);
}
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');
fwrite(STDERR, "Document built; handlers wired.\n");

STDOUT はハーネス用に空けておきます。PDF は NEXTPDF_COOKBOOK_OUTPUT にのみ出力されます。

  • catch ブロックは具体的なものから一般的なものへと並べます。 PHP は最初に一致した catch にマッチします。catch (NextPdfException $e)catch (FontNotFoundException $e) の前に置くと、具体的なブロックがデッドコードになります。
  • DegradedExceptionNextPdfException ではありません。 ソースで検証済みのとおり、これは RuntimeException を継承しています。単一の catch (NextPdfException $e) では、strict デグラデーションによる拒否を黙って伝播させてしまいます。デグラデーションポリシーが関わる場合は、これ(または RuntimeException)を明示的にキャッチします。
  • getContext() は契約上 APM セーフです。 キーは snake_case です。値はプリミティブまたはプリミティブのリストであり、ネストされたオブジェクトやリソースは含まれません。そのまま直接シリアライズでき、ドキュメントのバイトを含むことは決してありません。
  • 例外メッセージをパースしないでください。 メッセージは人間が読むためのものであり、変更される可能性があります。型付きアクセサー(getFontName()capability->id など)と getContext() が、安定したマシン向けのサーフェスです。
  • 古い件数に関する注意。 古い資料では「N 個のドメイン例外」という固定の数が引用されている場合があります。この階層はリリースを重ねるごとに増えていきます。ハードコードされた件数ではなく、NextPdfException 基底型と instanceof に頼ってください。
  • PSR-3 のプレースホルダーは文字列のままにします。 ログに記録する際は、メッセージを {placeholder} トークンを含む文字列のままにし、値はコンテキスト配列に入れます(PSR-3 §1.2)。例外オブジェクトをメッセージに埋め込まないでください。

例外処理は定常状態のコストを追加しません。NextPDF は例外的な状態のときにのみスローし、getContext() は必要に応じて小さな配列を構築します。performance_budgetwall_ms: 2000peak_mb: 96)は、任意のドキュメントではなく、このレシピのハーネス実行を対象とした上限です。

  • getContext() は、ログセーフになるよう設計されています。プリミティブのみで、ドキュメントのペイロードやファイルのバイトは含みません。ログコンテキストに追加する値については、引き続き利用者の責任となります。ユーザー由来のもの(たとえばファイルパス)は、シンクに到達する前に、ログポリシーに従ってスクラブしてください。
  • ファイルシステムの構成が漏えいするような形で、生の例外メッセージをエンドユーザーに表示しないでください。汎用的なメッセージを表示し、構造化されたコンテキストはサーバー側でログに記録します。
記述仕様箇条リファレンス ID
例外は、PSR-3 のログコンテキストで exception キーの下に配置。PSR-3§1.3
ログメッセージは文字列のまま保持され、プレースホルダー名はコンテキストキーに対応。PSR-3§1.2
変更日時は保存のたびに再生成されるため、出力はバイト単位ではなく構造的に安定。ISO 32000-2§14.3

このレシピは、構造的な再現性プロファイルで検証されています。出力には、保存のたびに再生成されるトレーラーの /ID と変更日時が含まれるため、バイト単位の同一性は達成できません。qpdf で正規化された構造は安定しています。