Gérer les erreurs avec la hiérarchie d'exceptions de NextPDF
En un coup d’œil
Section intitulée « En un coup d’œil »NextPDF lève des exceptions typées pour signaler les états exceptionnels. Il ne masque jamais une erreur derrière un retour false ou null. Chaque exception de domaine hérite d’une base abstraite commune, NextPdfException, et expose un contexte de diagnostic structuré via ContextAwareExceptionInterface. Ce recipe te montre comment intercepter les exceptions au bon niveau de granularité et journaliser le contexte structuré dans une chaîne de supervision applicative (APM). Il signale aussi les défaillances qu’un unique bloc attrape-tout ne couvre pas.
Installation
Section intitulée « Installation »composer require nextpdf/core:^3Aucune extension supplémentaire n’est requise.
Aperçu conceptuel
Section intitulée « Aperçu conceptuel »La hiérarchie est la suivante :
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\OracleConformanceFailureCette hiérarchie a deux conséquences pratiques, toutes deux vérifiées par rapport au code source :
catch (NextPdfException $e)intercepte chaque exception sousNextPDF\Exception, y compris les violations du mode strict. Elles héritent toutes de la base abstraite.- Il n’intercepte pas tout ce que la bibliothèque peut lever.
NextPDF\Support\DegradedExceptionétendRuntimeExceptiondirectement, et nonNextPdfException. Uncatch (NextPdfException $e)n’intercepte donc pas un rejet de la politique de dégradation. Pour gérer ce cas, intercepte explicitementDegradedException(ou le plus largeRuntimeException). Ce recipe explicite cette frontière au lieu de prétendre qu’un seul bloc attrape-tout couvre tout.
NextPdfException::getContext() renvoie un array<string, mixed> dont les clés sont en snake_case et dont les valeurs sont uniquement des primitives (ou des listes de primitives). Tu peux donc le sérialiser directement dans le tableau de contexte d’un logger PSR-3. PSR-3 §1.3 place une exception sous la clé de contexte 'exception'. NextPDF, avec son getContext(), y ajoute des détails de domaine, et non l’objet exception lui-même.
Surface d’API
Section intitulée « Surface d’API »Cette surface d’API est générée à partir du PHPDoc de NextPDF\Exception\NextPdfException, de NextPDF\Contracts\ContextAwareExceptionInterface, des exceptions de domaine concrètes (par exemple NextPDF\Exception\FontNotFoundException, avec getFontName() / getSearchPaths() / wasFallbackAttempted()), et de NextPDF\Support\DegradedException (qui porte les Capability et DegradationPolicy). Les membres utilisés ci-dessous sont NextPdfException::getContext() et les accesseurs propres à chaque exception.
Exemple de code — Démarrage rapide
Section intitulée « Exemple de code — Démarrage rapide »<?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());}Exemple de code — Production
Section intitulée « Exemple de code — Production »L’exemple complet illustre l’interception granulaire, la journalisation du contexte structuré et la frontière de DegradedException. Il respecte le canal de sortie du harnais.
<?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 reste libre pour le harnais ; le PDF n’est écrit que vers NEXTPDF_COOKBOOK_OUTPUT.
Cas limites & pièges
Section intitulée « Cas limites & pièges »- Ordonne les blocs catch du plus spécifique au plus général. PHP exécute le premier
catchcompatible. Uncatch (NextPdfException $e)placé avantcatch (FontNotFoundException $e)rend le bloc spécifique inaccessible (code mort). DegradedExceptionn’est pas uneNextPdfException. Vérifié par rapport au code source, elle étendRuntimeException. Un seulcatch (NextPdfException $e)laisse se propager silencieusement un rejet de dégradation stricte. Intercepte-le (ouRuntimeException) explicitement quand une politique de dégradation est en jeu.getContext()est sûr pour l’APM par contrat. Les clés sont en snake_case. Les valeurs sont des primitives ou des listes de primitives, sans objets imbriqués ni ressources. Tu peux sérialiser le résultat directement, et il ne contient jamais d’octets de document.- Ne parse pas les messages d’exception. Les messages sont destinés aux humains et peuvent changer. Les accesseurs typés (
getFontName(),capability->id, etc.) etgetContext()constituent la surface machine stable. - Attention aux décomptes obsolètes. D’anciens supports peuvent citer un nombre fixe de « N exceptions de domaine ». La hiérarchie s’enrichit au fil des versions. Appuie-toi sur le type de base
NextPdfExceptionet surinstanceof, jamais sur un nombre codé en dur. - Les espaces réservés PSR-3 restent des chaînes. Lorsque tu journalises, garde le message sous forme de chaîne avec des jetons
{placeholder}et place les valeurs dans le tableau de contexte (PSR-3 §1.2). N’interpole pas l’objet exception dans le message.
Performances
Section intitulée « Performances »La gestion des exceptions n’ajoute aucun coût en fonctionnement normal. NextPDF ne lève des exceptions que dans les états exceptionnels, et getContext() construit un petit tableau à la demande. Le performance_budget (wall_ms: 2000, peak_mb: 96) borne l’exécution de ce recipe dans le harnais, et non celle de documents arbitraires.
Notes de sécurité
Section intitulée « Notes de sécurité »getContext()est conçu pour être sûr à journaliser : uniquement des primitives, aucune charge utile de document, aucun octet de fichier. Tu restes responsable des valeurs que tu ajoutes à un contexte de journal. Nettoie toute valeur provenant de l’utilisateur (un chemin de fichier, par exemple) selon ta politique de journalisation avant qu’elle n’atteigne un puits.- N’affiche pas de messages d’exception bruts aux utilisateurs finaux d’une manière qui révélerait l’organisation du système de fichiers. Affiche un message générique et journalise le contexte structuré côté serveur.
Conformité
Section intitulée « Conformité »| Déclaration | Spécification | Article | reference_id |
|---|---|---|---|
Une exception a sa place dans le contexte de journal PSR-3 sous la clé exception. | PSR-3 | §1.3 | |
| Les messages de journal restent des chaînes ; les noms d’espace réservé correspondent aux clés de contexte. | PSR-3 | §1.2 | |
| La date de modification est régénérée à chaque enregistrement, donc la sortie est stable structurellement (et non octet pour octet). | ISO 32000-2 | §14.3 |
Ce recipe est vérifié avec le profil de reproductibilité structurel. La sortie contient un /ID dans le trailer et une date de modification qui sont régénérés à chaque enregistrement ; l’identité octet pour octet n’est donc pas atteignable. La structure normalisée par qpdf est stable.