Ga naar inhoud

Fouten als feature

Spec: ISO 9241-110, §5.6.4 Evidence: Code-backed

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.

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 ). 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.

  • 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 ContextAwareExceptionInterface en biedt getContext(): 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.

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.

OntwerpeigenschapIn de broncodeWat het u oplevert
Eén abstracte basisNextPdfException (abstract, implementeert de context-interface)Vang elke bibliotheekfout op met één type, nooit per ongeluk de algemene basis
Specifieke getypeerde subklassenFontNotFoundException, InvalidConfigException, SignatureException, …Vang precies de fout op die u kunt afhandelen
Gestructureerde contextgetContext() — uitsluitend snake_case-primitievenLoggen of naar APM sturen zonder berichttekst te parseren
Actiegerichte berichtenNamed constructors koppelen onderliggende oorzaak + oplossingEen zin waarop u kunt handelen, geen sjabloon
Opzettelijk luidruchtigNotImplementedException, StrictModeViolationEen stille leemte wordt een getypeerde, grepbare stop
Triagemetadata“Actionable by:” in elke klasse-docblockWeet wiens probleem het is voordat u de trace leest

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 van getContext() en de named constructors van SignatureException zijn 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 — §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.

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.

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.

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.

  • 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 ContextAwareExceptionInterface implementeert en getContext() 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.