Ga naar inhoud

Fouten afhandelen met de exception-hiërarchie van NextPDF

NextPDF werpt getypeerde exceptions voor uitzonderlijke situaties. Een fout wordt nooit verborgen achter een retourwaarde false of null. Elke domein-exception erft van dezelfde abstracte basis, NextPdfException, en biedt gestructureerde diagnostische context via ContextAwareExceptionInterface. Dit recipe laat zien waar je moet afvangen en hoe je gestructureerde context logt voor een APM-pijplijn. Het laat ook zien welke fouten één allesomvattende catch niet afdekt.

Terminal window
composer require nextpdf/core:^3

Je hebt geen extra extensie nodig.

De hiërarchie is:

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

Deze hiërarchie heeft twee praktische gevolgen, allebei geverifieerd aan de hand van de broncode:

  1. catch (NextPdfException $e) vangt elke exception onder NextPDF\Exception af, inclusief de strict-mode-overtredingen. Ze breiden allemaal de abstracte basis uit.
  2. Het vangt niet alles af wat de bibliotheek kan werpen. NextPDF\Support\DegradedException erft rechtstreeks van RuntimeException, niet van NextPdfException. Een catch (NextPdfException $e) vangt dus een afwijzing door het degradatiebeleid niet af. Om zo’n afwijzing af te handelen, vang je expliciet DegradedException (of het bredere RuntimeException) af. Dit recipe maakt die grens expliciet, in plaats van één allesomvattende catch als volledige dekking te beschouwen.

NextPdfException::getContext() retourneert een array<string, mixed> met snake_case-sleutels en uitsluitend primitieve waarden, of lijsten van primitieve waarden. Je kunt deze rechtstreeks serialiseren naar de context-array van een PSR-3-logger. PSR-3 §1.3 plaatst een exception onder de contextsleutel 'exception'. De implementatie van getContext() in NextPDF voegt domeindetails toe naast die sleutel, niet het exception-object zelf.

Dit API-oppervlak volgt uit de PHPDoc op NextPDF\Exception\NextPdfException, NextPDF\Contracts\ContextAwareExceptionInterface, de concrete domein-exceptions (bijvoorbeeld NextPDF\Exception\FontNotFoundException, met getFontName() / getSearchPaths() / wasFallbackAttempted()), en NextPDF\Support\DegradedException (die de Capability en DegradationPolicy meedraagt). De onderstaande voorbeelden gebruiken NextPdfException::getContext() en de accessors per exception.

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

Het volledige voorbeeld toont gerichte catches, logging met gestructureerde context en de grens van DegradedException. Het houdt ook het uitvoerkanaal van de harness vrij.

<?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 blijft vrij voor de harness; de PDF gaat uitsluitend naar NEXTPDF_COOKBOOK_OUTPUT.

  • Plaats catch-blokken in de volgorde specifiek → algemeen. PHP gebruikt de eerste compatibele catch. Een catch (NextPdfException $e) die vóór catch (FontNotFoundException $e) staat, maakt het specifieke blok tot dode code.
  • DegradedException is geen NextPdfException. Volgens de broncode erft het van RuntimeException. Eén enkele catch (NextPdfException $e) laat een afwijzing door strikte degradatie doorpropageren. Vang die (of RuntimeException) expliciet af wanneer er een degradatiebeleid van toepassing is.
  • getContext() is volgens contract APM-veilig. Sleutels zijn snake_case. Waarden zijn primitieven of lijsten van primitieven, zonder geneste objecten en zonder resources. Je kunt het rechtstreeks serialiseren. Het bevat nooit document-bytes.
  • Parseer geen exception-berichten. Berichten zijn leesbaar voor mensen en kunnen veranderen. Gebruik de getypeerde accessors (getFontName(), capability->id, enzovoort) en getContext() als het stabiele oppervlak voor machinegebruik.
  • Let op met verouderde aantallen. Ouder materiaal noemt mogelijk een vast aantal „N domein-exceptions”. De hiërarchie groeit met elke release. Vertrouw op het basistype NextPdfException en op instanceof, nooit op een hardgecodeerd aantal.
  • PSR-3-placeholders blijven strings. Houd het logbericht een string met {placeholder}-tokens, en plaats de waarden in de context-array (PSR-3 §1.2). Interpoleer het exception-object niet in het bericht.

Exception-afhandeling voegt geen overhead toe in het normale pad. NextPDF werpt alleen voor uitzonderlijke situaties, en getContext() bouwt op aanvraag een kleine array op. Het performance_budget (wall_ms: 2000, peak_mb: 96) begrenst de harness-run voor dit recipe, niet willekeurige documenten.

  • getContext() is ontworpen om logveilig te zijn: uitsluitend primitieven, geen documentpayload en geen bestand-bytes. Je blijft verantwoordelijk voor de waarden die je aan een logcontext toevoegt. Sanitiseer alles wat door de gebruiker is aangeleverd (bijvoorbeeld een bestandspad) volgens je loggingbeleid voordat het een sink bereikt.
  • Toon ruwe exception-berichten niet aan eindgebruikers op een manier die de indeling van het bestandssysteem kan lekken. Toon een generiek bericht en log de gestructureerde context aan de serverzijde.
BeweringSpecificatieClausulereference_id
Een exception hoort in de PSR-3-logcontext onder de sleutel exception.PSR-3§1.3
Logberichten blijven strings; placeholdernamen worden aan contextsleutels gekoppeld.PSR-3§1.2
De wijzigingsdatum wordt bij elke save opnieuw gegenereerd, dus de uitvoer is structureel (niet byte-)stabiel.ISO 32000-2§14.3

Dit recipe is geverifieerd met het structurele reproduceerbaarheidsprofiel. De uitvoer bevat een trailer /ID en een wijzigingsdatum die bij elke save opnieuw worden gegenereerd, dus byte-identiteit is niet haalbaar. De met qpdf genormaliseerde structuur blijft stabiel.