Pular para o conteúdo

Trate erros com a hierarquia de exceções do NextPDF

O NextPDF lança exceções tipadas para estados excepcionais. Ele nunca mascara um erro retornando false ou null. Toda exceção de domínio estende a mesma base abstrata, NextPdfException, e expõe um contexto de diagnóstico estruturado por meio de ContextAwareExceptionInterface. Esta receita mostra onde capturar exceções e como registrar contexto estruturado em um pipeline de monitoramento de desempenho de aplicações (APM). Ela também mostra quais falhas um catch-all único não cobre.

Terminal window
composer require nextpdf/core:^3

Você não precisa instalar nenhuma extensão adicional.

A hierarquia é:

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

Essa hierarquia traz duas consequências práticas, ambas verificadas no código-fonte:

  1. catch (NextPdfException $e) captura toda exceção em NextPDF\Exception, incluindo as violações de modo estrito. Todas elas estendem a base abstrata.
  2. Ele não captura tudo o que a biblioteca pode lançar. NextPDF\Support\DegradedException estende RuntimeException diretamente, não NextPdfException. Portanto, um catch (NextPdfException $e) não captura uma rejeição de política de degradação. Para tratar esse caso, capture DegradedException (ou o mais amplo RuntimeException) explicitamente. Esta receita explicita esse limite em vez de tratar um único catch-all como cobertura completa.

NextPdfException::getContext() retorna um array<string, mixed> com chaves em snake_case e somente valores primitivos ou listas de valores primitivos. Você pode serializá-lo diretamente no array de contexto de um logger PSR-3. A PSR-3 §1.3 coloca uma exceção sob a chave de contexto 'exception'. O getContext() do NextPDF adiciona detalhes de domínio junto dessa chave, não o objeto da exceção em si.

Esta superfície da API vem do PHPDoc de NextPDF\Exception\NextPdfException, de NextPDF\Contracts\ContextAwareExceptionInterface, das exceções de domínio concretas (por exemplo NextPDF\Exception\FontNotFoundException, com getFontName() / getSearchPaths() / wasFallbackAttempted()), e de NextPDF\Support\DegradedException (que carrega o Capability e a DegradationPolicy). Os exemplos abaixo usam NextPdfException::getContext() e os acessores específicos de cada exceção.

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

O exemplo completo mostra capturas granulares, registro de contexto estruturado e o limite do DegradedException. Ele também mantém o canal de saída do 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 fica livre para o harness; o PDF é gravado apenas em NEXTPDF_COOKBOOK_OUTPUT.

  • Ordene os blocos catch do específico → geral. O PHP usa o primeiro catch compatível. Um catch (NextPdfException $e) colocado antes de catch (FontNotFoundException $e) torna o bloco específico código morto.
  • DegradedException não é uma NextPdfException. A verificação no código-fonte confirma que ela estende RuntimeException. Um único catch (NextPdfException $e) deixa uma rejeição de degradação estrita se propagar. Capture-a (ou RuntimeException) explicitamente quando a política de degradação estiver em jogo.
  • getContext() é seguro para APM por contrato. As chaves ficam em snake_case. Os valores são primitivos ou listas de primitivos, sem objetos aninhados nem recursos. Você pode serializá-lo diretamente. Ele nunca contém bytes do documento.
  • Não faça parsing das mensagens de exceção. As mensagens são legíveis por humanos e podem mudar. Use os acessores tipados (getFontName(), capability->id, e assim por diante) e getContext() como a superfície estável para máquina.
  • Ressalva sobre contagens desatualizadas. Material mais antigo pode citar um número fixo de “N exceções de domínio”. A hierarquia cresce a cada versão. Confie no tipo base NextPdfException e em instanceof, nunca em uma contagem fixa no código.
  • Os placeholders da PSR-3 permanecem strings. Ao registrar, mantenha a mensagem como uma string com tokens {placeholder} e coloque os valores no array de contexto (PSR-3 §1.2). Não interpole o objeto da exceção na mensagem.

O tratamento de exceções não adiciona custo no caminho normal de execução. O NextPDF lança exceções apenas para estados excepcionais, e getContext() constrói um array pequeno sob demanda. O performance_budget (wall_ms: 2000, peak_mb: 96) limita a execução do harness para esta receita, não para documentos arbitrários.

  • getContext() foi projetado para ser seguro para logs: apenas primitivos, sem payload do documento e sem bytes de arquivo. Você ainda é responsável pelos valores que adiciona a um contexto de log. Limpe qualquer dado fornecido pelo usuário (um caminho de arquivo, por exemplo) de acordo com a política de logging antes que chegue a um sink.
  • Não exiba mensagens de exceção brutas para usuários finais de modo que vazem a estrutura do sistema de arquivos. Mostre uma mensagem genérica e registre o contexto estruturado no servidor.
DeclaraçãoEspecificaçãoCláusulareference_id
Uma exceção pertence ao contexto de log da PSR-3 sob a chave exception.PSR-3§1.3
As mensagens de log permanecem strings; os nomes de placeholders mapeiam para chaves de contexto.PSR-3§1.2
A data de modificação é regenerada a cada salvamento, então a saída é estável estruturalmente (não byte a byte).ISO 32000-2§14.3

Esta receita é verificada com o perfil de reprodutibilidade estrutural. A saída inclui um /ID no trailer e uma data de modificação que são regenerados a cada salvamento, portanto a identidade byte a byte não é alcançável. A estrutura normalizada pelo qpdf permanece estável.