Salta ai contenuti

Gestire gli errori con la gerarchia delle eccezioni di NextPDF

NextPDF solleva eccezioni tipizzate per gli stati eccezionali. Non nasconde mai un errore dietro un valore restituito false o null. Ogni eccezione di dominio estende un’unica classe base astratta, NextPdfException, ed espone un contesto diagnostico strutturato tramite ContextAwareExceptionInterface. Questa ricetta mostra come intercettarle con la granularità corretta e come registrare il contesto strutturato per una pipeline di application performance monitoring (APM). Chiarisce inoltre quali errori il singolo blocco catch generale non copre.

Terminal window
composer require nextpdf/core:^3

Non sono richieste estensioni aggiuntive.

La gerarchia è questa:

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

Da questa gerarchia derivano due conseguenze pratiche, entrambe verificate sul codice sorgente:

  1. catch (NextPdfException $e) intercetta ogni eccezione sotto NextPDF\Exception, comprese le violazioni della modalità strict. Tutte estendono la classe base astratta.
  2. Non intercetta tutto ciò che la libreria può generare. NextPDF\Support\DegradedException estende RuntimeException direttamente, non NextPdfException. Pertanto un catch (NextPdfException $e) non intercetta un rifiuto dovuto alla policy di degradazione. Per gestire questa condizione, intercettare esplicitamente DegradedException (o la più generica RuntimeException). Questa ricetta rende esplicito tale confine, invece di suggerire che un singolo blocco catch generale copra tutto.

NextPdfException::getContext() restituisce un array<string, mixed> con chiavi in snake_case e valori esclusivamente primitivi (o liste di primitivi); è quindi sicuro serializzarlo direttamente nell’array di contesto di un logger PSR-3. PSR-3 §1.3 colloca un’eccezione sotto la chiave di contesto 'exception'. In NextPDF, getContext() affianca a questa chiave i dettagli di dominio, non l’oggetto eccezione stesso.

La superficie API descritta qui deriva dal PHPDoc di NextPDF\Exception\NextPdfException, NextPDF\Contracts\ContextAwareExceptionInterface, delle eccezioni di dominio concrete (per esempio NextPDF\Exception\FontNotFoundException, con getFontName() / getSearchPaths() / wasFallbackAttempted()) e di NextPDF\Support\DegradedException (che trasporta Capability e DegradationPolicy). I membri utilizzati di seguito sono NextPdfException::getContext() e gli accessor specifici di ciascuna eccezione.

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

L’esempio completo mostra l’intercettazione granulare, la registrazione del contesto strutturato e il confine di DegradedException. Mantiene il canale di output richiesto dall’harness.

<?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 rimane libero per l’harness; il PDF viene scritto solo in NEXTPDF_COOKBOOK_OUTPUT.

  • Ordinare i blocchi catch da specifico → generale. PHP individua la prima clausola catch compatibile. Un catch (NextPdfException $e) posto prima di catch (FontNotFoundException $e) rende irraggiungibile il blocco specifico.
  • DegradedException non è una NextPdfException. Come verificato sul codice sorgente, estende RuntimeException. Un singolo catch (NextPdfException $e) lascia propagare silenziosamente un rifiuto dovuto alla degradazione strict. Intercettarla esplicitamente (o tramite RuntimeException) quando è attiva una policy di degradazione.
  • getContext() è sicuro per l’uso con gli APM per contratto. Le chiavi sono in snake_case. I valori sono primitivi o liste di primitivi, senza oggetti annidati e senza risorse. È possibile serializzarlo direttamente e non contiene mai byte del documento.
  • Non analizzare i messaggi delle eccezioni. I messaggi sono destinati alla lettura umana e possono cambiare. Gli accessor tipizzati (getFontName(), capability->id e così via) e getContext() costituiscono la superficie stabile per l’elaborazione automatica.
  • Avvertenza sui conteggi obsoleti. Il materiale più datato potrebbe citare un numero fisso di «N eccezioni di dominio». La gerarchia si amplia da una release all’altra. Affidarsi al tipo base NextPdfException e a instanceof, mai a un conteggio cablato nel codice.
  • I segnaposto PSR-3 restano stringhe. In fase di registrazione, mantenere il messaggio come stringa con token {placeholder} e inserire i valori nell’array di contesto (PSR-3 §1.2). Non interpolare l’oggetto eccezione nel messaggio.

La gestione delle eccezioni non introduce costi nel flusso normale. NextPDF genera eccezioni solo negli stati eccezionali e getContext() costruisce un piccolo array su richiesta. Il performance_budget (wall_ms: 2000, peak_mb: 96) delimita l’esecuzione di questa ricetta nell’harness, non documenti arbitrari.

  • getContext() è progettato per essere sicuro per il logging: solo primitivi, nessun payload del documento, nessun byte di file. L’utente resta comunque responsabile dei valori che aggiunge a un contesto di log. Ripulire tutto ciò che è fornito dall’utente (un percorso di file, per esempio) in base alla propria policy di logging prima che raggiunga un sink.
  • Non restituire agli utenti finali i messaggi grezzi delle eccezioni quando potrebbero rivelare la struttura del filesystem. Mostrare un messaggio generico e registrare il contesto strutturato lato server.
DichiarazioneSpecificaClausolareference_id
Un’eccezione appartiene al contesto di log PSR-3 sotto la chiave exception.PSR-3§1.3
I messaggi di log restano stringhe; i nomi dei segnaposto corrispondono alle chiavi di contesto.PSR-3§1.2
La data di modifica viene rigenerata a ogni salvataggio, pertanto l’output è stabile a livello strutturale (non di byte).ISO 32000-2§14.3

Questa ricetta è verificata con il profilo di riproducibilità strutturale. L’output contiene un trailer /ID e una data di modifica rigenerati a ogni salvataggio, quindi l’identità a livello di byte non è ottenibile. La struttura normalizzata da qpdf è stabile.