İçeriğe geç

Özel hata kurtarma ve yeniden deneme stratejilerini uygulayın

Üretim ortamındaki bir belge hizmeti, bir istisnayı yakalayıp günlüğe yazmakla yetinmez. Sonrasında ne yapacağına karar verir: kademe düşürülmüş çıktıyla devam etmek, ikinci bir işleme yoluna geçmek, motorun kabul ettiği girdiyle yeniden denemek veya hatadan önce oluşturulan sayfaları teslim etmek. Bu tarif, NextPDF’in istisna hiyerarşisi ve belge durumunu denetleme yöntemleri üzerine kurulu dört kurtarma stratejisini gösterir:

  • Yazı tipi hatasında denetimli kademe düşürmeNextPDF\Exception\FontNotFoundException yakalayın, garantili bir yazı tipine geri dönün ve belgeyi oluşturmaya devam edin.
  • Yedek işleyici — süreç içi Document::writeHtml() yolu girdiyi reddettiğinde, Document::writeHtmlChrome() ile, yani nextpdf/artisan Chrome köprüsü üzerinden yeniden deneyin.
  • Alternatif HTML ile yeniden denemeNextPDF\Exception\HtmlParsingException veya NextPDF\Exception\CssResolutionBudgetExceededException oluştuğunda, basitleştirilmiş ve çalıştığı bilinen bir HTML çeşidiyle yeniden deneyin.
  • Kısmi belge kurtarma — bir hatadan sonra Document::getNumPages() okuyun ve o ana kadar oluşturulan içeriği atmak yerine kaydedin.

Hangi düzeyde yakalama yapmanız gerektiğini zaten biliyorsunuz. Eşlik eden NextPDF istisna hiyerarşisiyle hataları işleyin sayfası hiyerarşinin kendisini kapsar. Bu sayfa ise yakalamadan sonra ne yapacağınızı gösterir.

Bu tarif, açık kaynak yazılım (OSS) olan core sürümünü hedefler. Burada adı geçen her API nextpdf/core içinde bulunur. Tek isteğe bağlı bağımlılık, Chrome yedeği için nextpdf/artisan paketidir.

Terminal window
composer require nextpdf/core:^3

Yedek işleyici stratejisi ayrıca Chrome köprüsünü kullanır:

Terminal window
composer require nextpdf/artisan

Paket nextpdf/artisan bulunmadığında, Document::writeHtmlChrome() işlemek yerine NextPDF\Exception\PageLayoutException fırlatır. Aşağıdaki yedek stratejisi, eksik bir köprüyü kurtarılabilir başka bir durum olarak ele alır.

Kurtarma, NextPDF hakkında her ikisi de kaynak üzerinden doğrulanmış iki gerçeğe dayanır.

İstisna hiyerarşisi, neyin kurtarılabilir olduğunu gösterir. Etki alanına özgü her istisna, soyut temel sınıf NextPDF\Exception\NextPdfException sınıfını genişletir; bu sınıf da RuntimeException sınıfını genişletir ve NextPDF\Contracts\ContextAwareExceptionInterface arabirimini uygular. İlgili hata için bir kurtarma yolu seçmek üzere belirli bir alt türü yakalayın:

  • FontNotFoundException şu bilgileri taşır: getFontName(), getSearchPaths() ve wasFallbackAttempted() — farklı bir yazı tipiyle yeniden denemek için yeterlidir.
  • HtmlParsingException şu bilgileri taşır: getRule(), getPosition() ve getHtmlSnippet() — basitleştirilmiş bir yeniden denemenin değip değmeyeceğine karar vermek için yeterlidir.
  • CssResolutionBudgetExceededException şu bilgileri taşır: getVisits() ve getBudget() — sadeleştirilmiş bir stil sayfasının patolojik bir seçiciyi giderebileceğine işaret eder.
  • Önemli bir sınır: NextPDF\Support\DegradedException, RuntimeException sınıfını doğrudan genişletir, NextPdfException sınıfını değil. Bu nedenle catch (NextPdfException $e), kademe düşürme ilkesi kaynaklı bir reddi yakalamaz. Etkin NextPDF\Contracts\DegradationPolicy ilkesi Strict veya Balanced olduğunda, bu durumdan kurtulmak için DegradedException istisnasını açıkça yakalayın.

Belge, siz onu oluştururken denetlenebilir. Bir Document, oluşturma durumunu salt okunur erişimcilerle sunar. getNumPages(), etkin ve henüz boşaltılmamış sayfa dahil olmak üzere toplam sayfa sayısını döndürür ve getPage(), geçerli sayfanın sıfır tabanlı dizinini döndürür. Oluşturma sırasında oluşan bir hatadan sonra, tamamlanmış sayfa olup olmadığını öğrenmek için getNumPages() okuyun, ardından bunları dışarı aktarmak için save() veya getPdfData() çağırın. Motor ayrıca ölümcül olmayan kademe düşürme olaylarını da kaydeder: getWarnings() bir list<NextPDF\Support\Warning> döndürür, hasWarnings() bunlardan herhangi birinin toplanıp toplanmadığını bildirir ve hasDegradedParity() çıktı doğruluğunun etkilenip etkilenmediğini bildirir. Bu yöntemler, bir kurtarma rutininin istisnayı ayrıştırmadan “temiz biçimde başarılı oldu” ile “azaltılmış doğrulukla başarılı oldu” durumlarını ayırt etmesine olanak tanır.

Kademe düşürme ilkesi, hangi olayları istisna olarak, hangilerini uyarı olarak işleyeceğinizi denetler. NextPDF\Core\Config varsayılan olarak DegradationPolicy::Balanced kullanır; bu ilke sınırlı kademe düşürmelerde uyarı üretip devam eder, ancak engelleyici bir etki olduğunda fırlatır. DegradationPolicy::Permissive asla fırlatmaz ve her şeyi uyarı kanalında toplar. DegradationPolicy::Strict herhangi bir uyumluluk riskinde, anlamsal kayıpta veya engelleyici etkide fırlatır. Önce ilkeyi seçin, ardından o ilkenin ürettiği hata biçimleri için kurtarma yazın.

Aşağıdaki kurtarma kodu şu doğrulanmış üyeleri kullanır:

  • 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): self ve degradationPolicy varsayılanı DegradationPolicy::Balanced.
  • NextPDF\Contracts\DegradationPolicyStrict, Balanced, Permissive.
  • NextPDF\Exception\NextPdfException (soyut temel sınıf), NextPDF\Exception\FontNotFoundException, NextPDF\Exception\HtmlParsingException, NextPDF\Exception\CssResolutionBudgetExceededException, NextPDF\Exception\WriterException, NextPDF\Exception\PageLayoutException.
  • NextPDF\Support\DegradedException (capability ve policy taşıyan), NextPDF\Support\Capability (id, status, reason, isDegraded()), NextPDF\Support\Warning, NextPDF\Support\WarningSeverity.

En küçük işe yarar kurtarma, eksik bir yazı tipi hatasını yakalar, garantili bir yazı tipine geri döner ve devam eder. Bu parça, üretim örneğindeki daha geniş işleme mantığını kapsamaz. Günlük kaydı ve DegradedException sınırını içeren eksiksiz bir işleyici için aşağıdaki üretim örneğini okuyun.

<?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');

Tam örnek, dört stratejinin tümünü tek bir işleme hattında birleştirir: yazı tipi yedeği, süreç içi yoldan Chrome’a geçen işleyici yedeği, alternatif HTML yeniden denemesi ve getNumPages() tarafından yönlendirilen kısmi belge kurtarma. Harness, çıktı kanalı sözleşmesine uyar ve hiçbir zaman çıplak bir Exception yakalamaz veya bir catch bloğunu boş bırakmaz.

<?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, harness için boş kalır. Kurtarma tanılamaları STDERR kanalına gider ve PDF dosyası yalnızca NEXTPDF_COOKBOOK_OUTPUT konumuna yazılır.

  • catch bloklarını özelden genele doğru sıralayın. PHP, ilk uyumlu catch bloğuyla eşleşir. catch (NextPdfException $e) bloğunu catch (WriterException $e) bloğundan önce yerleştirmek, özel bloğu ulaşılamaz hale getirir; çünkü WriterException, NextPdfException sınıfını genişletir.
  • DegradedException hiyerarşinin dışında yer alır. RuntimeException sınıfını genişletir, NextPdfException sınıfını değil. Yalnızca NextPdfException yakalayan bir iş hattı, katı ilke kaynaklı bir reddin yakalanmadan yayılmasına izin verir. Varsayılan olmayan bir kademe düşürme ilkesi etkin olduğunda DegradedException (veya daha geniş bir RuntimeException) yakalayın.
  • Yedek yazı tipi de başarısız olabilir. Yedek yazı tipiniz de kayıtlı değilse, ikinci setFont() çağrısı yeniden fırlatır. Motorun bir dosya sistemi araması yapmadan çözdüğü helvetica gibi bir Base14 takma adı kullanın veya yedeği garanti altına almak için başta addFontDirectory() ile paketlenmiş bir yazı tipi kaydedin.
  • getNumPages() etkin ve boşaltılmamış sayfayı sayar. Bir sayfa açıkken, boşaltılmış sayfa sayısına bir eklenmiş değeri döndürür. Bir “kısmi kaydetme”, hata oluştuğunda oluşturulmakta olan sayfayı içerir; bu genellikle istediğiniz şeydir. Yalnızca tamamen tamamlanmış sayfalara ihtiyacınız varsa, ayrıca getPage() üzerinde dallanma yapın.
  • Chrome yedeği yalnızca kullanılabilirliği değil, doğruluğu da değiştirir. Süreç içi yol ve Chrome köprüsü farklı yerleşim motorları kullanır, dolayısıyla Chrome’a geri dönen bir belge farklı görünebilir. Yedeği şeffaf bir ikame olarak değil, bir kurtarma olarak ele alın ve çıktıyı hangi yolun ürettiğini kaydedin.
  • Bir yeniden deneme, çalıştığı bilinen girdiyi kullanmalıdır. Basitleştirilmiş HTML yeniden denemesi yalnızca basitleştirilmiş çeşidin gerçekten daha basit olduğu durumda işe yarar: daha az iç içe seçici içermeli ve çözümleme bütçesini tüketen :has() zincirleri barındırmamalıdır. Zaten başarısız olmuş aynı girdiyle yeniden denemek, aynı istisnada döngüye girer.
  • Temiz bir çalıştırmadan sonra uyarıları inceleyin. İstisna fırlatmadan tamamlanan bir işleme, yine de kademe düşürmeyle tamamlanmış olabilir. Çıktıyı piksel düzeyinde sadık kabul etmeden önce hasDegradedParity() denetleyin ve getWarnings() okuyun; DegradationPolicy::Permissive altında her kademe düşürme bir uyarıdır, asla bir istisna değildir.
  • Kurtarma yalnızca hata yolunda maliyet ekler. NextPDF istisnai durumlarda fırlattığından, sorunsuz bir işleme çevresindeki try/catch için ek maliyet üstlenmez.
  • İşleyici yedeği işlemeyi yeniden çalıştırır. Süreç içi deneme atılır ve Chrome denemesi sıfırdan başlar, dolayısıyla yedek işleme en kötü durumda her iki işleme süresine ek olarak Chrome’a süreçler arası gidiş dönüş maliyetine mal olur. İstek zaman aşımı değerlerini ayarlarken bunu hesaba katın.
  • Alternatif HTML yeniden denemesi ikinci bir belgeyi ayrıştırır. Yeniden denemenin birincil denemeye göre ucuz kalması için basitleştirilmiş çeşidi küçük tutun.
  • Kısmi kaydetme, halihazırda oluşturulmuş sayfaları seri hale getirir. Maliyeti, başarısız olan işe göre değil, hayatta kalan sayfa sayısına göre ölçeklenir.
  • Ham istisna iletilerini veya dosya sistemi yollarını son kullanıcılara göstermeyin. Bir FontNotFoundException iletisi aranan dizinleri içerir ve bir WriterException çıktı yolunu içerir; her ikisi de sunucu dizin yapısını sızdırır. Yapılandırılmış bağlamı sunucu tarafında günlüğe yazın ve çağırana genel bir ileti döndürün.
  • Yeniden denenen HTML’yi her denemede güvenilmeyen girdi olarak ele alın. Yedek yol ve basitleştirilmiş HTML yeniden denemesi aynı girdi sınırından geçer; süreç içi yol ve Chrome köprüsü her biri kendi HTML güvenlik ilkesini uygular ve yeniden deneme bu doğrulamayı gevşetmez. Bir “basitleştirilmiş” çeşidi, sırf siz yazdınız diye daha güvenli kabul etmeyin.
  • Kısmi kaydetme yine de dosya yazar. Eksiksiz bir çıktıya uyguladığınız aynı yol doğrulamasını, izinleri ve depolama konumu kurallarını kısmi çıktıya da uygulayın. Document::save() akış sarmalayıcılarını ve boş baytları reddeder ve yol geçişini engellemek için üst dizini çözer, ancak ilettiğiniz hedef sizin sorumluluğunuzdadır.

Bu tarif normatif bir standart iddiasında bulunmaz. NextPDF’in genel istisna ve belge denetleme API’lerini kurtarma denetim akışına dönüştürür; ISO 32000-2 veya başka herhangi bir standart tarafından tanımlanan davranışı öne sürmez, dolayısıyla hiçbir citations: bloğu taşımaz.

Bu sayfa anlamsal yeniden üretilebilirlik profiliyle doğrulanır. Kurtarılan belge, her kaydetmede yeniden oluşturulan bir fragman /ID ve değişiklik tarihi taşır, dolayısıyla bayt özdeşliği elde edilemez. Yapısal soyut sözdizimi ağacı (AST) ve yalnızca üst veri karşılaştırması, çalıştırmalar arasında kararlı kalır.