Własne strategie odzyskiwania po błędach i ponawiania prób
W skrócie
Dział zatytułowany „W skrócie”Produkcyjna usługa generowania dokumentów nie kończy na przechwyceniu i zarejestrowaniu wyjątku. Musi zdecydować, co dalej: kontynuować z obniżoną jakością wyniku, przełączyć się na alternatywną ścieżkę renderowania, ponowić próbę z danymi wejściowymi akceptowanymi przez silnik albo dostarczyć strony zbudowane przed wystąpieniem błędu. Ten przepis przedstawia cztery strategie odzyskiwania oparte na hierarchii wyjątków NextPDF oraz metodach badania stanu dokumentu:
- Łagodna degradacja przy błędzie czcionki — przechwyć
NextPDF\Exception\FontNotFoundException, przełącz się na gwarantowany krój i kontynuuj budowanie dokumentu. - Zapasowy renderer — gdy ścieżka
Document::writeHtml()działająca w procesie odrzuci dane wejściowe, ponów próbę przezDocument::writeHtmlChrome(), czyli mostnextpdf/artisando Chrome. - Ponowienie próby z alternatywnym kodem HTML — gdy wystąpi
NextPDF\Exception\HtmlParsingExceptionlubNextPDF\Exception\CssResolutionBudgetExceededException, ponów próbę z uproszczonym, sprawdzonym wariantem HTML. - Odzyskiwanie częściowego dokumentu — odczytaj
Document::getNumPages()po błędzie i zapisz to, co zostało już zbudowane, zamiast je odrzucać.
Wiesz już, jak przechwytywać wyjątki na odpowiednim poziomie. Powiązana strona Obsługa błędów z hierarchią wyjątków NextPDF opisuje samą hierarchię. Ta strona pokazuje, co zrobić po przechwyceniu.
Ten przepis dotyczy edycji core open source software (OSS). Wszystkie elementy application programming interface (API) wymienione tutaj znajdują się w nextpdf/core. Jedyną opcjonalną zależnością jest nextpdf/artisan na potrzeby rozwiązania zapasowego opartego na Chrome.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/core:^3Strategia zapasowego renderera dodatkowo korzysta z mostu do Chrome:
composer require nextpdf/artisanGdy brakuje nextpdf/artisan, Document::writeHtmlChrome() zamiast renderować, rzuca NextPDF\Exception\PageLayoutException. Strategia zapasowa przedstawiona poniżej traktuje brakujący most jako kolejny przypadek możliwy do obsłużenia w ramach odzyskiwania.
Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”Odzyskiwanie zależy od dwóch faktów dotyczących NextPDF; oba zweryfikowano względem kodu źródłowego.
Hierarchia wyjątków wskazuje, które błędy można obsłużyć odzyskiwaniem. Każdy wyjątek domenowy rozszerza abstrakcyjną klasę bazową NextPDF\Exception\NextPdfException, która rozszerza RuntimeException i implementuje NextPDF\Contracts\ContextAwareExceptionInterface. Przechwyć konkretny podtyp, aby wybrać ścieżkę odzyskiwania dla danego błędu:
FontNotFoundExceptionudostępniagetFontName(),getSearchPaths()orazwasFallbackAttempted()— wystarczająco dużo informacji, aby ponowić próbę z innym krojem.HtmlParsingExceptionudostępniagetRule(),getPosition()orazgetHtmlSnippet()— wystarczająco dużo informacji, aby zdecydować, czy warto podjąć uproszczone ponowienie próby.CssResolutionBudgetExceededExceptionudostępniagetVisits()orazgetBudget()— to sygnał, że okrojony arkusz stylów może rozwiązać problem z patologicznym selektorem.- Istotna granica:
NextPDF\Support\DegradedExceptionrozszerzaRuntimeExceptionbezpośrednio, a nieNextPdfException. Dlategocatch (NextPdfException $e)nie przechwytuje odrzucenia wynikającego z zasad degradacji. Gdy aktywnąNextPDF\Contracts\DegradationPolicyjestStrictlubBalanced, jawnie przechwyćDegradedException, aby móc odzyskać działanie po takim odrzuceniu.
Dokument można badać podczas budowania. Klasa Document udostępnia stan budowania przez akcesory tylko do odczytu. getNumPages() zwraca całkowitą liczbę stron, łącznie z aktywną, jeszcze niezapisaną stroną, a getPage() zwraca liczony od zera indeks bieżącej strony. Po błędzie podczas budowania odczytaj getNumPages(), aby dowiedzieć się, czy powstały już jakiekolwiek kompletne strony, a następnie wywołaj save() lub getPdfData(), aby je wyemitować. Silnik rejestruje również niekrytyczne zdarzenia degradacji: getWarnings() zwraca list<NextPDF\Support\Warning>, hasWarnings() informuje, czy zebrano jakiekolwiek ostrzeżenia, a hasDegradedParity() informuje, czy degradacja wpłynęła na wierność wyniku. Te metody pozwalają procedurze odzyskiwania odróżnić „zakończono powodzeniem bez zastrzeżeń” od „zakończono powodzeniem z obniżoną wiernością” bez analizowania wyjątku.
Zasady degradacji decydują, które zdarzenia są obsługiwane jako wyjątki, a które jako ostrzeżenia. NextPDF\Core\Config domyślnie używa DegradationPolicy::Balanced, która ostrzega i kontynuuje przy ograniczonej degradacji, ale rzuca wyjątek przy wpływie blokującym. DegradationPolicy::Permissive nigdy nie rzuca wyjątku i zbiera wszystko w kanale ostrzeżeń. DegradationPolicy::Strict rzuca wyjątek przy każdym ryzyku zgodności, utracie znaczenia lub wpływie blokującym. Najpierw wybierz zasady, a następnie napisz odzyskiwanie dla rodzajów błędów, które te zasady wytwarzają.
Powierzchnia API
Dział zatytułowany „Powierzchnia API”Poniższy kod odzyskiwania korzysta z następujących zweryfikowanych składowych:
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): selforaz domyślna wartośćdegradationPolicyrównaDegradationPolicy::Balanced.NextPDF\Contracts\DegradationPolicy—Strict,Balanced,Permissive.NextPDF\Exception\NextPdfException(abstrakcyjna klasa bazowa),NextPDF\Exception\FontNotFoundException,NextPDF\Exception\HtmlParsingException,NextPDF\Exception\CssResolutionBudgetExceededException,NextPDF\Exception\WriterException,NextPDF\Exception\PageLayoutException.NextPDF\Support\DegradedException(zawierającacapabilityipolicy),NextPDF\Support\Capability(id,status,reason,isDegraded()),NextPDF\Support\Warning,NextPDF\Support\WarningSeverity.
Przykład kodu — szybki start
Dział zatytułowany „Przykład kodu — szybki start”Najprostszy użyteczny wariant odzyskiwania przechwytuje błąd brakującej czcionki, przełącza dokument na gwarantowany krój i kontynuuje działanie. Fragment pomija szerszą obsługę z przykładu produkcyjnego. Pełną obsługę z rejestrowaniem oraz granicą DegradedException znajdziesz w przykładzie produkcyjnym poniżej.
<?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');Przykład kodu — wersja produkcyjna
Dział zatytułowany „Przykład kodu — wersja produkcyjna”Pełny przykład łączy wszystkie cztery strategie w jednym potoku renderowania: rozwiązanie zapasowe dla czcionki, zapasowy renderer przełączający ze ścieżki działającej w procesie na Chrome, ponowienie próby z alternatywnym kodem HTML oraz odzyskiwanie częściowego dokumentu sterowane przez getNumPages(). Uwzględnia kanał wyjściowy szkieletu testowego i nigdy nie przechwytuje gołego Exception ani nie zostawia pustego bloku catch.
<?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 pozostaje dostępny dla szkieletu testowego. Diagnostyka odzyskiwania trafia do STDERR, a plik w formacie Portable Document Format (PDF) jest zapisywany wyłącznie do NEXTPDF_COOKBOOK_OUTPUT.
Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- Porządkuj bloki catch od szczegółowych do ogólnych. PHP dopasowuje pierwszy zgodny
catch. Umieszczeniecatch (NextPdfException $e)przedcatch (WriterException $e)zamienia ten szczegółowy blok w martwy kod, ponieważWriterExceptionrozszerzaNextPdfException. DegradedExceptionznajduje się poza hierarchią. RozszerzaRuntimeException, a nieNextPdfException. Potok przechwytujący tylkoNextPdfExceptionsprawia, że odrzucenie wynikające z zasad Strict propaguje się bez przechwycenia. PrzechwyćDegradedException(lub szerszyRuntimeException), gdy aktywne są zasady degradacji inne niż domyślne.- Rozwiązanie zapasowe dla czcionki też może zawieść. Jeśli sam krój zapasowy nie jest zarejestrowany, drugie wywołanie
setFont()ponownie rzuca wyjątek. Użyj aliasu Base14, takiego jakhelvetica, który silnik rozwiązuje bez odwołania do systemu plików, albo zarejestruj dołączony krój przezaddFontDirectory()przy starcie, aby rozwiązanie zapasowe było gwarantowane. getNumPages()liczy aktywną, jeszcze niezapisaną stronę. Zwraca liczbę zapisanych stron powiększoną o jeden, gdy jakaś strona jest aktualnie otwarta. „Częściowy zapis” obejmuje stronę, która była budowana w chwili wystąpienia błędu, co zwykle jest pożądane. Jeśli potrzebujesz wyłącznie w pełni ukończonych stron, dodaj też rozgałęzienie wedługgetPage().- Rozwiązanie zapasowe oparte na Chrome zmienia wierność, a nie tylko dostępność. Potok działający w procesie oraz most do Chrome korzystają z różnych silników układu, więc dokument przełączony na Chrome może wyglądać inaczej. Traktuj rozwiązanie zapasowe jako odzyskiwanie, a nie niezauważalne zastępstwo, i odnotuj, która ścieżka wytworzyła wynik.
- Ponowienie próby musi używać sprawdzonych danych wejściowych. Ponowienie próby z uproszczonym kodem HTML pomaga tylko wtedy, gdy uproszczony wariant jest naprawdę prostszy: mniej zagnieżdżonych selektorów, brak łańcuchów z
:has(), które wyczerpują budżet rozwiązywania. Ponawianie próby z tymi samymi danymi wejściowymi, które już zawiodły, tylko wraca do tego samego wyjątku. - Sprawdzaj ostrzeżenia po bezbłędnym przebiegu. Renderowanie, które kończy się bez rzucenia wyjątku, i tak mogło ulec degradacji. Sprawdź
hasDegradedParity()i odczytajgetWarnings(), zanim potraktujesz wynik jako wierny co do piksela; przyDegradationPolicy::Permissivekażda degradacja jest ostrzeżeniem, nigdy wyjątkiem.
Wydajność
Dział zatytułowany „Wydajność”- Odzyskiwanie dodaje koszt wyłącznie na ścieżce błędu. NextPDF rzuca wyjątek w stanach wyjątkowych, więc bezbłędne renderowanie nie ponosi kosztu otaczającego
try/catch. - Rozwiązanie zapasowe renderera uruchamia renderowanie ponownie. Próba w procesie jest odrzucana, a próba w Chrome zaczyna się od nowa, więc renderowanie zapasowe kosztuje w najgorszym przypadku oba czasy renderowania plus narzut komunikacji międzyprocesowej z Chrome. Uwzględnij to w budżecie przy ustawianiu limitów czasu żądań.
- Ponowienie próby z alternatywnym kodem HTML parsuje drugi dokument. Utrzymuj uproszczony wariant niewielkim, aby ponowienie próby było tanie względem próby podstawowej.
- Częściowy zapis serializuje strony, które zostały już zbudowane. Jego koszt skaluje się wraz z liczbą zachowanych stron, a nie z pracą, która zawiodła.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”- Nie pokazuj użytkownikom końcowym surowych komunikatów wyjątków ani ścieżek w systemie plików. Komunikat
FontNotFoundExceptionzawiera przeszukiwane katalogi, aWriterExceptionzawiera ścieżkę wyjściową; oba ujawniają układ serwera. Rejestruj strukturalny kontekst po stronie serwera i zwracaj wywołującemu ogólny komunikat. - Traktuj ponawiany kod HTML jako niezaufane dane wejściowe przy każdej próbie. Zarówno rozwiązanie zapasowe, jak i ponowienie próby z uproszczonym kodem HTML przekraczają tę samą granicę wejściową; potok działający w procesie oraz most do Chrome stosują własne zasady bezpieczeństwa HTML, a ponowienie próby nie osłabia tej walidacji. Nie zakładaj, że „uproszczony” wariant jest bezpieczniejszy tylko dlatego, że powstał we własnym kodzie.
- Częściowy zapis i tak tworzy plik. Do częściowego wyniku stosuj tę samą walidację ścieżki, uprawnienia i zasady wyboru lokalizacji przechowywania, które stosujesz do pełnego wyniku.
Document::save()odrzuca opakowania strumieni i bajty null oraz rozwiązuje katalog nadrzędny, aby zablokować przechodzenie po ścieżkach, ale za przekazywane miejsce docelowe odpowiada wywołujący.
Zgodność
Dział zatytułowany „Zgodność”Ten przepis nie deklaruje żadnej normatywnej zgodności ze standardami. Łączy publiczne API wyjątków i badania dokumentów NextPDF w przepływ sterowania odzyskiwaniem; nie deklaruje zachowania zdefiniowanego przez ISO 32000-2 ani żaden inny standard, więc nie zawiera bloku citations:.
Ta strona jest weryfikowana przy użyciu semantycznego profilu odtwarzalności. Odzyskany dokument zawiera w trailerze /ID oraz datę modyfikacji, które są generowane na nowo przy każdym zapisie, więc identyczność bajtowa nie jest osiągalna. Porównanie strukturalnego drzewa składni abstrakcyjnej (AST) wraz z samymi metadanymi jest stabilne między uruchomieniami.
Zobacz też
Dział zatytułowany „Zobacz też”- Obsługa błędów z hierarchią wyjątków NextPDF — szczegółowość przechwytywania i ustrukturyzowany kontekst, czyli podstawa tej strony.
- Moduł wyjątków — pełna dokumentacja wyjątków.
- Moduł support —
DegradedException,Capability,Warningoraz typy degradacji. - Moduł config — konfiguracja zasad degradacji.
- Bezpieczne renderowanie plików PDF w długo działającym workerze — odzyskiwanie w workerze, który ponownie wykorzystuje współdzielone rejestry.