Fouten als feature
Spec: ISO 9241-110, §5.6.4 ISO 9241-110 §5.6.4 Evidence: Code-backed
In één oogopslag
Sectie met titel “In één oogopslag”NextPDF behandelt zijn exceptiehiërarchie als een API-oppervlak, ontworpen met dezelfde zorg als de methoden die de exceptie opwerpen. Een fout is specifiek, getypeerd, af te vangen op het detailniveau dat u nodig hebt en draagt gestructureerde context mee voor uw logs.
Deze pagina laat dat oppervlak zien in de broncode van de engine zelf: het basistype, de getypeerde subklassen, de named constructors die een onderliggende oorzaak aan het bericht koppelen en de gestructureerde context die elke NextPDF-exceptie blootlegt.
Waarom dit belangrijk is
Sectie met titel “Waarom dit belangrijk is”Een foutmelding is de engine die tegen u spreekt op het slechtst denkbare moment: in productie, om 2 a.m., met een document dat al had moeten worden verstuurd. Wat dat bericht dan zegt, bepaalt of de volgende stap een oplossing is of een langdurig onderzoek.
Een generieke RuntimeException: something went wrong biedt u geen enkel aanknopingspunt. Die vertelt u dat de engine is mislukt, maar niet wat er is mislukt, niet waar en al helemaal niet wat u moet doen. Richtlijnen op het gebied van human factors zijn hier duidelijk over. Een fout moet zichzelf zo goed verklaren dat oplossen de voor de hand liggende volgende stap is en geen onderzoeksproject ( Spec: ISO 9241-110, §5.6.4.3 ISO 9241-110 §5.6.4.3 ). Een exceptie die de oorzaak en de oplossing benoemt, is geen luxe. Het is het verschil tussen een oplossing van vijf minuten en een van vijf uur.
De korte versie
Sectie met titel “De korte versie”- Elke NextPDF-fout erft van één abstracte basis,
NextPdfException, zodat u alle bibliotheekfouten met één enkel type kunt opvangen. - Daaronder zitten specifieke, getypeerde subklassen — een lettertype dat niet gevonden kan worden, een ongeldige configuratie, een mislukte handtekeningbewerking — zodat u precies de fout kunt opvangen die u kunt afhandelen.
- Elke NextPDF-exceptie implementeert
ContextAwareExceptionInterfaceen biedtgetContext(): een gestructureerde, logveilige map, zodat u nooit een berichttekst hoeft te parseren om diagnostische gegevens te achterhalen. - Berichten zijn actiegericht: named constructors koppelen de werkelijke onderliggende oorzaak (en vaak de oplossing) aan het bericht, in plaats van een generieke sjabloon.
- Elke exceptieklasse documenteert wie er actie op kan ondernemen — ontwikkelaar, infrastructuur of bibliotheekaanroeper — zodat triage al begint voordat u de stacktrace leest.
Hoe NextPDF dit aanpakt
Sectie met titel “Hoe NextPDF dit aanpakt”De hiërarchie is ondiep en doordacht. Er is één basis, een laag domeinspecifieke typen en een contract dat ze allemaal volgen.
Eén basis, catch-all by design. NextPdfException is abstract, breidt RuntimeException uit en implementeert ContextAwareExceptionInterface:
abstract class NextPdfException extends RuntimeException implements ContextAwareExceptionInterface{ /** @return array<string, mixed> */ public function getContext(): array { return []; }}Abstract is een bewuste keuze. U krijgt de algemene basis nooit per ongeluk te verwerken, omdat deze nooit rechtstreeks wordt opgeworpen. U vangt deze bewust op als vangnet en vangt een specifieke subklasse op wanneer u iets specifieks kunt doen.
Specifieke, getypeerde subklassen. Een ontbrekend lettertype is geen generieke fout; het is FontNotFoundException en draagt de gegevens mee die u nodig hebt om te handelen:
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()}Het bericht noemt het lettertype en de exacte paden waarin is gezocht. U hoeft niet te raden welke map ontbrak; de exceptie vertelt het u.
Gestructureerde context, geen string-scraping. Elke exceptie retourneert een snake_case-map met uitsluitend primitieven, die veilig rechtstreeks in een log of APM-payload kan worden geserialiseerd:
public function getContext(): array{ return [ 'config_key' => $this->configKey, 'given_value' => $this->givenValue, 'expected_type' => $this->expectedType, ];}Het contract maakt het doel expliciet. Een logging-middleware kan $logger->error($e->getMessage(), $e->getContext()) aanroepen voor elke NextPDF-exceptie zonder het bericht ooit te parseren. Het bericht is voor mensen. De context is voor machines. Geen van beide hoeft de rol van de ander te vervullen.
Actiegerichte berichten via named constructors. Hier zijn fouten niet langer toevallig: ze worden ontworpen. SignatureException zegt niet alleen “ondertekenen mislukt op niveau B-LT”. Het biedt named constructors die de werkelijke onderliggende oorzaak, en vaak de exacte oplossing, aan het bericht koppelen:
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');}Het bericht vermeldt wat er mis is en wat u eraan kunt doen. Er zijn verwante constructors voor een ontbrekend capability-pakket, een ontbrekende HTTP-client, een per ongeluk gekozen digest-only-algoritme, een sleuteltype dat niet bij het algoritme past, en meer. Elk daarvan maakt van een foutcategorie een zin waar een ontwikkelaar iets mee kan zonder de broncode van de engine te lezen.
Fouten die opzettelijk luidruchtig zijn. Sommige excepties bestaan juist zodat een stille leemte luidruchtig wordt. NotImplementedException draagt een machinaal te greppen feature-label en een followUp-verwijzing mee:
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, ); }}Wanneer een pad wordt bereikt maar nog niet is aangesloten, werpt het deze op in plaats van een plausibele no-op terug te geven. Hetzelfde idee ligt ten grondslag aan StrictModeViolation, waarvan de subklassen een kort, grepbaar label voor de afwijkende constructie meedragen, plus optionele locatie- en citaatcontext. Een afwijking van de specificatie wordt een getypeerde, contextuele stop, geen stilletjes foutieve weergave.
Triagemetadata in de klasse zelf. Elke exceptieklasse vermeldt in de eigen docblock wie er actie op kan ondernemen. Zo is FontNotFoundException “Developer (verify font path) or Infrastructure (fix file permissions)”. InvalidConfigException is “Developer (fix configuration before calling NextPDF)”. NotImplementedException is “Library callers — either remove the call or pin to a future release”. Triage begint al vóór de stacktrace, omdat de vraag “is dit mijn probleem of dat van operations?” al een vastgelegd antwoord heeft.
De tabel vat het ontwerp samen en laat zien wat elke eigenschap u oplevert.
| Ontwerpeigenschap | In de broncode | Wat het u oplevert |
|---|---|---|
| Eén abstracte basis | NextPdfException (abstract, implementeert de context-interface) | Vang elke bibliotheekfout op met één type, nooit per ongeluk de algemene basis |
| Specifieke getypeerde subklassen | FontNotFoundException, InvalidConfigException, SignatureException, … | Vang precies de fout op die u kunt afhandelen |
| Gestructureerde context | getContext() — uitsluitend snake_case-primitieven | Loggen of naar APM sturen zonder berichttekst te parseren |
| Actiegerichte berichten | Named constructors koppelen onderliggende oorzaak + oplossing | Een zin waarop u kunt handelen, geen sjabloon |
| Opzettelijk luidruchtig | NotImplementedException, StrictModeViolation | Een stille leemte wordt een getypeerde, grepbare stop |
| Triagemetadata | “Actionable by:” in elke klasse-docblock | Weet wiens probleem het is voordat u de trace leest |
Wat het bewijs zegt
Sectie met titel “Wat het bewijs zegt”Deze pagina is Evidence: Code-backed : elke klasse, signatuur en berichtvorm is geciteerd uit de exception-namespace van de engine, niet geparafraseerd.
- De abstracte basis en het bijbehorende
ContextAwareExceptionInterface-contract, de getypeerde subklassen, de vorm vangetContext()en de named constructors vanSignatureExceptionzijn letterlijk uit de broncode geciteerd. - De triageregels “Actionable by:” zijn klasse-docblock-contracten in diezelfde bestanden.
- Het anker op het gebied van human factors is Spec: ISO 9241-110 ISO 9241-110 — §5.6.4.3, over fouten die zichzelf voldoende verklaren om opgelost te worden, en het §6-principe van robuustheid tegen gebruiksfouten. De engine behandelt de ontwikkelaar als gebruiker en de exceptie als de interface die aan die bepalingen moet voldoen.
Praktijkvoorbeeld
Sectie met titel “Praktijkvoorbeeld”Vang breed op als vangnet, vang specifiek op waar u kunt handelen en geef de gestructureerde context rechtstreeks door aan uw logger — zonder berichtparsing.
<?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; }}De specifieke catch herstelt omdat het exceptietype aangaf dat herstel mogelijk was. Het vangnet logt gestructureerde context voor al het overige. Op geen enkel moment leest de applicatie het bericht om te achterhalen wat er is gebeurd.
Veelvoorkomend misverstand
Sectie met titel “Veelvoorkomend misverstand”De gebruikelijke misvatting is dat een diepe exceptieboom overengineering is en dat één fouttype eenvoudiger zou zijn. Dat zou eenvoudiger zijn voor de engine en slechter voor u. Eén type betekent dat elke fout een generieke stacktrace is en dat herstellogica neerkomt op tekenreeksvergelijking. Die vergelijking is fragiel; de volgende herformulering van het bericht breekt haar. Een kleine, specifieke hiërarchie verplaatst die kennis naar het typesysteem, waar de compiler en uw catch-blokken deze kunnen gebruiken.
Een tweede misverstand is dat het bericht en de context overbodig zijn. Dat zijn ze niet. Het bericht is proza voor een mens die een logregel leest. De context is een getypeerde map voor routing in code, alerting of dashboards. Ze door elkaar halen is precies de string-parsing-valkuil die het getContext()-contract wegneemt.
Grenzen en beperkingen
Sectie met titel “Grenzen en beperkingen”De hiërarchie is opzettelijk ondiep. NextPDF maakt niet voor elke denkbare fout een aparte exceptieklasse. Het maakt er een wanneer het specifiek opvangen van die fout iets is wat een aanroeper redelijkerwijs zou doen. Te ver opsplitsen zou het string-parsing-probleem inruilen voor het uitdijende probleem van eindeloze catch-lijsten.
getContext() is gestructureerd voor logs en APM en retourneert daarom volgens contract uitsluitend primitieven en lijsten van primitieven, zonder geneste objecten. Het is diagnostische context, geen geserialiseerde momentopname van de interne werking van de engine. Het is ook geen stabiel wire-formaat om externe schema’s op te bouwen.
Deze pagina beschrijft het ontwerpoppervlak van de excepties. De exacte verzameling excepties en hun velden evolueert mee met de engine. De hier geciteerde klassen en vormen zijn actueel op het moment van deze review en illustreren het contract; ze vormen geen bevroren catalogus. Het contract — één basis, getypeerde subklassen, gestructureerde context, actiegerichte berichten — is het stabiele deel.
Verwante documentatie
Sectie met titel “Verwante documentatie”- Een API die weigert te gissen — de fail-fast-bewakingen waardoor deze excepties überhaupt worden opgeworpen.
- De NextPDF-ontwerpfilosofie — waarom “fouten zijn een API-oppervlak” een eersteklas principe is.
- Het pipelinemodel — waar deze fouten opduiken terwijl een document door de engine beweegt en hoe ze worden waargenomen.
Verklarende woordenlijst
Sectie met titel “Verklarende woordenlijst”- Code-backed (bewijsniveau) — een pagina waarvan de beweringen worden getoetst aan de broncode van de engine zelf, en geciteerd in plaats van geparafraseerd.
- Contextbewuste exceptie — een NextPDF-exceptie die
ContextAwareExceptionInterfaceimplementeert engetContext()blootlegt. Die methode retourneert een snake_case-map met primitieve diagnostische velden die veilig in een log of APM-payload kan worden geserialiseerd zonder de berichttekst te parseren. - Named constructor — een statische factorymethode (bijvoorbeeld
SignatureException::tsaUrlEmpty()) die een exceptie bouwt met een bericht dat is gekoppeld aan een specifieke onderliggende oorzaak en, vaak, aan de oplossing daarvan. - PAdES — PDF Advanced Electronic Signatures, de ETSI-profielfamilie voor het ondertekenen van PDF’s. Bij eerste gebruik voluit geschreven; uitgebreid behandeld op de pagina’s over ondertekening.
- TSA — Time-Stamping Authority, de vertrouwde dienst die RFC 3161-tijdstempels uitgeeft die door de hogere PAdES-profielen worden gebruikt.