Bỏ qua để đến nội dung

Xử lý lỗi dựa trên hệ thống phân cấp ngoại lệ của NextPDF

NextPDF ném ra các ngoại lệ có kiểu cho những trạng thái bất thường. Nó không bao giờ che giấu lỗi bằng giá trị trả về false hoặc null. Mọi ngoại lệ nghiệp vụ đều kế thừa cùng một lớp cơ sở trừu tượng, NextPdfException, và cung cấp ngữ cảnh chẩn đoán có cấu trúc thông qua ContextAwareExceptionInterface. Hướng dẫn này chỉ ra nên bắt ngoại lệ ở đâu và cách ghi nhật ký ngữ cảnh có cấu trúc cho một pipeline giám sát hiệu năng ứng dụng (APM). Hướng dẫn cũng làm rõ những lỗi nào mà một khối catch-all duy nhất không bao quát.

Terminal window
composer require nextpdf/core:^3

Bạn không cần thêm bất kỳ extension nào.

Hệ thống phân cấp có dạng như sau:

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

Hệ thống phân cấp này có hai hệ quả thực tế, cả hai đều đã được kiểm chứng từ mã nguồn:

  1. catch (NextPdfException $e) bắt mọi ngoại lệ nằm dưới NextPDF\Exception, bao gồm cả các vi phạm chế độ strict. Tất cả chúng đều kế thừa lớp cơ sở trừu tượng.
  2. catch (NextPdfException $e) không bắt mọi thứ mà thư viện có thể ném ra. NextPDF\Support\DegradedException kế thừa RuntimeException trực tiếp, không phải NextPdfException. Vì vậy một catch (NextPdfException $e) không bắt được một sự từ chối theo chính sách suy giảm. Để xử lý trường hợp này, hãy bắt DegradedException (hoặc RuntimeException rộng hơn) một cách tường minh. Hướng dẫn này làm rõ ranh giới đó thay vì coi một khối catch-all là bao quát đầy đủ.

NextPdfException::getContext() trả về một array<string, mixed> với các khóa snake_case và chỉ chứa các giá trị nguyên thủy hoặc danh sách các giá trị nguyên thủy. Bạn có thể tuần tự hóa nó trực tiếp vào mảng context của một logger PSR-3. PSR-3 §1.3 đặt ngoại lệ dưới khóa context 'exception'. getContext() của NextPDF bổ sung chi tiết nghiệp vụ bên cạnh khóa đó, chứ không phải chính đối tượng ngoại lệ.

Bề mặt API này được mô tả trong PHPDoc của NextPDF\Exception\NextPdfException, NextPDF\Contracts\ContextAwareExceptionInterface, các ngoại lệ nghiệp vụ cụ thể (ví dụ NextPDF\Exception\FontNotFoundException, với getFontName() / getSearchPaths() / wasFallbackAttempted()), và NextPDF\Support\DegradedException (mang theo CapabilityDegradationPolicy). Các ví dụ bên dưới sử dụng NextPdfException::getContext() và các accessor riêng cho từng ngoại lệ.

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

Ví dụ đầy đủ cho thấy cách bắt ngoại lệ theo kiểu cụ thể, ghi nhật ký ngữ cảnh có cấu trúc và ranh giới DegradedException. Ví dụ này cũng giữ nguyên kênh đầu ra của 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 được giữ trống cho harness; tệp PDF chỉ được ghi tới NEXTPDF_COOKBOOK_OUTPUT.

  • Sắp xếp các khối catch theo thứ tự cụ thể → tổng quát. PHP khớp với khối catch tương thích đầu tiên. Một catch (NextPdfException $e) đặt trước catch (FontNotFoundException $e) sẽ khiến khối cụ thể trở thành mã chết.
  • DegradedException không phải là NextPdfException. Qua kiểm chứng mã nguồn, nó kế thừa RuntimeException. Một catch (NextPdfException $e) đơn lẻ sẽ để lỗi từ chối theo chính sách suy giảm strict tiếp tục lan ra. Hãy bắt nó (hoặc RuntimeException) một cách tường minh khi chính sách suy giảm đang được áp dụng.
  • getContext() an toàn cho APM theo hợp đồng. Các khóa ở dạng snake_case. Các giá trị là kiểu nguyên thủy hoặc danh sách các kiểu nguyên thủy, không có đối tượng lồng nhau và không có tài nguyên. Bạn có thể tuần tự hóa trực tiếp. Nó không bao giờ chứa các byte của tài liệu.
  • Không phân tích cú pháp các thông báo ngoại lệ. Các thông báo dành cho con người đọc và có thể thay đổi. Hãy dùng các accessor có kiểu (getFontName(), capability->id, v.v.) và getContext() làm bề mặt ổn định cho máy.
  • Đừng dựa vào số lượng đã lỗi thời. Tài liệu cũ có thể trích dẫn một con số cố định “N ngoại lệ nghiệp vụ”. Hệ thống phân cấp mở rộng dần qua các bản phát hành. Hãy dựa vào kiểu cơ sở NextPdfExceptioninstanceof, không bao giờ dựa vào một con số mã hóa cứng.
  • Các placeholder của PSR-3 vẫn là chuỗi. Khi ghi nhật ký, hãy giữ thông báo là một chuỗi với các token {placeholder}, và đặt các giá trị vào mảng context (PSR-3 §1.2). Không nội suy đối tượng ngoại lệ vào thông báo.

Việc xử lý ngoại lệ không thêm chi phí nào ở trạng thái ổn định. NextPDF chỉ ném ngoại lệ cho những trạng thái bất thường, và getContext() dựng một mảng nhỏ khi cần. performance_budget (wall_ms: 2000, peak_mb: 96) giới hạn lượt chạy harness cho hướng dẫn này, không áp dụng cho mọi tài liệu.

  • getContext() được thiết kế để an toàn cho nhật ký: chỉ chứa kiểu nguyên thủy, không có payload tài liệu và không có byte tệp. Bạn vẫn chịu trách nhiệm về những giá trị mà bạn thêm vào context nhật ký. Hãy làm sạch mọi dữ liệu do người dùng cung cấp (ví dụ một đường dẫn tệp) theo chính sách ghi nhật ký của bạn trước khi dữ liệu đó tới một sink.
  • Không hiển thị các thông báo ngoại lệ thô cho người dùng cuối theo cách làm lộ cấu trúc hệ thống tệp. Hãy hiển thị một thông báo tổng quát, và ghi nhật ký ngữ cảnh có cấu trúc ở phía máy chủ.
Phát biểuĐặc tảĐiều khoảnreference_id
Một ngoại lệ thuộc về context nhật ký PSR-3 dưới khóa exception.PSR-3§1.3
Các thông báo nhật ký vẫn là chuỗi; tên các placeholder ánh xạ tới các khóa context.PSR-3§1.2
Ngày sửa đổi được tạo lại sau mỗi lần lưu, nên đầu ra ổn định về mặt cấu trúc (không phải về byte).ISO 32000-2§14.3

Hướng dẫn này được kiểm chứng với profile khả năng tái lập structural. Đầu ra mang theo một trailer /ID và một ngày sửa đổi được tạo lại sau mỗi lần lưu, nên không thể đạt được tính đồng nhất về byte. Sau khi được chuẩn hóa bằng qpdf, cấu trúc vẫn ổn định.