Pular para o conteúdo

Erros como recurso

Spec: ISO 9241-110, §5.6.4 Evidence: Code-backed

O NextPDF trata a hierarquia de exceções como uma superfície de API, projetada com o mesmo cuidado dos métodos que a lançam. Uma falha é específica, tipada, capturável na granularidade de que você precisa e carrega contexto estruturado para os logs.

Esta página mostra essa superfície no próprio código-fonte do mecanismo: o tipo base, as subclasses tipadas, os named constructors que vinculam uma causa raiz à mensagem e o contexto estruturado que toda exceção do NextPDF expõe.

Uma mensagem de erro é o mecanismo falando com você no pior momento possível: produção, 2 a.m. e um documento que já deveria ter sido entregue. O que essa mensagem diz, nesse momento, define se o próximo passo é uma correção ou uma longa investigação.

Um RuntimeException: something went wrong genérico não leva você a lugar nenhum. Ele informa que o mecanismo falhou, mas não o que falhou, onde falhou e, certamente, não o que fazer. As diretrizes de fatores humanos são diretas a esse respeito. Um erro deve se explicar bem o bastante para que corrigi-lo seja o próximo passo óbvio, não um projeto de pesquisa ( Spec: ISO 9241-110, §5.6.4.3 ). Uma exceção que nomeia a causa e a solução não é luxo. É a diferença entre uma correção de cinco minutos e uma de cinco horas.

  • Toda falha do NextPDF estende uma única base abstrata, NextPdfException, de modo que você pode capturar todos os erros da biblioteca com um único tipo.
  • Abaixo dela ficam subclasses específicas e tipadas — uma fonte que não pode ser encontrada, uma configuração inválida, uma operação de assinatura que falhou — para que você capture exatamente a falha que consegue tratar.
  • Toda exceção do NextPDF implementa ContextAwareExceptionInterface e expõe getContext(): um mapa estruturado e seguro para logs, de modo que você nunca precise analisar uma string de mensagem para recuperar diagnósticos.
  • As mensagens são acionáveis: named constructors vinculam a causa raiz real (e, muitas vezes, a correção) à mensagem, em vez de um template genérico.
  • Cada classe de exceção documenta quem pode agir sobre ela — desenvolvedor, infraestrutura ou chamador da biblioteca — para que a triagem comece antes de você ler a stack trace.

A hierarquia é rasa e deliberada. Há uma base, uma camada de tipos específicos do domínio e um contrato que todos eles seguem.

Uma base, abrangente por design. NextPdfException é abstrata, estende RuntimeException e implementa ContextAwareExceptionInterface:

abstract class NextPdfException extends RuntimeException implements ContextAwareExceptionInterface
{
/** @return array<string, mixed> */
public function getContext(): array
{
return [];
}
}

Ser abstrata é uma decisão. Você nunca captura a base vaga por acidente, porque ela nunca é lançada diretamente. Você a captura deliberadamente, como rede de segurança, e captura uma subclasse específica quando consegue fazer algo específico.

Subclasses específicas e tipadas. Uma fonte ausente não é um erro genérico; é FontNotFoundException e carrega os dados de que você precisa para agir:

final class FontNotFoundException extends NextPdfException
{
public function __construct(
private readonly string $fontName,
private readonly array $searchPaths,
private readonly bool $fallbackAttempted,
?Throwable $previous = null,
) {
parent::__construct(
\sprintf('Font "%s" not found. Searched: [%s].', $fontName, \implode(', ', $searchPaths)),
0,
$previous,
);
}
// getFontName(), getSearchPaths(), wasFallbackAttempted(), getContext()
}

A mensagem nomeia a fonte e os caminhos exatos pesquisados. Você não precisa adivinhar qual diretório estava faltando; a exceção informa.

Contexto estruturado, não extração de strings. Toda exceção retorna um mapa em snake_case, somente com primitivos, que é seguro serializar diretamente em um log ou em um payload de APM:

public function getContext(): array
{
return [
'config_key' => $this->configKey,
'given_value' => $this->givenValue,
'expected_type' => $this->expectedType,
];
}

O contrato é explícito sobre o motivo. Um middleware de logging pode chamar $logger->error($e->getMessage(), $e->getContext()) para qualquer exceção do NextPDF sem jamais analisar a mensagem. A mensagem é para humanos. O contexto é para máquinas. Nenhum precisa fazer o papel do outro.

Mensagens acionáveis por meio de named constructors. É aqui que os erros deixam de ser incidentais e passam a ser projetados. SignatureException não diz apenas “a assinatura falhou no nível B-LT”. Ela oferece named constructors que vinculam à mensagem a causa raiz real e, frequentemente, a solução exata:

public static function tsaUrlEmpty(string $signatureLevel): self
{
return new self('', $signatureLevel, null,
'TSA endpoint URL is empty: pass a non-empty `tsaUrl` to the TsaClient '
. 'constructor (e.g. "https://timestamp.example.com/tsa") or remove the '
. 'TSA client wiring if no timestamping is required at this signature level');
}

A mensagem indica o que está errado e o que fazer a respeito. Há constructors equivalentes para um pacote de capacidade ausente, um cliente HTTP inexistente, um algoritmo somente de digest escolhido por engano, um tipo de chave que não corresponde ao algoritmo e mais. Cada um transforma uma classe de falha em uma frase sobre a qual um desenvolvedor pode agir sem ler o código-fonte do mecanismo.

Falhas que são barulhentas de propósito. Algumas exceções existem justamente para que uma lacuna silenciosa se torne barulhenta. NotImplementedException carrega um rótulo feature localizável por grep por máquina e uma referência followUp:

final class NotImplementedException extends NextPdfException
{
public function __construct(
public readonly string $feature,
public readonly string $followUp,
?Throwable $previous = null,
) {
parent::__construct(
\sprintf('%s is not implemented in this release. %s', $feature, $followUp),
0, $previous,
);
}
}

Um caminho alcançado, mas não conectado, lança isto em vez de retornar um no-op plausível. A mesma ideia orienta StrictModeViolation, cujas subclasses carregam um rótulo curto e localizável por grep para o construto divergente, além de contexto opcional de localização e citação. Um desvio da especificação se torna uma parada tipada e contextual, não uma renderização silenciosamente incorreta.

Metadados de triagem na própria classe. Cada classe de exceção nomeia quem pode agir sobre ela em seu docblock. Por exemplo, FontNotFoundException é “Developer (verify font path) or Infrastructure (fix file permissions)”. InvalidConfigException é “Developer (fix configuration before calling NextPDF)”. NotImplementedException é “Library callers — either remove the call or pin to a future release”. A triagem começa antes da stack trace, porque a pergunta “isso é meu ou de operações?” já tem uma resposta registrada.

A tabela resume o design e o que cada propriedade oferece a você.

Propriedade de designNo código-fonteO que isso traz para você
Uma base abstrataNextPdfException (abstrata, implementa a interface de contexto)Capture todos os erros da biblioteca com um único tipo, nunca a base vaga por acidente
Subclasses específicas e tipadasFontNotFoundException, InvalidConfigException, SignatureException, …Capture exatamente a falha que você consegue tratar
Contexto estruturadogetContext() — somente primitivos em snake_caseRegistre no log ou envie para o APM sem analisar uma string de mensagem
Mensagens acionáveisNamed constructors vinculam causa raiz + soluçãoUma frase sobre a qual você pode agir, não um template
Barulhentas de propósitoNotImplementedException, StrictModeViolationUma lacuna silenciosa se torna uma parada tipada e localizável via grep
Metadados de triagem”Actionable by:” no docblock de cada classeSaiba de quem é o problema antes de ler a trace

Esta página é Evidence: Code-backed : toda classe, assinatura e forma de mensagem é citada a partir do namespace de exceções do mecanismo, não parafraseada.

  • A base abstrata e seu contrato ContextAwareExceptionInterface, as subclasses tipadas, a forma de getContext() e os named constructors de SignatureException são citados literalmente do código-fonte.
  • As linhas de triagem “Actionable by:” são contratos de docblock de classe nesses mesmos arquivos.
  • A âncora de fatores humanos é Spec: ISO 9241-110 — §5.6.4.3, sobre erros que se explicam o suficiente para serem corrigidos, e o princípio de robustez a erros de uso da §6. O mecanismo trata o desenvolvedor como o usuário e a exceção como a interface que precisa satisfazer essas cláusulas.

Capture de forma ampla como rede de segurança, capture especificamente onde você consegue agir e envie o contexto estruturado diretamente para o logger — sem analisar a mensagem.

<?php
declare(strict_types=1);
use NextPDF\Core\Document;
use NextPDF\Exception\FontNotFoundException;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
function renderInvoice(LoggerInterface $logger): ?string
{
try {
$document = Document::createStandalone();
$document->setTitle('Invoice 2026-0042');
$document->addPage();
$document->setFont('BrandSans', '', 12);
$document->cell(0, 10, 'Thank you for your business.', newLine: true);
return $document->getPdfData();
} catch (FontNotFoundException $e) {
// Specific: we can recover — fall back to a built-in font.
// getContext() is log-safe structured data, not a parsed string.
$logger->warning($e->getMessage(), $e->getContext());
return null; // caller re-renders with 'helvetica'
} catch (NextPdfException $e) {
// Backstop: any other NextPDF failure, still with structured context.
$logger->error($e->getMessage(), $e->getContext());
return null;
}
}

O catch específico se recupera porque o tipo da exceção informou que a recuperação era possível. A rede de segurança registra contexto estruturado para todo o resto. Em nenhum momento a aplicação lê a mensagem para descobrir o que aconteceu.

A leitura equivocada mais comum é que uma árvore de exceções profunda é overengineering e que um único tipo de erro seria mais simples. Seria mais simples para o mecanismo e pior para você. Um único tipo significa que toda falha vira uma stack trace genérica e que a lógica de recuperação depende de comparação de strings. Essa comparação é frágil; a próxima reformulação da mensagem a quebra. Uma hierarquia pequena e específica move esse conhecimento para o sistema de tipos, onde o compilador e os blocos catch podem usá-lo.

Um segundo equívoco é achar que a mensagem e o contexto são redundantes. Não são. A mensagem é prosa para um humano lendo uma linha de log. O contexto é um mapa tipado para roteamento de código, alertas ou dashboards. Confundi-los é exatamente a armadilha de análise de strings que o contrato getContext() existe para eliminar.

A hierarquia é intencionalmente rasa. O NextPDF não cria uma classe de exceção distinta para cada falha concebível. Ele cria uma quando capturar aquela falha especificamente é algo que um chamador faria de forma razoável. Dividir demais trocaria o problema de análise de strings por uma lista de catch descontrolada.

getContext() é estruturado para logs e APM; por isso, retorna, por contrato, apenas primitivos e listas de primitivos, sem objetos aninhados. É contexto de diagnóstico, não um snapshot serializado dos detalhes internos do mecanismo. Também não é um formato de transmissão estável para construir esquemas externos.

Esta página descreve a superfície de design das exceções. O conjunto exato de exceções e seus campos evolui junto com o mecanismo. As classes e formas citadas aqui são as vigentes no momento desta revisão e ilustram o contrato, sem formar um catálogo congelado. O contrato — uma base, subclasses tipadas, contexto estruturado, mensagens acionáveis — é a parte estável.

  • Code-backed (nível de evidência) — uma página cujas afirmações são verificadas contra o próprio código-fonte do mecanismo, citadas em vez de parafraseadas.
  • Exceção ciente de contexto — uma exceção do NextPDF que implementa ContextAwareExceptionInterface e expõe getContext(). Esse método retorna um mapa em snake_case de campos de diagnóstico primitivos, seguro para serializar em um log ou payload de APM sem analisar a string de mensagem.
  • Named constructor — um método de fábrica estático (por exemplo SignatureException::tsaUrlEmpty()) que constrói uma exceção com uma mensagem vinculada a uma causa raiz específica e, muitas vezes, à sua solução.
  • PAdES — PDF Advanced Electronic Signatures, a família de perfis ETSI para assinatura de PDF. Expandido na primeira utilização; abordado em profundidade nas páginas de assinatura.
  • TSA — Time-Stamping Authority, o serviço confiável que emite os timestamps RFC 3161 usados pelos perfis PAdES superiores.