NextPDF 예외 계층 구조로 오류 처리하기
한눈에 보기
섹션 제목: “한눈에 보기”NextPDF는 예외 상황에 대해 타입이 지정된 예외를 던집니다. 오류를 false 또는 null 반환값으로 숨기지 않습니다. 모든 도메인 예외는 하나의 추상 기반 클래스인 NextPdfException을 확장하며, ContextAwareExceptionInterface를 통해 구조화된 진단 컨텍스트를 노출합니다. 이 레시피는 적절한 범위에서 예외를 포착하는 방법과 애플리케이션 성능 모니터링(APM) 파이프라인에 맞게 구조화된 컨텍스트를 로깅하는 방법을 보여 줍니다. 또한 단일 포괄 catch가 처리하지 않는 실패도 짚어 줍니다.
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이 계층 구조에서 도출되는 실무상 결과는 두 가지이며, 둘 다 소스 코드로 검증되었습니다.
catch (NextPdfException $e)는 strict 모드 위반을 포함하여NextPDF\Exception아래의 모든 예외를 포착합니다. 이들은 모두 추상 기반 클래스를 확장합니다.- 그렇다고 라이브러리가 던질 수 있는 모든 예외를 포착하는 것은 아닙니다.
NextPDF\Support\DegradedException은RuntimeException을 직접 확장하며,NextPdfException은 확장하지 않습니다. 따라서catch (NextPdfException $e)는 다운그레이드 정책 거부를 포착하지 않습니다. 이를 처리하려면DegradedException(또는 더 넓은 범위의RuntimeException)을 명시적으로 포착하세요. 이 레시피는 단일 포괄 catch가 모든 것을 처리한다고 가정하지 않고, 그 경계를 명확히 설명합니다.
NextPdfException::getContext()는 snake_case 키와 기본형(또는 기본형의 리스트) 값만 담은 array<string, mixed>를 반환하므로, PSR-3 로거의 컨텍스트 배열로 바로 직렬화해도 안전합니다. PSR-3 §1.3은 예외를 'exception' 컨텍스트 키 아래에 둡니다. NextPDF의 getContext()는 예외 객체 자체가 아니라 도메인 세부 정보를 그 옆에 추가합니다.
API 표면
섹션 제목: “API 표면”이 API 표면은 NextPDF\Exception\NextPdfException, NextPDF\Contracts\ContextAwareExceptionInterface, 구체적인 도메인 예외(예: NextPDF\Exception\FontNotFoundException, getFontName() / getSearchPaths() / wasFallbackAttempted() 포함), 그리고 NextPDF\Support\DegradedException(Capability와 DegradationPolicy를 담고 있음)의 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)앞에 두면 구체적인 블록이 데드 코드가 됩니다. DegradedException은NextPdfException이 아닙니다. 소스 코드로 검증한 결과, 이 예외는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_budget(wall_ms: 2000, peak_mb: 96)은 임의의 문서가 아니라 이 레시피의 하니스 실행을 한정합니다.
보안 참고 사항
섹션 제목: “보안 참고 사항”getContext()는 로그에 안전하게 사용할 수 있도록 설계되었습니다. 기본형만 담으며, 문서 페이로드나 파일 바이트는 담지 않습니다. 다만 로그 컨텍스트에 사용자가 추가하는 값은 여전히 사용자 책임입니다. 사용자가 제공한 값(예: 파일 경로)은 싱크에 도달하기 전에 로깅 정책에 따라 정제하세요.- 파일 시스템 레이아웃을 노출하는 방식으로 원본 예외 메시지를 최종 사용자에게 그대로 출력하지 마세요. 일반 메시지를 표시하고 구조화된 컨텍스트는 서버 측에서 로깅하세요.
적합성
섹션 제목: “적합성”| 진술 | 규격 | 조항 | reference_id (참조 ID) |
|---|---|---|---|
예외는 PSR-3 로그 컨텍스트에서 exception 키 아래에 둡니다. | PSR-3 | §1.3 | |
| 로그 메시지는 문자열로 유지되며, 플레이스홀더 이름은 컨텍스트 키에 매핑됩니다. | PSR-3 | §1.2 | |
| 수정 날짜는 저장할 때마다 재생성되므로, 출력은 바이트 단위가 아니라 구조적으로 안정적입니다. | ISO 32000-2 | §14.3 |
이 레시피는 구조적 재현성 프로파일로 검증되었습니다. 출력에는 저장할 때마다 재생성되는 트레일러 /ID와 수정 날짜가 포함되므로, 바이트 단위의 동일성은 달성할 수 없습니다. qpdf로 정규화된 구조는 안정적입니다.