ข้ามไปยังเนื้อหา

การจัดการข้อผิดพลาดด้วยลำดับชั้นของ exception ใน NextPDF

NextPDF โยน exception แบบมีชนิดเมื่ออยู่ในสถานะผิดปกติ และไม่ซ่อนข้อผิดพลาดไว้หลังค่าคืน false หรือ null domain exception ทุกประเภทขยายมาจากคลาสฐานแบบ abstract เดียวกันคือ NextPdfException และเปิดเผยบริบทการวินิจฉัยแบบมีโครงสร้างผ่าน ContextAwareExceptionInterface สูตรนี้แสดงตำแหน่งที่ควรดักจับ และวิธีบันทึกบริบทแบบมีโครงสร้างลงในไปป์ไลน์การตรวจสอบประสิทธิภาพแอปพลิเคชัน (APM) นอกจากนี้ยังระบุด้วยว่าการดักจับแบบครอบคลุมทั้งหมดเพียงรายการเดียวไม่ครอบคลุมความล้มเหลวใดบ้าง

Terminal window
composer require nextpdf/core:^3

ไม่ต้องติดตั้ง extension เพิ่มเติมใดๆ

ลำดับชั้นมีดังนี้:

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

ลำดับชั้นนี้มีผลเชิงปฏิบัติสองประการ และทั้งสองประการตรวจสอบเทียบกับซอร์สแล้ว:

  1. catch (NextPdfException $e) ดักจับทุก exception ภายใต้ NextPDF\Exception รวมถึงการละเมิดโหมด strict ด้วย เพราะทั้งหมดขยายมาจากคลาสฐานแบบ abstract
  2. ไม่ดักจับทุกอย่างที่ไลบรารีอาจโยนออกมา NextPDF\Support\DegradedException ขยายมาจาก RuntimeException โดยตรง ไม่ใช่ NextPdfException ดังนั้น catch (NextPdfException $e) จึงไม่ดักจับกรณีที่นโยบายการลดทอนความสามารถปฏิเสธการทำงาน หากต้องการจัดการกรณีดังกล่าว ให้ดักจับ DegradedException (หรือ RuntimeException ที่กว้างกว่า) อย่างชัดเจน สูตรนี้ทำให้ขอบเขตดังกล่าวชัดเจน แทนที่จะถือว่าการดักจับแบบครอบคลุมทั้งหมดเพียงรายการเดียวให้ความครอบคลุมอย่างสมบูรณ์

NextPdfException::getContext() คืนค่าเป็น array<string, mixed> ที่มีคีย์รูปแบบ snake_case และมีเฉพาะค่าพื้นฐานหรือรายการของค่าพื้นฐานเท่านั้น คุณสามารถ serialize ค่านี้ลงในอาร์เรย์บริบทของ logger ตาม PSR-3 ได้โดยตรง PSR-3 §1.3 กำหนดให้วาง exception ไว้ใต้คีย์บริบท 'exception' ส่วน getContext() ของ NextPDF จะเพิ่มรายละเอียดเชิง domain ไว้ร่วมกับคีย์นั้น ไม่ใช่ใส่ตัวออบเจ็กต์ exception เอง

พื้นผิว API นี้อ้างอิงจาก PHPDoc ของ NextPDF\Exception\NextPdfException, NextPDF\Contracts\ContextAwareExceptionInterface, domain exception ที่เป็นรูปธรรม (เช่น NextPDF\Exception\FontNotFoundException ซึ่งมี getFontName() / getSearchPaths() / wasFallbackAttempted()) และ NextPDF\Support\DegradedException (ซึ่งมี Capability และ DegradationPolicy แนบมาด้วย) ตัวอย่างด้านล่างใช้ NextPdfException::getContext() และ accessor เฉพาะของแต่ละ exception

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

ตัวอย่างฉบับเต็มแสดงการดักจับแบบละเอียด การบันทึกบริบทแบบมีโครงสร้าง และขอบเขตของ DegradedException นอกจากนี้ยังคงรักษาช่องทางเอาต์พุตของ 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 ยังคงปล่อยว่างไว้สำหรับ harness ส่วนไฟล์ PDF จะถูกส่งไปยัง NEXTPDF_COOKBOOK_OUTPUT เท่านั้น

  • จัดเรียงบล็อก catch จากเฉพาะเจาะจง → ทั่วไป PHP จะจับคู่กับบล็อก catch แรกที่เข้ากันได้ catch (NextPdfException $e) ที่วางไว้ก่อน catch (FontNotFoundException $e) จะทำให้บล็อกที่เฉพาะเจาะจงกว่านั้นกลายเป็นโค้ดที่ไม่มีทางถูกเรียกใช้
  • DegradedException ไม่ใช่ NextPdfException จากการตรวจสอบเทียบกับซอร์ส คลาสนี้ขยายมาจาก RuntimeException catch (NextPdfException $e) เพียงรายการเดียวจะปล่อยให้กรณีปฏิเสธจากการลดทอนความสามารถแบบ strict แพร่กระจายต่อไป ให้ดักจับ exception นั้น (หรือ RuntimeException) อย่างชัดเจนเมื่อมีนโยบายการลดทอนความสามารถเข้ามาเกี่ยวข้อง
  • getContext() ปลอดภัยต่อ APM ตามสัญญา คีย์อยู่ในรูปแบบ snake_case ค่าเป็นค่าพื้นฐานหรือรายการของค่าพื้นฐาน โดยไม่มีออบเจ็กต์ซ้อนและไม่มี resource คุณสามารถ serialize ค่านี้ได้โดยตรง ค่านี้ไม่มีไบต์ของเอกสาร
  • อย่าแยกวิเคราะห์ข้อความของ exception ข้อความเหล่านี้มีไว้ให้มนุษย์อ่านและอาจเปลี่ยนแปลงได้ ให้ใช้ accessor แบบมีชนิด (getFontName(), capability->id เป็นต้น) และ getContext() เป็นพื้นผิวที่เสถียรสำหรับการประมวลผลโดยเครื่อง
  • ข้อควรระวังเรื่องจำนวนที่ล้าสมัย เอกสารเก่าอาจอ้างถึงค่าคงที่ “N domain exceptions” ลำดับชั้นนี้เติบโตขึ้นในแต่ละรุ่นที่ออกใหม่ ให้พึ่งพาชนิดฐาน NextPdfException และ instanceof อย่าพึ่งพาจำนวนที่ฮาร์ดโค้ดไว้เด็ดขาด
  • placeholder ของ PSR-3 ยังคงเป็นสตริง เมื่อบันทึก ให้คงข้อความไว้เป็นสตริงที่มีโทเค็น {placeholder} และวางค่าไว้ในอาร์เรย์บริบท (PSR-3 §1.2) อย่าแทรกออบเจ็กต์ exception เข้าไปในข้อความ

การจัดการ exception ไม่เพิ่มต้นทุนในสภาวะคงตัว NextPDF โยน exception เฉพาะเมื่ออยู่ในสถานะผิดปกติเท่านั้น และ getContext() สร้างอาร์เรย์ขนาดเล็กเฉพาะเมื่อถูกเรียกใช้ performance_budget (wall_ms: 2000, peak_mb: 96) กำหนดขอบเขตการรัน harness สำหรับสูตรนี้ ไม่ใช่สำหรับเอกสารทั่วไปใดๆ

  • getContext() ออกแบบมาให้ปลอดภัยต่อการบันทึก: มีเฉพาะค่าพื้นฐาน ไม่มีเพย์โหลดของเอกสาร และไม่มีไบต์ของไฟล์ อย่างไรก็ตาม ยังต้องรับผิดชอบต่อค่าที่เพิ่มเข้าไปในบริบทของบันทึกเอง ให้ล้างข้อมูลทุกอย่างที่ผู้ใช้ป้อนเข้ามา (เช่น เส้นทางไฟล์) ตามนโยบายการบันทึกที่ใช้อยู่ ก่อนที่ข้อมูลจะไปถึงปลายทาง
  • อย่าแสดงข้อความ exception แบบดิบให้ผู้ใช้ปลายทางเห็นในลักษณะที่เปิดเผยโครงสร้างของระบบไฟล์ ให้แสดงข้อความทั่วไป และบันทึกบริบทแบบมีโครงสร้างไว้ที่ฝั่งเซิร์ฟเวอร์
ข้อความยืนยันข้อกำหนดข้อรหัสอ้างอิง (reference_id)
ตาม PSR-3 ควรวาง exception ไว้ในบริบทของบันทึกภายใต้ exception ในฐานะคีย์บริบทPSR-3§1.3
ข้อความบันทึกยังคงเป็นสตริง และชื่อ placeholder จะถูกแมปไปยังคีย์บริบทPSR-3§1.2
วันที่แก้ไขจะถูกสร้างใหม่ทุกครั้งที่บันทึก ดังนั้นเอาต์พุตจึงเสถียรในเชิงโครงสร้าง (ไม่ใช่ระดับไบต์)ISO 32000-2§14.3

สูตรนี้ผ่านการตรวจสอบด้วยโปรไฟล์ความสามารถในการทำซ้ำแบบโครงสร้าง เอาต์พุตมีค่า /ID ใน trailer และวันที่แก้ไขที่ถูกสร้างใหม่ทุกครั้งที่บันทึก ดังนั้นจึงไม่สามารถบรรลุความเหมือนกันในระดับไบต์ได้ โครงสร้างที่ปรับให้เป็นมาตรฐานด้วย qpdf ยังคงเสถียร