Hierarquia de exceções e exceções customizadas
1. Introdução à hierarquia de exceções no PHP
O PHP possui um sistema robusto de tratamento de erros e exceções, centrado na interface Throwable, introduzida no PHP 7. Essa interface é a base de toda a hierarquia de exceções e erros da linguagem.
1.1 A classe base Throwable e suas interfaces
Throwable define os métodos essenciais que qualquer exceção ou erro deve implementar: getMessage(), getCode(), getFile(), getLine(), getTrace(), getTraceAsString() e getPrevious(). Tanto Exception quanto Error implementam essa interface.
1.2 Divisão principal: Exception vs Error
A hierarquia se divide em dois grandes ramos:
Exception: representa condições excepcionais que podem ser recuperadas. É a classe base para exceções tradicionais.Error: representa erros internos do PHP que geralmente não podem ser tratados em tempo de execução, comoTypeError,ParseError,DivisionByZeroError.
try {
// Código que pode lançar exceções ou erros
} catch (\Throwable $t) {
echo "Capturado: " . $t->getMessage();
}
1.3 A hierarquia nativa de exceções (SPL exceptions)
O PHP oferece um conjunto de exceções SPL (Standard PHP Library) organizadas em categorias lógicas e de runtime.
2. Explorando as exceções SPL nativas
2.1 Exceções lógicas: LogicException, InvalidArgumentException, OutOfRangeException
Essas exceções indicam problemas na lógica do programa, geralmente detectáveis antes da execução.
function dividir($a, $b) {
if (!is_numeric($a) || !is_numeric($b)) {
throw new \InvalidArgumentException("Ambos os parâmetros devem ser numéricos.");
}
if ($b == 0) {
throw new \DivisionByZeroError("Divisão por zero não é permitida.");
}
return $a / $b;
}
2.2 Exceções de tempo de execução: RuntimeException, UnexpectedValueException, OverflowException
Ocorrem durante a execução do programa e podem ser imprevisíveis.
function lerArquivo($caminho) {
if (!file_exists($caminho)) {
throw new \RuntimeException("Arquivo não encontrado: $caminho");
}
$conteudo = file_get_contents($caminho);
if ($conteudo === false) {
throw new \UnexpectedValueException("Falha ao ler o conteúdo do arquivo.");
}
return $conteudo;
}
2.3 Exceções de domínio: DomainException, RangeException, LengthException
Relacionadas a domínios específicos, como valores fora de faixa ou comprimentos inválidos.
function criarArray($tamanho) {
if ($tamanho < 0) {
throw new \DomainException("Tamanho não pode ser negativo.");
}
if ($tamanho > 1000) {
throw new \RangeException("Tamanho máximo excedido (1000).");
}
return array_fill(0, $tamanho, null);
}
3. Criando exceções customizadas
3.1 Estrutura básica de uma classe de exceção customizada
class MinhaExcecao extends \Exception {
public function __construct($mensagem = "", $codigo = 0, \Throwable $previous = null) {
parent::__construct($mensagem, $codigo, $previous);
}
}
3.2 Estendendo Exception vs estendendo RuntimeException
A escolha depende da categoria da exceção:
- Estenda
Exceptionpara erros lógicos ou de domínio. - Estenda
RuntimeExceptionpara erros que ocorrem em tempo de execução.
class ValidacaoException extends \InvalidArgumentException {}
class BancoDadosException extends \RuntimeException {}
3.3 Adicionando propriedades e métodos extras à exceção
class EmailInvalidoException extends \InvalidArgumentException {
private string $email;
public function __construct(string $email, string $mensagem = "") {
$this->email = $email;
parent::__construct($mensagem ?: "Email inválido: $email");
}
public function getEmail(): string {
return $this->email;
}
}
4. Boas práticas na definição de exceções customizadas
4.1 Nomenclatura semântica e coesa para classes de exceção
Use nomes que descrevam claramente o problema: ArquivoNaoEncontradoException, SaldoInsuficienteException, ConexaoPerdidaException.
4.2 Quando criar uma exceção específica vs usar exceções nativas
Crie exceções customizadas quando:
- Precisar de propriedades adicionais.
- O contexto exigir tratamento diferenciado.
- A exceção nativa não transmitir informação suficiente.
4.3 Organizando exceções em namespaces e diretórios
namespace App\Excecoes;
class UsuarioNaoEncontradoException extends \RuntimeException {}
class EmailDuplicadoException extends \DomainException {}
5. Capturando exceções de forma hierárquica
5.1 Ordem dos blocos catch e a hierarquia de classes
Sempre capture as exceções mais específicas primeiro.
try {
// código que pode lançar exceções
} catch (EmailInvalidoException $e) {
// trata email inválido
} catch (InvalidArgumentException $e) {
// trata argumentos inválidos genéricos
} catch (\Exception $e) {
// trata qualquer outra exceção
}
5.2 Capturando múltiplos tipos de exceção com união de tipos (PHP 8+)
try {
// código
} catch (EmailInvalidoException | UsuarioNaoEncontradoException $e) {
echo "Erro de validação: " . $e->getMessage();
}
5.3 Uso de finally em conjunto com exceções customizadas
function processarPedido(Pedido $pedido) {
$conexao = abrirConexao();
try {
$conexao->iniciarTransacao();
$pedido->validar();
$conexao->salvar($pedido);
$conexao->confirmarTransacao();
} catch (ValidacaoException $e) {
$conexao->reverterTransacao();
throw new ProcessamentoException("Erro ao processar pedido", 0, $e);
} finally {
$conexao->fechar();
}
}
6. Exceções customizadas com códigos de erro e mensagens
6.1 Definindo constantes de código de erro na classe de exceção
class ApiException extends \RuntimeException {
public const ERRO_AUTENTICACAO = 1001;
public const ERRO_VALIDACAO = 1002;
public const ERRO_SERVIDOR = 1003;
}
6.2 Formatando mensagens dinâmicas com dados contextuais
class SaldoInsuficienteException extends \RuntimeException {
public function __construct(
private float $saldoAtual,
private float $valorSolicitado
) {
$mensagem = sprintf(
"Saldo insuficiente. Atual: R$ %.2f, solicitado: R$ %.2f",
$saldoAtual,
$valorSolicitado
);
parent::__construct($mensagem, 2001);
}
}
6.3 Métodos auxiliares para logging e rastreamento
class LoggableException extends \RuntimeException {
public function toLog(): array {
return [
'mensagem' => $this->getMessage(),
'codigo' => $this->getCode(),
'arquivo' => $this->getFile(),
'linha' => $this->getLine(),
'trace' => $this->getTraceAsString(),
];
}
}
7. Encadeamento de exceções e exceções aninhadas
7.1 Passando a exceção anterior no construtor ($previous)
try {
// operação que falha
} catch (\PDOException $e) {
throw new BancoDadosException("Falha na consulta", 0, $e);
}
7.2 Criando exceções que encapsulam outras exceções
class ProcessamentoException extends \RuntimeException {
private array $erros;
public function __construct(string $mensagem, array $erros = []) {
$this->erros = $erros;
parent::__construct($mensagem);
}
public function getErros(): array {
return $this->erros;
}
}
7.3 Rastreamento de causas raiz com getPrevious()
function obterCausaRaiz(\Throwable $e): \Throwable {
while ($e->getPrevious() !== null) {
$e = $e->getPrevious();
}
return $e;
}
8. Exemplos práticos de implementação
8.1 Sistema de validação com exceções customizadas por tipo de erro
class ValidacaoException extends \RuntimeException {
private array $erros;
public function __construct(array $erros) {
$this->erros = $erros;
parent::__construct("Erros de validação encontrados");
}
public function getErros(): array {
return $this->erros;
}
}
function validarUsuario(array $dados): void {
$erros = [];
if (empty($dados['nome'])) {
$erros[] = "Nome é obrigatório";
}
if (!filter_var($dados['email'] ?? '', FILTER_VALIDATE_EMAIL)) {
$erros[] = "Email inválido";
}
if (!empty($erros)) {
throw new ValidacaoException($erros);
}
}
8.2 Camada de repositório com exceções de domínio
class RepositorioException extends \RuntimeException {}
class EntidadeNaoEncontradaException extends RepositorioException {}
class UsuarioRepositorio {
public function buscarPorId(int $id): Usuario {
$stmt = $this->db->prepare("SELECT * FROM usuarios WHERE id = ?");
$stmt->execute([$id]);
$dados = $stmt->fetch();
if (!$dados) {
throw new EntidadeNaoEncontradaException(
"Usuário com ID $id não encontrado"
);
}
return new Usuario($dados);
}
}
8.3 Tratamento centralizado de exceções customizadas com set_exception_handler
set_exception_handler(function (\Throwable $e) {
$codigo = $e->getCode() ?: 500;
http_response_code($codigo);
if ($e instanceof ValidacaoException) {
echo json_encode([
'erro' => 'validação',
'mensagens' => $e->getErros()
]);
} elseif ($e instanceof EntidadeNaoEncontradaException) {
echo json_encode([
'erro' => 'não_encontrado',
'mensagem' => $e->getMessage()
]);
} else {
error_log($e->getMessage() . "\n" . $e->getTraceAsString());
echo json_encode([
'erro' => 'interno',
'mensagem' => 'Erro interno do servidor'
]);
}
exit;
});
Referências
- PHP Manual: Exceções — Documentação oficial sobre exceções no PHP, incluindo sintaxe e exemplos básicos.
- PHP Manual: Throwable Interface — Documentação da interface Throwable, base de toda a hierarquia de exceções e erros.
- PHP Manual: SPL Exceptions — Lista completa das exceções SPL nativas do PHP com descrições detalhadas.
- PHP The Right Way: Exceções — Guia prático sobre boas práticas no uso de exceções em PHP.
- PHP 8: Union Types in Catch Blocks — Artigo técnico sobre o uso de tipos união em blocos catch no PHP 8.
- PHP Manual: set_exception_handler — Documentação oficial da função para tratamento centralizado de exceções não capturadas.