Przejdź do głównej zawartości

Obsługa błędów z uwzględnieniem hierarchii wyjątków NextPDF

NextPDF zgłasza typowane wyjątki w stanach wyjątkowych. Nigdy nie ukrywa błędu w zwracanej wartości false ani null. Każdy wyjątek domenowy rozszerza tę samą abstrakcyjną klasę bazową NextPdfException i udostępnia ustrukturyzowany kontekst diagnostyczny przez ContextAwareExceptionInterface. Ten przepis pokazuje, gdzie przechwytywać wyjątki i jak rejestrować ustrukturyzowany kontekst na potrzeby potoku APM. Pokazuje też, których awarii pojedyncza uniwersalna klauzula przechwytująca nie obejmuje.

Okno terminala
composer require nextpdf/core:^3

Nie jest wymagane żadne dodatkowe rozszerzenie.

Hierarchia wygląda następująco:

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

Ta hierarchia ma dwie praktyczne konsekwencje, obie zweryfikowane na podstawie kodu źródłowego:

  1. catch (NextPdfException $e) przechwytuje każdy wyjątek w przestrzeni NextPDF\Exception, łącznie z naruszeniami trybu ścisłego. Wszystkie rozszerzają abstrakcyjną klasę bazową.
  2. Nie przechwytuje wszystkich wyjątków, które biblioteka może zgłosić. NextPDF\Support\DegradedException rozszerza klasę RuntimeException bezpośrednio, a nie NextPdfException. Dlatego catch (NextPdfException $e) nie przechwytuje odrzucenia wynikającego z zasady degradacji. Aby je obsłużyć, przechwyć jawnie DegradedException (lub szerszy RuntimeException). Ten przepis wyraźnie wskazuje tę granicę, zamiast traktować jedną uniwersalną klauzulę przechwytującą jako pełne pokrycie.

NextPdfException::getContext() zwraca array<string, mixed> z kluczami w notacji snake_case oraz wartościami będącymi wyłącznie prymitywami lub listami prymitywów. Możesz ją bezpośrednio serializować jako tablicę kontekstu rejestratora zgodnego z PSR-3. Zgodnie z PSR-3 §1.3 wyjątek umieszcza się pod kluczem kontekstu 'exception'. Metoda getContext() w NextPDF dodaje obok tego klucza szczegóły domenowe, a nie sam obiekt wyjątku.

Ta powierzchnia API wynika z PHPDoc klas NextPDF\Exception\NextPdfException, NextPDF\Contracts\ContextAwareExceptionInterface, konkretnych wyjątków domenowych (na przykład NextPDF\Exception\FontNotFoundException, z metodami getFontName() / getSearchPaths() / wasFallbackAttempted()) oraz NextPDF\Support\DegradedException (która przechowuje Capability oraz DegradationPolicy). Poniższe przykłady używają NextPdfException::getContext() oraz akcesorów poszczególnych wyjątków.

<?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());
}

Kompletny przykład pokazuje szczegółowe klauzule przechwytujące, rejestrowanie ustrukturyzowanego kontekstu oraz granicę DegradedException. Zachowuje także kanał wyjściowy dla środowiska testowego.

<?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 pozostaje nieużywany przez środowisko testowe; plik PDF trafia wyłącznie do NEXTPDF_COOKBOOK_OUTPUT.

  • Porządkuj bloki catch od szczegółowych do ogólnych. PHP dopasowuje pierwszą zgodną klauzulę catch. Klauzula catch (NextPdfException $e) umieszczona przed catch (FontNotFoundException $e) sprawia, że blok szczegółowy staje się martwym kodem.
  • DegradedException nie jest NextPdfException. Zgodnie z weryfikacją kodu źródłowego DegradedException rozszerza RuntimeException. Pojedyncza klauzula catch (NextPdfException $e) pozwala, aby odrzucenie wynikające ze ścisłej degradacji rozpropagowało się dalej. Przechwyć je jawnie (lub przechwyć RuntimeException), gdy obowiązuje zasada degradacji.
  • Metoda getContext() jest z założenia bezpieczna dla APM. Klucze są w notacji snake_case. Wartości są prymitywami lub listami prymitywów, bez zagnieżdżonych obiektów i bez zasobów. Możesz ją serializować bezpośrednio. Nigdy nie zawiera bajtów dokumentu.
  • Nie parsuj komunikatów wyjątków. Komunikaty są czytelne dla człowieka i mogą się zmieniać. Jako stabilnej powierzchni maszynowej używaj typowanych akcesorów (getFontName(), capability->id i tak dalej) oraz getContext().
  • Nie polegaj na nieaktualnej liczbie. Starsze materiały mogą podawać stałą liczbę „N wyjątków domenowych”. Hierarchia rozrasta się wraz z kolejnymi wydaniami. Polegaj na typie bazowym NextPdfException i operatorze instanceof, nigdy na zakodowanej na stałe liczbie.
  • Komunikaty z symbolami zastępczymi PSR-3 pozostają ciągami znaków. Podczas rejestrowania pozostaw komunikat jako ciąg znaków z tokenami {placeholder}, a wartości umieść w tablicy kontekstu (PSR-3 §1.2). Nie interpoluj obiektu wyjątku do komunikatu.

Obsługa wyjątków nie dodaje żadnego narzutu w stanie ustalonym. NextPDF zgłasza wyjątki wyłącznie w stanach wyjątkowych, a getContext() buduje niewielką tablicę na żądanie. Limit performance_budget (wall_ms: 2000, peak_mb: 96) dotyczy przebiegu środowiska testowego dla tego przepisu, a nie dowolnych dokumentów.

  • Metoda getContext() została zaprojektowana tak, aby była bezpieczna do rejestrowania: wyłącznie prymitywy, bez zawartości dokumentu i bez bajtów pliku. Nadal odpowiadasz za wartości, które dodajesz do kontekstu dziennika. Oczyść wszystko, co pochodzi od użytkownika (na przykład ścieżkę pliku), zgodnie ze swoją zasadą rejestrowania, zanim trafi do odbiorcy.
  • Nie wyświetlaj użytkownikom końcowym surowych komunikatów wyjątków w sposób ujawniający układ systemu plików. Pokaż ogólny komunikat, a ustrukturyzowany kontekst rejestruj po stronie serwera.
StwierdzenieSpecyfikacjaKlauzulareference_id
Wyjątek należy umieścić w kontekście dziennika PSR-3 pod kluczem exception.PSR-3§1.3
Komunikaty dziennika pozostają ciągami znaków; nazwy symboli zastępczych są odwzorowywane na klucze kontekstu.PSR-3§1.2
Data modyfikacji jest generowana ponownie przy każdym zapisie, więc wynik jest stabilny strukturalnie (a nie bajtowo).ISO 32000-2§14.3

Ten przepis jest weryfikowany przy użyciu strukturalnego profilu odtwarzalności. Wynik zawiera w trailerze /ID oraz datę modyfikacji, które są generowane ponownie przy każdym zapisie, więc nie da się osiągnąć identyczności bajtowej. Struktura znormalizowana przez qpdf pozostaje stabilna.