Обработка ошибок с учётом иерархии исключений NextPDF
NextPDF выбрасывает типизированные исключения в исключительных ситуациях. Библиотека никогда не скрывает ошибку за возвратом false или null. Каждое доменное исключение расширяет один и тот же абстрактный базовый класс, NextPdfException, и предоставляет структурированный диагностический контекст через ContextAwareExceptionInterface. Этот рецепт показывает, где перехватывать исключения и как логировать структурированный контекст для конвейера APM (мониторинга производительности приложений). Он также показывает, какие сбои единый перехват по базовому типу не покрывает.
Установка
Заголовок раздела «Установка»composer require nextpdf/core:^3Никаких дополнительных расширений не требуется.
Концептуальный обзор
Заголовок раздела «Концептуальный обзор»Иерархия выглядит так:
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Эта иерархия имеет два практических следствия, оба проверены по исходному коду:
catch (NextPdfException $e)перехватывает каждое исключение в пространствеNextPDF\Exception, включая нарушения строгого режима. Все они расширяют абстрактный базовый класс.- Он перехватывает не всё, что может выбросить библиотека.
NextPDF\Support\DegradedExceptionрасширяетRuntimeExceptionнапрямую, а неNextPdfException. Поэтомуcatch (NextPdfException $e)не перехватывает сбой по политике деградации. Чтобы обработать его, перехватитеDegradedException(или более широкийRuntimeException) явно. Этот рецепт показывает эту границу явно и не считает единый перехват по базовому типу полным покрытием.
NextPdfException::getContext() возвращает array<string, mixed> с ключами в snake_case и только примитивными значениями либо списками примитивных значений. Его можно напрямую сериализовать в массив контекста логгера PSR-3. PSR-3 §1.3 помещает исключение под ключ контекста 'exception'. Метод getContext() в NextPDF добавляет наряду с этим ключом доменные детали, а не сам объект исключения.
Поверхность API
Заголовок раздела «Поверхность API»Эта поверхность API взята из PHPDoc для NextPDF\Exception\NextPdfException, NextPDF\Contracts\ContextAwareExceptionInterface, конкретных доменных исключений (например, NextPDF\Exception\FontNotFoundException, с методами getFontName() / getSearchPaths() / wasFallbackAttempted()) и NextPDF\Support\DegradedException (который содержит Capability и DegradationPolicy). В примерах ниже используются NextPdfException::getContext() и методы доступа для конкретных исключений.
Пример кода — быстрый старт
Заголовок раздела «Пример кода — быстрый старт»<?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. Он также оставляет свободным канал вывода тестовой обвязки.
<?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 остаётся свободным для тестовой обвязки; PDF записывается только в NEXTPDF_COOKBOOK_OUTPUT.
Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»- Размещайте блоки catch от конкретного к общему. PHP сопоставляет первый совместимый
catch. Блокcatch (NextPdfException $e), размещённый передcatch (FontNotFoundException $e), делает конкретный блок недостижимым кодом. DegradedExceptionне являетсяNextPdfException. Как проверено по исходному коду, он расширяетRuntimeException. Один лишьcatch (NextPdfException $e)пропускает дальше сбой по строгой политике деградации. Перехватывайте его (илиRuntimeException) явно, когда задействована политика деградации.getContext()по контракту безопасен для APM. Ключи в snake_case. Значения — это примитивы или списки примитивов, без вложенных объектов и без ресурсов. Его можно сериализовать напрямую. Он никогда не содержит байты документа.- Не разбирайте сообщения исключений. Сообщения предназначены для чтения человеком и могут меняться. Используйте типизированные методы доступа (
getFontName(),capability->idи так далее) иgetContext()как стабильную машинную поверхность. - Предостережение об устаревшем количестве. Более старые материалы могут указывать фиксированное число “N доменных исключений”. Иерархия растёт от выпуска к выпуску. Полагайтесь на базовый тип
NextPdfExceptionиinstanceof, а не на жёстко заданное число. - Подстановочные элементы PSR-3 остаются строками. При логировании оставляйте сообщение строкой с токенами
{placeholder}, а значения помещайте в массив контекста (PSR-3 §1.2). Не подставляйте объект исключения в сообщение.
Производительность
Заголовок раздела «Производительность»Обработка исключений не добавляет постоянных издержек. NextPDF выбрасывает исключения только в исключительных ситуациях, а getContext() по требованию строит небольшой массив. performance_budget (wall_ms: 2000, peak_mb: 96) ограничивает запуск тестовой обвязки для этого рецепта, а не произвольные документы.
Замечания по безопасности
Заголовок раздела «Замечания по безопасности»getContext()спроектирован как безопасный для логирования: только примитивы, без полезной нагрузки документа и без байтов файла. Вы по-прежнему отвечаете за значения, которые добавляете в контекст лога. Очищайте всё, что предоставлено пользователем (например, путь к файлу), согласно вашей политике логирования, прежде чем оно попадёт в приёмник.- Не выводите конечным пользователям необработанные сообщения исключений так, чтобы это раскрывало структуру файловой системы. Показывайте обобщённое сообщение, а структурированный контекст логируйте на стороне сервера.
Соответствие
Заголовок раздела «Соответствие»| Утверждение | Спецификация | Раздел | reference_id (идентификатор ссылки) |
|---|---|---|---|
Исключение помещается в контекст лога PSR-3 под ключом exception. | PSR-3 | §1.3 | |
| Сообщения лога остаются строками; имена подстановочных элементов сопоставляются с ключами контекста. | PSR-3 | §1.2 | |
| Дата изменения генерируется заново при каждом сохранении, поэтому вывод стабилен структурно (а не побайтово). | ISO 32000-2 | §14.3 |
Этот рецепт проверен с профилем воспроизводимости structural. Вывод содержит /ID в трейлере и дату изменения, которые генерируются заново при каждом сохранении, поэтому побайтовая идентичность недостижима. Нормализованная qpdf структура остаётся стабильной.