Eigene Strategien für Fehler-Recovery und Wiederholungsversuche umsetzen
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Ein Dokumentdienst im Produktivbetrieb macht mehr, als nur eine Exception abzufangen und zu protokollieren. Er entscheidet, wie es weitergeht: mit einem degradierten Ergebnis fortfahren, auf einen zweiten Rendering-Pfad umschalten, mit Eingaben, die die Engine akzeptiert, erneut versuchen oder genau die Seiten ausliefern, die vor dem Fehler bereits aufgebaut wurden. Dieses Recipe zeigt vier Recovery-Strategien, die auf der Exception-Hierarchie von NextPDF und den Methoden zur Inspektion des Dokumentzustands aufbauen:
- Kontrollierte Degradierung bei einem Schriftartfehler — fangen Sie
NextPDF\Exception\FontNotFoundExceptionab, weichen Sie auf eine garantiert verfügbare Schriftart aus und bauen Sie das Dokument weiter auf. - Ein Fallback-Renderer — wenn der In-Process-Pfad
Document::writeHtml()die Eingabe ablehnt, versuchen Sie es erneut überDocument::writeHtmlChrome(), die Chrome-Brücke vonnextpdf/artisan. - Erneuter Versuch mit alternativem HTML — wenn
NextPDF\Exception\HtmlParsingExceptionoderNextPDF\Exception\CssResolutionBudgetExceededExceptionausgelöst wird, versuchen Sie es erneut mit einer vereinfachten, erprobten HTML-Variante. - Recovery eines Teildokuments — lesen Sie nach einem Fehler
Document::getNumPages()aus und speichern Sie das bereits Aufgebaute, statt es zu verwerfen.
Sie wissen bereits, wie Sie mit der richtigen Granularität abfangen. Die zugehörige Seite Fehler mit der NextPDF-Exception-Hierarchie behandeln behandelt die Hierarchie selbst. Diese Seite behandelt, was Sie nach dem Abfangen tun.
Dieses Recipe richtet sich an die OSS-Core-Edition. Jede hier genannte API liegt in nextpdf/core. Die einzige optionale Abhängigkeit ist nextpdf/artisan für den Chrome-Fallback.
Installation
Abschnitt betitelt „Installation“composer require nextpdf/core:^3Die Fallback-Renderer-Strategie nutzt zusätzlich die Chrome-Brücke:
composer require nextpdf/artisanWenn nextpdf/artisan fehlt, rendert Document::writeHtmlChrome() nicht, sondern wirft NextPDF\Exception\PageLayoutException. Deshalb behandelt die Fallback-Strategie weiter unten eine fehlende Brücke als einen weiteren behebbaren Fall.
Konzeptioneller Überblick
Abschnitt betitelt „Konzeptioneller Überblick“Recovery beruht auf zwei Fakten über NextPDF, die beide anhand des Quellcodes verifiziert wurden.
Die Exception-Hierarchie zeigt, was sich beheben lässt. Jede Domänen-Exception erweitert die abstrakte Basis NextPDF\Exception\NextPdfException, die wiederum RuntimeException erweitert und NextPDF\Contracts\ContextAwareExceptionInterface implementiert. Fangen Sie einen spezifischen Untertyp ab, um einen auf den Fehler zugeschnittenen Recovery-Pfad zu wählen:
FontNotFoundExceptionträgtgetFontName(),getSearchPaths()undwasFallbackAttempted()— genug, um mit einer anderen Schriftart erneut zu versuchen.HtmlParsingExceptionträgtgetRule(),getPosition()undgetHtmlSnippet()— genug, um zu entscheiden, ob sich ein vereinfachter erneuter Versuch lohnt.CssResolutionBudgetExceededExceptionträgtgetVisits()undgetBudget()— ein Signal für einen pathologischen Selektor, das ein abgespecktes Stylesheet beseitigen kann.- Eine wichtige Abgrenzung:
NextPDF\Support\DegradedExceptionerweitertRuntimeExceptiondirekt, nichtNextPdfException. Deshalb fängtcatch (NextPdfException $e)eine Ablehnung durch die Degradierungs-Policy nicht ab. Wenn die aktiveNextPDF\Contracts\DegradationPolicyStrictoderBalancedist, fangen SieDegradedExceptionexplizit ab, um für diesen Fall eine Recovery auszuführen.
Das Dokument lässt sich inspizieren, während Sie es aufbauen. Ein Document legt seinen Aufbauzustand über schreibgeschützte Accessor-Methoden offen. getNumPages() gibt die Gesamtseitenzahl einschließlich der aktiven, noch nicht geflushten Seite zurück, und getPage() gibt den nullbasierten Index der aktuellen Seite zurück. Lesen Sie nach einem Fehler mitten im Aufbau getNumPages() aus, um zu erfahren, ob überhaupt vollständige Seiten vorliegen, und rufen Sie dann save() oder getPdfData() auf, um sie auszugeben. Die Engine zeichnet außerdem nicht fatale Degradierungsereignisse auf: getWarnings() gibt eine list<NextPDF\Support\Warning> zurück, hasWarnings() meldet, ob welche gesammelt wurden, und hasDegradedParity() meldet, ob die Ausgabetreue beeinträchtigt wurde. Damit kann eine Recovery-Routine „sauber gelungen“ von „mit reduzierter Treue gelungen“ unterscheiden, ohne eine Exception parsen zu müssen.
Die Degradierungs-Policy bestimmt, welche Ereignisse Sie als Exceptions und welche Sie als Warnungen behandeln. NextPDF\Core\Config verwendet standardmäßig DegradationPolicy::Balanced, was bei begrenzter Degradierung warnt und weitermacht, bei einer blockierenden Auswirkung jedoch wirft. DegradationPolicy::Permissive wirft nie und sammelt alles im Warnkanal. DegradationPolicy::Strict wirft bei jedem Compliance-Risiko, jedem Semantikverlust oder jeder blockierenden Auswirkung. Wählen Sie zuerst die Policy und schreiben Sie dann Recovery für die Fehlerformen, die diese Policy erzeugt.
API-Oberfläche
Abschnitt betitelt „API-Oberfläche“Der folgende Recovery-Code nutzt diese verifizierten Member:
NextPDF\Core\Document::createStandalone(?Config $config = null): self,addPage(),setFont(string $family, string $style = '', float $size = 12.0): static,cell(...),writeHtml(string $html): static,writeHtmlChrome(string $html, ?float $width = null, ?float $height = null): static,save(string $path): void,getPdfData(): string,getNumPages(): int,getPage(): int,getWarnings(): list<Warning>,hasWarnings(): bool,hasDegradedParity(): bool,addFontDirectory(string $directory): static.NextPDF\Core\Config::withDegradationPolicy(DegradationPolicy $policy): selfund derdegradationPolicy-StandardwertDegradationPolicy::Balanced.NextPDF\Contracts\DegradationPolicy—Strict,Balanced,Permissive.NextPDF\Exception\NextPdfException(abstrakte Basis),NextPDF\Exception\FontNotFoundException,NextPDF\Exception\HtmlParsingException,NextPDF\Exception\CssResolutionBudgetExceededException,NextPDF\Exception\WriterException,NextPDF\Exception\PageLayoutException.NextPDF\Support\DegradedException(trägtcapabilityundpolicy),NextPDF\Support\Capability(id,status,reason,isDegraded()),NextPDF\Support\Warning,NextPDF\Support\WarningSeverity.
Codebeispiel — Schnellstart
Abschnitt betitelt „Codebeispiel — Schnellstart“Die kleinste sinnvolle Recovery: Fangen Sie einen Fehler wegen einer fehlenden Schriftart ab, weichen Sie auf eine garantiert verfügbare Schriftart aus und machen Sie weiter. Dieses Snippet lässt die umfassendere Behandlung aus dem Produktionsbeispiel weg. Für einen vollständigen Handler mit Logging und der DegradedException-Abgrenzung lesen Sie das Produktionsbeispiel weiter unten.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Exception\FontNotFoundException;
$doc = Document::createStandalone();$doc->addPage();
try { // A face that may not be installed on every host. $doc->setFont('CorporateSans', '', 12);} catch (FontNotFoundException $e) { // Recover: fall back to a face the engine always resolves. $doc->setFont('helvetica', '', 12);}
$doc->cell(0, 10, 'Rendered with a recovered font.', newLine: true);$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');Codebeispiel — Produktion
Abschnitt betitelt „Codebeispiel — Produktion“Das vollständige Beispiel verknüpft alle vier Strategien zu einer Render-Pipeline: einen Font-Fallback, einen Renderer-Fallback vom In-Process-Pfad auf Chrome, einen erneuten Versuch mit alternativem HTML und die durch getNumPages() gesteuerte Recovery eines Teildokuments. Es respektiert den Ausgabekanal des Harness, fängt nie eine bloße Exception ab und lässt auch keinen catch-Block leer.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Contracts\ContextAwareExceptionInterface;use NextPDF\Contracts\DegradationPolicy;use NextPDF\Core\Config;use NextPDF\Core\Document;use NextPDF\Exception\CssResolutionBudgetExceededException;use NextPDF\Exception\FontNotFoundException;use NextPDF\Exception\HtmlParsingException;use NextPDF\Exception\NextPdfException;use NextPDF\Exception\PageLayoutException;use NextPDF\Exception\WriterException;use NextPDF\Support\DegradedException;
/** * A minimal structured sink. In production this is your PSR-3 logger; the * exception class and its structured context become log fields. * * @param array<string, mixed> $context */function logRecovery(string $message, array $context): void{ fwrite(STDERR, $message . ' ' . json_encode($context, JSON_THROW_ON_ERROR) . "\n");}
/** * Resolve a usable font, degrading from the requested face to a guaranteed * fallback. Returns the face actually applied so the caller can record it. * * @param non-empty-string $requested * @param non-empty-string $fallback * * @return non-empty-string */function applyFontWithFallback(Document $doc, string $requested, string $fallback): string{ try { $doc->setFont($requested, '', 12);
return $requested; } catch (FontNotFoundException $e) { // STRATEGY 1 — graceful degradation on a font failure. logRecovery('Font unavailable; degrading to a guaranteed face', [ 'exception' => $e::class, 'font_name' => $e->getFontName(), 'searched' => $e->getSearchPaths(), 'fallback' => $fallback, ]); $doc->setFont($fallback, '', 12);
return $fallback; }}
/** * Render HTML through the in-process pipeline, then through the Chrome bridge, * then through a simplified HTML variant. Each layer recovers a more specific * failure than the last. */function renderHtmlWithRecovery(Document $doc, string $primaryHtml, string $simplifiedHtml): void{ try { // Primary path: the in-process HTML/CSS pipeline. $doc->writeHtml($primaryHtml);
return; } catch (CssResolutionBudgetExceededException $e) { // STRATEGY 3 — retry with alternative HTML for a pathological selector. logRecovery('CSS resolution budget exceeded; retrying with simplified HTML', [ 'exception' => $e::class, 'visits' => $e->getVisits(), 'budget' => $e->getBudget(), ]); $doc->writeHtml($simplifiedHtml);
return; } catch (HtmlParsingException $e) { // STRATEGY 2 — fall back to the Chrome renderer for input the // in-process parser rejects. The Chrome bridge uses a browser CSS // engine, so it may accept what the in-process parser would not. logRecovery('In-process HTML parse failed; trying the Chrome fallback renderer', [ 'exception' => $e::class, 'rule' => $e->getRule(), 'position' => $e->getPosition(), ]);
try { $doc->writeHtmlChrome($primaryHtml);
return; } catch (PageLayoutException $chromeError) { // The Chrome bridge is absent (nextpdf/artisan not installed) or // rejected the input. Last resort: the simplified HTML variant // through the in-process pipeline. logRecovery('Chrome fallback unavailable; retrying with simplified HTML', [ 'exception' => $chromeError::class, ]); $doc->writeHtml($simplifiedHtml);
return; } }}
// --- Configure the degradation policy up front ---------------------------// Balanced (the default) warns on bounded degradation and throws only on a// blocking impact. A regulated workflow would choose DegradationPolicy::Strict.$config = (new Config())->withDegradationPolicy(DegradationPolicy::Balanced);$doc = Document::createStandalone($config);
$primaryHtml = '<h1>Quarterly report</h1><p>Body paragraph with rich styling.</p>';$simplifiedHtml = '<h1>Quarterly report</h1><p>Body paragraph (simplified).</p>';
$outputPath = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf';
try { $doc->addPage(); $applied = applyFontWithFallback($doc, 'CorporateSans', 'helvetica'); $doc->cell(0, 12, 'Custom error recovery patterns', newLine: true);
renderHtmlWithRecovery($doc, $primaryHtml, $simplifiedHtml);
$doc->save($outputPath);
logRecovery('Document built', [ 'font_applied' => $applied, 'pages' => $doc->getNumPages(), 'has_warnings' => $doc->hasWarnings(), 'degraded_parity' => $doc->hasDegradedParity(), ]);} catch (DegradedException $e) { // BOUNDARY: DegradedException extends RuntimeException directly, NOT // NextPdfException, so the catch-all below would not have caught it. // Under Strict/Balanced policy a blocking degradation lands here. logRecovery('Capability degraded under the active policy; emitting a built partial', [ 'exception' => $e::class, 'capability' => $e->capability->id, 'status' => $e->capability->status->value, 'reason' => $e->capability->reason ?? 'unknown', 'policy' => $e->policy->value, ]); // STRATEGY 4 — partial-document recovery: save whatever pages exist. if ($doc->getNumPages() > 0) { $doc->save($outputPath); }} catch (WriterException $e) { // Serialization or I/O failure: the in-memory document is valid but could // not be written. Surface the stage so infrastructure can act on it. logRecovery('PDF write failed; document was valid in memory', [ 'exception' => $e::class, 'writer_state' => $e->getWriterState(), 'output_path' => $e->getOutputPath(), ]);} catch (NextPdfException $e) { // Catch-all for every other NextPDF\Exception\*. STRATEGY 4 again: if any // complete pages were built before the failure, emit them rather than // discarding the work. $context = ['exception' => $e::class, 'pages' => $doc->getNumPages()]; if ($e instanceof ContextAwareExceptionInterface) { $context += $e->getContext(); } logRecovery('Unrecovered NextPDF failure; attempting a partial save', $context); if ($doc->getNumPages() > 0) { $doc->save($outputPath); }}
fwrite(STDERR, "Recovery pipeline complete.\n");STDOUT bleibt für das Harness frei. Recovery-Diagnosen gehen an STDERR, und das PDF wird nur nach NEXTPDF_COOKBOOK_OUTPUT geschrieben.
Grenzfälle & Fallstricke
Abschnitt betitelt „Grenzfälle & Fallstricke“- Ordnen Sie catch-Blöcke von spezifisch nach allgemein. PHP greift auf den ersten passenden
catchzu. Stellen Siecatch (NextPdfException $e)vorcatch (WriterException $e), wird der spezifische Block zu totem Code, weilWriterExceptionNextPdfExceptionerweitert. DegradedExceptionsteht außerhalb der Hierarchie. Sie erweitertRuntimeException, nichtNextPdfException. Eine Pipeline, die nurNextPdfExceptionabfängt, lässt eine Ablehnung unter der Strict-Policy ungefangen durchlaufen. Fangen SieDegradedException(oder eine breitereRuntimeException) ab, wenn eine von der Voreinstellung abweichende Degradierungs-Policy aktiv ist.- Auch ein Font-Fallback kann scheitern. Wenn Ihre Fallback-Schriftart selbst nicht registriert ist, wirft das zweite
setFont()erneut. Verwenden Sie einen Base14-Alias wiehelvetica, den die Engine ohne Dateisystem-Lookup auflöst, oder registrieren Sie beim Start überaddFontDirectory()eine mitgelieferte Schriftart, damit der Fallback garantiert ist. getNumPages()zählt die aktive, noch nicht geflushte Seite mit. Es gibt die geflushte Seitenzahl plus eins zurück, wenn gerade eine Seite geöffnet ist. Ein „Teil-Speichern“ umfasst also die Seite, die beim Auftreten des Fehlers gerade aufgebaut wurde — was meist genau das ist, was Sie wollen. Benötigen Sie nur vollständig fertige Seiten, verzweigen Sie zusätzlich anhand vongetPage().- Der Chrome-Fallback verändert die Treue, nicht nur die Verfügbarkeit. Die In-Process-Pipeline und die Chrome-Brücke nutzen unterschiedliche Layout-Engines, sodass ein Dokument, das auf Chrome ausweicht, anders aussehen kann. Behandeln Sie den Fallback als Recovery, nicht als transparenten Ersatz, und halten Sie fest, welcher Pfad die Ausgabe erzeugt hat.
- Ein erneuter Versuch muss erprobte Eingaben verwenden. Der erneute Versuch mit vereinfachtem HTML hilft nur, wenn die vereinfachte Variante wirklich einfacher ist — weniger verschachtelte Selektoren, keine
:has()-Ketten, die das Auflösungsbudget aufzehren. Ein erneuter Versuch mit derselben Eingabe, die schon gescheitert ist, läuft in dieselbe Exception. - Inspizieren Sie Warnungen nach einem sauberen Durchlauf. Ein Render, der ohne Wurf zurückkehrt, kann trotzdem degradiert sein. Prüfen Sie
hasDegradedParity()und lesen SiegetWarnings(), bevor Sie die Ausgabe als pixelgenau behandeln; unterDegradationPolicy::Permissiveist jede Degradierung eine Warnung, nie eine Exception.
Performance
Abschnitt betitelt „Performance“- Recovery verursacht nur auf dem Fehlerpfad zusätzliche Kosten. NextPDF wirft bei Ausnahmezuständen; daher verursacht ein sauberer Render keine zusätzlichen Kosten für das umgebende
try/catch. - Ein Renderer-Fallback führt den Render-Vorgang erneut aus. Der In-Process-Versuch wird verworfen und der Chrome-Versuch beginnt von vorn, sodass ein Fallback-Render im schlimmsten Fall beide Render-Zeiten plus den prozessübergreifenden Round-Trip zu Chrome kostet. Planen Sie das ein, wenn Sie Request-Timeouts setzen.
- Ein erneuter Versuch mit alternativem HTML parst ein zweites Dokument. Halten Sie die vereinfachte Variante klein, damit der erneute Versuch im Vergleich zum primären Versuch günstig ist.
- Ein Teil-Speichern serialisiert die bereits aufgebauten Seiten. Seine Kosten skalieren mit der Anzahl der erhaltenen Seiten, nicht mit der Arbeit, die gescheitert ist.
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“- Geben Sie rohe Exception-Meldungen oder Dateisystempfade nicht an Endnutzer weiter. Eine
FontNotFoundException-Meldung enthält die durchsuchten Verzeichnisse und eineWriterExceptionenthält den Ausgabepfad; beide geben das Serverlayout preis. Protokollieren Sie den strukturierten Kontext serverseitig und geben Sie dem Aufrufer eine generische Meldung zurück. - Behandeln Sie HTML bei jedem Wiederholungsversuch als nicht vertrauenswürdige Eingabe. Der Fallback und der erneute Versuch mit vereinfachtem HTML laufen beide durch dieselbe Eingabegrenze; die In-Process-Pipeline und die Chrome-Brücke wenden jeweils ihre eigene HTML-Sicherheits-Policy an, und ein erneuter Versuch lockert diese Validierung nicht. Nehmen Sie nicht an, dass eine „vereinfachte“ Variante sicherer ist, nur weil Sie sie geschrieben haben.
- Auch ein Teil-Speichern schreibt eine Datei. Wenden Sie auf eine Teilausgabe dieselbe Pfadvalidierung, dieselben Berechtigungen und dieselben Speicherort-Regeln an wie auf eine vollständige.
Document::save()lehnt Stream-Wrapper und Null-Bytes ab und löst das übergeordnete Verzeichnis auf, um Path-Traversal zu blockieren, aber das Ziel, das Sie übergeben, liegt in Ihrer Verantwortung.
Konformität
Abschnitt betitelt „Konformität“Dieses Recipe erhebt keinen normativen Standardanspruch. Es setzt die öffentlichen NextPDF-APIs für Exceptions und Dokumentinspektion zu einem Recovery-Kontrollfluss zusammen; es behauptet kein Verhalten, das durch ISO 32000-2 oder einen anderen Standard definiert ist, und trägt daher keinen citations:-Block.
Es wird mit dem semantischen Reproduzierbarkeitsprofil verifiziert. Das wiederhergestellte Dokument trägt im Trailer ein /ID und ein Änderungsdatum, die bei jedem Speichern neu erzeugt werden, sodass Byte-Identität nicht erreichbar ist. Der Vergleich von strukturellem AST plus reinen Metadaten ist über mehrere Durchläufe hinweg stabil.
Siehe auch
Abschnitt betitelt „Siehe auch“- Fehler mit der NextPDF-Exception-Hierarchie behandeln — Abfang-Granularität und strukturierter Kontext, die Grundlage, auf der diese Seite aufbaut.
- Exception-Modul — die vollständige Exception-Referenz.
- Support-Modul —
DegradedException,Capability,Warningund die Degradierungstypen. - Config-Modul — Konfiguration der Degradierungs-Policy.
- PDFs sicher in einem langlaufenden Worker rendern — Recovery in einem Worker, der gemeinsam genutzte Registries wiederverwendet.