Exceções customizadas
Nesta aula, você aprenderá a criar exceções customizadas em PHP estendendo a classe Exception, explorando exceções de domínio para erros específicos de negócio, o encadeamento de exceções para rastrear a origem de falhas e boas práticas para decidir quando criar suas próprias exceções.
Exceções customizadas permitem que você crie classes de erro específicas para o seu domínio de aplicação, tornando o tratamento de erros mais expressivo e organizado. Ao estender a classe Exception do PHP, você pode adicionar propriedades e métodos personalizados, além de categorizar erros de forma semântica.
Nesta aula, vamos explorar como estender a classe Exception, criar exceções de domínio, encadear exceções para preservar a pilha de chamadas original e discutir quando é apropriado criar suas próprias exceções.
Estendendo Exception
A classe Exception do PHP é a base para todas as exceções. Para criar uma exceção customizada, basta definir uma nova classe que estenda Exception. Você pode sobrescrever o construtor e adicionar métodos específicos.
Exemplo de uma exceção simples para erros de validação:
<?php
class ValidationException extends Exception {
private $errors;
public function __construct($message = "", $code = 0, Throwable $previous = null, array $errors = []) {
parent::__construct($message, $code, $previous);
$this->errors = $errors;
}
public function getErrors(): array {
return $this->errors;
}
}
?>Essa exceção armazena uma lista de erros de validação. Ao lançá-la, você pode passar os detalhes dos campos inválidos. Exemplo de uso:
throw new ValidationException("Dados inválidos", 400, null, ["nome" => "Campo obrigatório"]);
Exceções de domínio
Exceções de domínio representam erros específicos do negócio, como "SaldoInsuficienteException" em um sistema financeiro ou "ProdutoEsgotadoException" em um e-commerce. Elas ajudam a tornar o código mais semântico e facilitam o tratamento seletivo.
Exemplo de exceção de domínio:
<?php
class SaldoInsuficienteException extends \RuntimeException {
private $saldoAtual;
private $valorSolicitado;
public function __construct($saldoAtual, $valorSolicitado, $message = "", $code = 0, Throwable $previous = null) {
$this->saldoAtual = $saldoAtual;
$this->valorSolicitado = $valorSolicitado;
$message = $message ?: "Saldo insuficiente: R$ {$saldoAtual} para saque de R$ {$valorSolicitado}";
parent::__construct($message, $code, $previous);
}
public function getSaldoAtual(): float {
return $this->saldoAtual;
}
public function getValorSolicitado(): float {
return $this->valorSolicitado;
}
}
?>Uso em uma classe Conta:
public function sacar(float $valor): void {
if ($valor > $this->saldo) {
throw new SaldoInsuficienteException($this->saldo, $valor);
}
$this->saldo -= $valor;
}
Encadeamento
O encadeamento de exceções permite que você lance uma nova exceção mantendo a referência à exceção original. Isso é útil ao capturar uma exceção de baixo nível e relançá-la com uma mensagem mais significativa, preservando a pilha de chamadas original.
No PHP, o encadeamento é feito passando a exceção anterior como terceiro parâmetro do construtor ($previous). Exemplo:
try {
// código que pode lançar PDOException
} catch (PDOException $e) {
throw new DatabaseException("Erro ao acessar o banco", 0, $e);
}
Você pode obter a exceção anterior usando o método getPrevious(). Isso é útil para depuração e logs.
Quando criar
Crie exceções customizadas quando:
- Você precisa de informações adicionais sobre o erro (como dados do contexto).
- Deseja tratar diferentes tipos de erro de forma seletiva com múltiplos blocos
catch. - O erro é específico do domínio e merece uma classe com nome significativo.
- Você quer evitar o uso de códigos de erro mágicos ou mensagens soltas.
Evite criar exceções para cada situação trivial. Se um erro pode ser representado por uma exceção nativa (como InvalidArgumentException), prefira usá-la. O excesso de classes de exceção pode tornar o código confuso.
Boas práticas
- Nomeie as exceções de forma clara, usando sufixo "Exception".
- Mantenha a hierarquia: estenda
ExceptionouRuntimeExceptionconforme a necessidade (exceções verificadas vs. não verificadas). - Documente as exceções que seu método pode lançar usando PHPDoc (
@throws). - Considere criar uma exceção base para seu projeto, como
AppException, e derivar as demais.
Referências
- PHP Manual: Exceções
- PHP Manual: Classe Exception
- PHP Manual: Estendendo Exceções
- PSR-3: Logger Interface (relevante para logging de exceções)
- PHP Manual: RuntimeException
Exercícios
- Crie uma exceção customizada chamada
ArquivoNaoEncontradoExceptionque aceite o nome do arquivo como parâmetro adicional. Implemente um métodogetNomeArquivo(). - Escreva um trecho de código que lance uma exceção
SaldoInsuficienteException(como no exemplo) e a capture, exibindo o saldo atual e o valor solicitado. - Implemente o encadeamento de exceções: capture uma
PDOExceptione lance umaDatabaseException(crie essa classe) com a exceção original como anterior. - Crie uma exceção base
AppExceptione duas subclasses:ValidacaoExceptionePersistenciaException. Mostre como capturar a exceção base para tratar ambas. - Explique em um parágrafo quando você deve criar uma exceção customizada em vez de usar uma exceção nativa.
<?php
class ArquivoNaoEncontradoException extends \Exception {
private $nomeArquivo;
public function __construct($nomeArquivo, $message = "", $code = 0, Throwable $previous = null) {
$this->nomeArquivo = $nomeArquivo;
$message = $message ?: "Arquivo não encontrado: {$nomeArquivo}";
parent::__construct($message, $code, $previous);
}
public function getNomeArquivo(): string {
return $this->nomeArquivo;
}
}
?>try {
$conta = new Conta(100);
$conta->sacar(200);
} catch (SaldoInsuficienteException $e) {
echo "Saldo atual: R$ " . $e->getSaldoAtual() . "<br>";
echo "Valor solicitado: R$ " . $e->getValorSolicitado();
}
<?php
class DatabaseException extends \Exception {}
try {
// código PDO
} catch (PDOException $e) {
throw new DatabaseException("Erro no banco de dados", 0, $e);
}
?><?php
class AppException extends \Exception {}
class ValidacaoException extends AppException {}
class PersistenciaException extends AppException {}
try {
// código que pode lançar qualquer AppException
} catch (AppException $e) {
echo "Erro da aplicação: " . $e->getMessage();
}
?>