Zum Inhalt springen

Fehler als Feature

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

NextPDF behandelt seine Ausnahmehierarchie als API-Oberfläche, die mit derselben Sorgfalt entworfen ist wie die Methoden, die sie auslösen. Ein Fehler ist spezifisch, typisiert, in der von Ihnen benötigten Granularität abfangbar und trägt strukturierten Kontext für Ihre Logs.

Diese Seite zeigt diese Oberfläche im Quellcode der Engine selbst: den Basistyp, die typisierten Unterklassen, die benannten Konstruktoren, die eine Grundursache mit der Meldung verknüpfen, und den strukturierten Kontext, den jede NextPDF-Ausnahme bereitstellt.

Eine Fehlermeldung ist die Engine, die zum denkbar schlechtesten Zeitpunkt zu Ihnen spricht: in der Produktion, um 2 a.m., und mit einem Dokument, das längst hätte ausgeliefert sein sollen. Was die Meldung in diesem Moment aussagt, entscheidet, ob der nächste Schritt ein Fix oder eine lange Untersuchung ist.

Eine generische RuntimeException: something went wrong lässt Ihnen keinen Ansatzpunkt. Sie sagt Ihnen, dass die Engine fehlgeschlagen ist, aber nicht, was fehlgeschlagen ist, nicht, wo es fehlgeschlagen ist, und schon gar nicht, was zu tun ist. Die Leitlinien zu menschlichen Faktoren sind in diesem Punkt eindeutig. Ein Fehler sollte sich gut genug selbst erklären, sodass seine Behebung der naheliegende nächste Schritt ist und kein Forschungsprojekt ( Spec: ISO 9241-110, §5.6.4.3 ). Eine Ausnahme, die Ursache und Abhilfe benennt, ist keine Nettigkeit. Sie ist der Unterschied zwischen einem Fünf-Minuten-Fix und einem Fünf-Stunden-Fix.

  • Jeder NextPDF-Fehler erbt von einer abstrakten Basis, NextPdfException, sodass Sie alle Bibliotheksfehler mit einem einzigen Typ abfangen können.
  • Darunter stehen spezifische, typisierte Unterklassen — eine Schriftart, die nicht gefunden werden kann, eine Konfiguration, die ungültig ist, eine Signaturoperation, die fehlgeschlagen ist — sodass Sie genau den Fehler abfangen können, den Sie behandeln können.
  • Jede NextPDF-Ausnahme implementiert ContextAwareExceptionInterface und stellt getContext() bereit: eine strukturierte, log-sichere Map, damit Sie nie eine Meldungszeichenkette parsen müssen, um Diagnosedaten zurückzugewinnen.
  • Meldungen sind handlungsleitend: benannte Konstruktoren binden die tatsächliche Grundursache (und oft den Fix) an die Meldung, statt einer generischen Vorlage.
  • Jede Ausnahmeklasse dokumentiert, wer auf sie reagieren kann — Entwickler, Infrastruktur oder Bibliotheksaufrufer —, sodass die Triage beginnt, bevor Sie den Stack-Trace lesen.

Die Hierarchie ist flach und bewusst gewählt. Es gibt eine Basis, eine Schicht domänenspezifischer Typen und einen Vertrag, den jeder einzelne von ihnen einhält.

Eine Basis, bewusst als Catch-all konzipiert. NextPdfException ist abstrakt, erweitert RuntimeException und implementiert ContextAwareExceptionInterface:

abstract class NextPdfException extends RuntimeException implements ContextAwareExceptionInterface
{
/** @return array<string, mixed> */
public function getContext(): array
{
return [];
}
}

Dass sie abstrakt ist, ist eine bewusste Entscheidung. Sie fangen den unspezifischen Basistyp nie versehentlich, weil er nie direkt ausgelöst wird. Sie fangen ihn bewusst als Auffangnetz, und Sie fangen eine spezifische Unterklasse, wenn Sie etwas Spezifisches tun können.

Spezifische, typisierte Unterklassen. Eine fehlende Schriftart ist kein generischer Fehler; sie ist eine FontNotFoundException und trägt die Daten, die Sie für die Behebung benötigen:

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

Die Meldung benennt die Schriftart und die exakt durchsuchten Pfade. Sie raten nicht, welches Verzeichnis gefehlt hat. Die Ausnahme sagt es Ihnen.

Strukturierter Kontext, kein Auslesen von Zeichenketten. Jede Ausnahme liefert eine snake_case-Map zurück, die ausschließlich Primitive enthält und sich gefahrlos direkt in ein Log oder eine APM-Payload serialisieren lässt:

public function getContext(): array
{
return [
'config_key' => $this->configKey,
'given_value' => $this->givenValue,
'expected_type' => $this->expectedType,
];
}

Der Grund für diesen Vertrag ist explizit. Eine Logging-Middleware kann $logger->error($e->getMessage(), $e->getContext()) für jede NextPDF-Ausnahme aufrufen, ohne die Meldung jemals zu parsen. Die Meldung ist für Menschen. Der Kontext ist für Maschinen. Keines von beiden muss die Rolle des anderen übernehmen.

Handlungsleitende Meldungen über benannte Konstruktoren. Hier sind Fehler nicht mehr beiläufig, sondern bewusst gestaltet. SignatureException sagt nicht nur „Signieren auf Stufe B-LT fehlgeschlagen“. Sie bietet benannte Konstruktoren an, die die tatsächliche Grundursache und häufig die exakte Abhilfe mit der Meldung verknüpfen:

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');
}

Die Meldung gibt an, was falsch ist, und was dagegen zu tun ist. Es gibt verwandte Konstruktoren für ein fehlendes Capability-Paket, einen fehlenden HTTP-Client, einen versehentlich gewählten reinen Digest-Algorithmus, einen Schlüsseltyp, der nicht zum Algorithmus passt, und weitere. Jeder davon verwandelt eine Fehlerklasse in einen Satz, auf den ein Entwickler reagieren kann, ohne den Quellcode der Engine zu lesen.

Fehler, die bewusst laut sind. Manche Ausnahmen existieren genau dazu, aus einer stillen Lücke eine laute zu machen. NotImplementedException trägt ein maschinell durchsuchbares feature-Label und eine followUp-Referenz:

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,
);
}
}

Ein erreichter, aber nicht angebundener Pfad löst diese Ausnahme aus, statt einen plausiblen No-op zurückzugeben. Derselbe Gedanke steckt hinter StrictModeViolation, deren Unterklassen ein kurzes, durchsuchbares Label für das abweichende Konstrukt sowie optionalen Standort- und Zitatkontext tragen. Eine Spec-Abweichung wird zu einem typisierten, kontextbezogenen Stopp statt zu einem still falschen Rendering.

Triage-Metadaten in der Klasse selbst. Jede Ausnahmeklasse benennt in ihrem Docblock, wer auf sie reagieren kann. Zum Beispiel ist FontNotFoundException „Entwickler (Schriftartpfad prüfen) oder Infrastruktur (Dateiberechtigungen korrigieren)“. InvalidConfigException ist „Entwickler (Konfiguration korrigieren, bevor NextPDF aufgerufen wird)“. NotImplementedException ist „Bibliotheksaufrufer — entweder den Aufruf entfernen oder auf ein künftiges Release festlegen“. Die Triage beginnt vor dem Stack-Trace, denn die Frage „Liegt das bei mir oder bei den Ops?“ hat bereits eine dokumentierte Antwort.

Die Tabelle fasst das Design zusammen und zeigt, was Ihnen jede Eigenschaft bringt.

Design-EigenschaftIm QuellcodeWas es Ihnen einbringt
Eine abstrakte BasisNextPdfException (abstrakt, implementiert das Kontext-Interface)Fangen Sie jeden Bibliotheksfehler mit einem Typ ab, ohne versehentlich den unspezifischen Basistyp zu verwenden
Spezifische, typisierte UnterklassenFontNotFoundException, InvalidConfigException, SignatureException, …Fangen Sie genau den Fehler ab, den Sie behandeln können
Strukturierter KontextgetContext() — ausschließlich snake_case-PrimitiveDirekt ins Log schreiben oder an APM senden, ohne eine Meldungszeichenkette zu parsen
Handlungsleitende MeldungenBenannte Konstruktoren binden Grundursache + AbhilfeEin Satz, auf den Sie reagieren können, statt einer Vorlage
Bewusst lautNotImplementedException, StrictModeViolationEine stille Lücke wird zu einem typisierten, durchsuchbaren Stopp
Triage-Metadaten„Actionable by:” in jedem Klassen-DocblockWissen, wessen Problem es ist, bevor Sie den Trace lesen

Diese Seite ist Evidence: Code-backed : Jede Klasse, jede Signatur und jede Meldungsform ist aus dem Ausnahme-Namespace der Engine zitiert, nicht paraphrasiert.

  • Die abstrakte Basis und ihr ContextAwareExceptionInterface-Vertrag, die typisierten Unterklassen, die getContext()-Form und die benannten Konstruktoren von SignatureException sind wörtlich aus dem Quellcode zitiert.
  • Die Triage-Zeilen „Actionable by:” sind Klassen-Docblock-Verträge in genau diesen Dateien.
  • Der Bezug zu menschlichen Faktoren ist Spec: ISO 9241-110 — §5.6.4.3, zu Fehlern, die sich gut genug selbst erklären, um behoben zu werden, und das Prinzip der Robustheit gegenüber Benutzungsfehlern aus §6. Die Engine behandelt den Entwickler als den Benutzer und die Ausnahme als die Schnittstelle, die diese Klauseln erfüllen muss.

Fangen Sie breit als Auffangnetz, spezifisch dort, wo Sie handeln können, und übergeben Sie den strukturierten Kontext direkt an Ihren Logger — ohne Parsen der Meldung.

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

Das spezifische catch kann wiederherstellen, weil der Ausnahmetyp signalisiert, dass eine Wiederherstellung möglich ist. Das Auffangnetz protokolliert strukturierten Kontext für alles Übrige. Zu keinem Zeitpunkt liest die Anwendung die Meldung, um herauszufinden, was passiert ist.

Die übliche Fehldeutung ist, dass ein tiefer Ausnahmebaum Over-Engineering sei und dass ein einziger Fehlertyp einfacher wäre. Er wäre einfacher für die Engine und schlechter für Sie. Ein Typ bedeutet, dass jeder Fehler ein generischer Stack-Trace ist und Wiederherstellungslogik auf String-Abgleich hinausläuft. Dieser Abgleich ist fragil, und die nächste Umformulierung der Meldung bricht ihn. Eine kleine, spezifische Hierarchie verlagert dieses Wissen ins Typsystem, wo der Compiler und Ihre catch-Blöcke es nutzen können.

Ein zweites Missverständnis ist, dass die Meldung und der Kontext redundant seien. Das sind sie nicht. Die Meldung ist Prosa für einen Menschen, der eine Log-Zeile liest. Der Kontext ist eine typisierte Map für Code-Routing, Alerting oder Dashboards. Beides zu vermengen ist genau die String-Parsing-Falle, zu deren Beseitigung der getContext()-Vertrag existiert.

Die Hierarchie ist bewusst flach. NextPDF erstellt nicht für jeden denkbaren Fehler eine eigene Ausnahmeklasse. Sie entsteht dort, wo ein Aufrufer diesen Fehler vernünftigerweise gezielt abfangen würde. Eine Übersegmentierung würde das String-Parsing-Problem gegen ein wucherndes Catch-Listen-Problem eintauschen.

getContext() ist für Logs und APM als strukturierter Kontext gedacht und gibt daher per Vertrag ausschließlich Primitive und Listen von Primitiven zurück, ohne verschachtelte Objekte. Es ist Diagnosekontext, kein serialisierter Schnappschuss von Engine-Interna. Es ist auch kein stabiles Wire-Format, gegen das externe Schemata gebaut werden sollten.

Diese Seite beschreibt die Designoberfläche der Ausnahmen. Der genaue Bestand an Ausnahmen und ihren Feldern entwickelt sich mit der Engine. Die hier zitierten Klassen und Formen sind zum Zeitpunkt dieser Prüfung aktuell und veranschaulichen den Vertrag, sie sind kein eingefrorener Katalog. Der Vertrag — eine Basis, typisierte Unterklassen, strukturierter Kontext, handlungsleitende Meldungen — ist der stabile Teil.

  • Code-gestützt (Evidenzstufe) — eine Seite, deren Aussagen am Quellcode der Engine selbst geprüft und zitiert statt paraphrasiert werden.
  • Kontextbewusste Ausnahme — eine NextPDF-Ausnahme, die ContextAwareExceptionInterface implementiert und getContext() bereitstellt. Diese Methode liefert eine snake_case-Map mit primitiven Diagnosefeldern zurück, die sich gefahrlos in ein Log oder eine APM-Payload serialisieren lässt, ohne die Meldungszeichenkette zu parsen.
  • Benannter Konstruktor — eine statische Factory-Methode (zum Beispiel SignatureException::tsaUrlEmpty()), die eine Ausnahme mit einer Meldung erstellt, die an eine spezifische Grundursache und oft an deren Abhilfe gebunden ist.
  • PAdES — PDF Advanced Electronic Signatures, die ETSI-Profilfamilie für das PDF-Signieren. Bei der ersten Verwendung ausgeschrieben; ausführlich auf den Signierseiten behandelt.
  • TSA — Time-Stamping Authority, der vertrauenswürdige Dienst, der die von den höheren PAdES-Profilen verwendeten RFC 3161-Zeitstempel ausstellt.