Ir al contenido

Gestiona errores con la jerarquía de excepciones de NextPDF

NextPDF lanza excepciones tipadas cuando se producen estados excepcionales. Nunca oculta un error devolviendo false o null. Todas las excepciones de dominio extienden una única base abstracta, NextPdfException, y exponen contexto de diagnóstico estructurado a través de ContextAwareExceptionInterface. Esta guía muestra cómo capturar con la granularidad adecuada y cómo registrar el contexto estructurado para una canalización de supervisión del rendimiento de aplicaciones (APM). También señala qué fallos no cubre el único bloque genérico.

Ventana de terminal
composer require nextpdf/core:^3

No se requiere ninguna extensión adicional.

La jerarquía es:

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

Esta jerarquía tiene dos consecuencias prácticas, ambas verificadas con el código fuente:

  1. catch (NextPdfException $e) captura toda excepción bajo NextPDF\Exception, incluidas las violaciones del modo estricto. Todas extienden la base abstracta.
  2. No captura todo lo que la biblioteca puede lanzar. NextPDF\Support\DegradedException extiende RuntimeException directamente, no NextPdfException. Por eso un catch (NextPdfException $e) no captura un rechazo de la política de degradación. Para manejar ese caso, captura DegradedException (o el más amplio RuntimeException) de forma explícita. Esta guía deja claro ese límite sin dar a entender que un único bloque genérico lo cubre todo.

NextPdfException::getContext() devuelve un array<string, mixed> con claves en snake_case y solo valores primitivos (o listas de primitivos), por lo que se puede serializar directamente en el array de contexto de un logger PSR-3. PSR-3 §1.3 coloca una excepción bajo la clave de contexto 'exception'. El getContext() de NextPDF añade detalles de dominio junto a ella, no el objeto de la excepción en sí.

Esta superficie de la API se genera a partir del PHPDoc de NextPDF\Exception\NextPdfException, NextPDF\Contracts\ContextAwareExceptionInterface, las excepciones de dominio concretas (por ejemplo NextPDF\Exception\FontNotFoundException, con getFontName() / getSearchPaths() / wasFallbackAttempted()), y NextPDF\Support\DegradedException (que lleva el Capability y la DegradationPolicy). Los miembros que se usan a continuación son NextPdfException::getContext() y los accesores de cada excepción.

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

El ejemplo completo muestra la captura granular, el registro de contexto estructurado y el límite de DegradedException. También respeta el canal de salida del 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 queda libre para el harness; el PDF va únicamente a NEXTPDF_COOKBOOK_OUTPUT.

  • Ordenar los bloques catch de específico → general. PHP hace coincidir el primer catch compatible. Un catch (NextPdfException $e) colocado antes de catch (FontNotFoundException $e) convierte el bloque específico en código muerto.
  • DegradedException no es una NextPdfException. Verificada con el código fuente, extiende RuntimeException. Un único catch (NextPdfException $e) deja que un rechazo de degradación estricta se propague sin capturarse. Capturarlo (o RuntimeException) de forma explícita cuando la política de degradación está en juego.
  • getContext() es seguro para APM por contrato. Las claves están en snake_case. Los valores son primitivos o listas de primitivos, sin objetos anidados ni recursos. Se puede serializar directamente, y nunca contiene bytes del documento.
  • No analizar los mensajes de las excepciones. Los mensajes son legibles por personas y pueden cambiar. Los accesores tipados (getFontName(), capability->id, etc.) y getContext() son la superficie de máquina estable.
  • Advertencia sobre conteos obsoletos. Material más antiguo puede citar un número fijo de «N excepciones de dominio». La jerarquía crece a lo largo de las versiones. Conviene apoyarse en el tipo base NextPdfException y en instanceof, nunca en un conteo codificado de forma fija.
  • Los marcadores de posición de PSR-3 siguen siendo cadenas. Al registrar, mantener el mensaje como una cadena con tokens {placeholder} y poner los valores en el array de contexto (PSR-3 §1.2). No interpolar el objeto de la excepción dentro del mensaje.

El manejo de excepciones no añade ningún costo durante el funcionamiento estable. NextPDF lanza solo en estados excepcionales, y getContext() construye un array pequeño bajo demanda. El performance_budget (wall_ms: 2000, peak_mb: 96) acota la ejecución de esta guía en el harness, no documentos arbitrarios.

  • getContext() está diseñado para ser seguro en registros: solo primitivos, sin carga útil del documento, sin bytes de archivo. La aplicación sigue siendo responsable de los valores que añades a un contexto de registro. Sanear cualquier dato proporcionado por el usuario (una ruta de archivo, por ejemplo) según la política de registro antes de que llegue a un sink.
  • No mostrar mensajes de excepción sin procesar a los usuarios finales de una forma que revele la disposición del sistema de archivos. Exponer un mensaje genérico y registrar el contexto estructurado del lado del servidor.
DeclaraciónEspecificaciónCláusulareference_id
Una excepción pertenece al contexto de registro de PSR-3 bajo la clave exception.PSR-3§1.3
Los mensajes de registro siguen siendo cadenas; los nombres de los marcadores de posición se asignan a claves de contexto.PSR-3§1.2
La fecha de modificación se regenera en cada guardado, así que la salida es estructuralmente estable (no byte a byte).ISO 32000-2§14.3

Esta guía está verificada con el perfil de reproducibilidad estructural. La salida incluye un /ID del tráiler y una fecha de modificación que se regeneran en cada guardado, así que la identidad byte a byte no se puede alcanzar. La estructura normalizada por qpdf es estable.