Errors as a feature
Questi contenuti non sono ancora disponibili nella tua lingua.
Spec: ISO 9241-110, §5.6.4 ISO 9241-110 §5.6.4 Evidence: Code-backed
At a glance
Section titled “At a glance”NextPDF treats its exception hierarchy as an API surface, designed with the same care as the methods that throw it. A failure is specific, typed, catchable at the granularity you need, and carries structured context for your logs.
This page shows that surface in the engine’s own source: the base type, the typed subclasses, the named constructors that bind a root cause to the message, and the structured context every NextPDF exception exposes.
Why this matters
Section titled “Why this matters”An error message is the engine speaking to you at the worst possible moment: production, 2 a.m., and a document that should have shipped. What that message says then decides whether the next step is a fix or a long investigation.
A generic RuntimeException: something went wrong gives you nowhere to go. It
tells you that the engine failed, but not what failed, not where, and
certainly not what to do. Human-factors guidance is direct about this. An
error should explain itself well enough that fixing it is the obvious next
step, not a research project ( Spec: ISO 9241-110, §5.6.4.3 ISO 9241-110 §5.6.4.3 ).
An exception that names the cause and the remedy is not a nicety. It is the
difference between a five-minute fix and a five-hour one.
The short version
Section titled “The short version”- Every NextPDF failure extends one abstract base,
NextPdfException, so you can catch all library errors with a single type. - Below it sit specific, typed subclasses — a font that cannot be found, a config that is invalid, a signature operation that failed — so you can catch exactly the failure you can handle.
- Every NextPDF exception implements
ContextAwareExceptionInterfaceand exposesgetContext(): a structured, log-safe map, so you never parse a message string to recover diagnostics. - Messages are actionable: named constructors bind the actual root cause (and often the fix) to the message, instead of a generic template.
- Each exception class documents who can act on it — developer, infrastructure, or library caller — so triage starts before you read the stack trace.
How NextPDF approaches it
Section titled “How NextPDF approaches it”The hierarchy is shallow and deliberate. There is one base, a layer of domain-specific types, and a contract every one of them follows.
One base, catch-all by design. NextPdfException is abstract, extends
RuntimeException, and implements ContextAwareExceptionInterface:
abstract class NextPdfException extends RuntimeException implements ContextAwareExceptionInterface{ /** @return array<string, mixed> */ public function getContext(): array { return []; }}Abstract is a decision. You never catch the vague base by accident, because it is never thrown directly. You catch it deliberately, as a backstop, and you catch a specific subclass when you can do something specific.
Specific, typed subclasses. A missing font is not a generic error; it is
FontNotFoundException, and it carries the data you need to act:
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()}The message names the font and the exact paths searched. You do not guess which directory was missing; the exception tells you.
Structured context, not string-scraping. Every exception returns a snake_case, primitives-only map that is safe to serialize straight into a log or an APM payload:
public function getContext(): array{ return [ 'config_key' => $this->configKey, 'given_value' => $this->givenValue, 'expected_type' => $this->expectedType, ];}The contract is explicit about why. A logging middleware can call
$logger->error($e->getMessage(), $e->getContext()) for any NextPDF
exception without ever parsing the message. The message is for humans. The
context is for machines. Neither has to act as the other.
Actionable messages via named constructors. This is where errors stop
being incidental and become designed. SignatureException does not only say
“signing failed at level B-LT”. It offers named constructors that bind the
real root cause, and frequently the exact remedy, to the message:
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');}The message states what is wrong and what to do about it. There are sibling constructors for a missing capability package, an absent HTTP client, a digest-only algorithm chosen by mistake, a key type that does not match the algorithm, and more. Each one turns a class of failure into a sentence a developer can act on without reading the engine’s source.
Failures that are loud on purpose. Some exceptions exist precisely so a
silent gap becomes a noisy one. NotImplementedException carries a
machine-grep-able feature label and a followUp reference:
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, ); }}A reached-but-unwired path throws this instead of returning a plausible no-op.
The same idea drives StrictModeViolation, whose subclasses carry a short
grep-able label for the deviating construct plus optional location and citation
context. A spec deviation becomes a typed, contextual stop, not a quietly wrong
render.
Triage metadata in the class itself. Each exception class names who can
act on it in its docblock. For example, FontNotFoundException is
“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 starts before the stack trace,
because the question “is this mine or operations?” already has an answer
written down.
The table summarises the design and what each property buys you.
| Design property | In the source | What it buys you |
|---|---|---|
| One abstract base | NextPdfException (abstract, implements the context interface) | Catch every library error with one type, never the vague base by accident |
| Specific typed subclasses | FontNotFoundException, InvalidConfigException, SignatureException, … | Catch exactly the failure you can handle |
| Structured context | getContext() — snake_case primitives only | Log or ship to APM without parsing a message string |
| Actionable messages | Named constructors bind root cause + remedy | A sentence you can act on, not a template |
| Loud-on-purpose | NotImplementedException, StrictModeViolation | A silent gap becomes a typed, grep-able stop |
| Triage metadata | ”Actionable by:” in each class docblock | Know whose problem it is before reading the trace |
What the evidence says
Section titled “What the evidence says”This page is Evidence: Code-backed : every class, signature, and message shape is quoted from the engine’s exception namespace, not paraphrased.
- The abstract base and its
ContextAwareExceptionInterfacecontract, the typed subclasses, thegetContext()shape, and theSignatureExceptionnamed constructors are quoted verbatim from the source. - The “Actionable by:” triage lines are class-docblock contracts in those same files.
- The human-factors anchor is Spec: ISO 9241-110 ISO 9241-110 — §5.6.4.3, on errors that explain themselves enough to be fixed, and the §6 use error robustness principle. The engine treats the developer as the user and the exception as the interface that has to satisfy those clauses.
Practical example
Section titled “Practical example”Catch broadly as a backstop, catch specifically where you can act, and feed the structured context straight to your logger — no message parsing.
<?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; }}The specific catch recovers because the exception type told it that recovery
was possible. The backstop logs structured context for everything else. At no
point does the application read the message to find out what happened.
Common misconception
Section titled “Common misconception”The usual misreading is that a deep exception tree is over-engineering, and
that one error type would be simpler. It would be simpler for the engine and
worse for you. One type means every failure is a generic stack trace and the
recovery logic is a string match. That match is fragile; the next message
reword breaks it. A small, specific hierarchy moves that knowledge into the
type system, where the compiler and your catch blocks can use it.
A second misconception is that the message and the context are redundant.
They are not. The message is prose for a human reading a log line. The
context is a typed map for code routing, alerting, or dashboards. Conflating
them is exactly the string-parsing trap the getContext() contract exists to
remove.
Limits and boundaries
Section titled “Limits and boundaries”The hierarchy is intentionally shallow. NextPDF does not create a distinct exception class for every conceivable failure. It creates one where catching that failure specifically is something a caller would reasonably do. Over-splitting would trade the string-parsing problem for a sprawling catch-list problem.
getContext() is structured for logs and APM, so it returns primitives and
lists of primitives only, with no nested objects, by contract. It is
diagnostic context, not a serialized snapshot of engine internals. It is also
not a stable wire format to build external schemas against.
This page describes the exception design surface. The exact set of exceptions and their fields evolves with the engine. The classes and shapes quoted here are current as of this review and are illustrative of the contract, not a frozen catalogue. The contract — one base, typed subclasses, structured context, actionable messages — is the stable part.
Related docs
Section titled “Related docs”- An API that refuses to guess — the fail-fast guards that throw these exceptions in the first place.
- The NextPDF design philosophy — why “errors are an API surface” is a first-class principle.
- The pipeline model — where these failures surface as a document moves through the engine, and how they are observed.
Glossary
Section titled “Glossary”- Code-backed (evidence level) — a page whose claims are checked against the engine’s own source, quoted rather than paraphrased.
- Context-aware exception — a NextPDF exception implementing
ContextAwareExceptionInterfaceand exposinggetContext(). That method returns a snake_case map of primitive diagnostic fields safe to serialize into a log or APM payload without parsing the message string. - Named constructor — a static factory method (for example
SignatureException::tsaUrlEmpty()) that builds an exception with a message bound to a specific root cause and, often, its remedy. - PAdES — PDF Advanced Electronic Signatures, the ETSI profile family for PDF signing. Expanded on first use; covered in depth on the signing pages.
- TSA — Time-Stamping Authority, the trusted service that issues RFC 3161 timestamps used by the higher PAdES profiles.