Exceções: throw, try, catch, finally
1. Fundamentos do Tratamento de Exceções em PHP
Exceções são mecanismos para lidar com situações excepcionais durante a execução de um programa. Diferente dos erros tradicionais do PHP (como E_WARNING ou E_NOTICE), as exceções interrompem o fluxo normal do código e podem ser capturadas e tratadas de forma estruturada.
A classe base para exceções é \Exception, que implementa a interface \Throwable (introduzida no PHP 7). A interface \Throwable é a base para todos os objetos que podem ser lançados com throw, incluindo erros do PHP (\Error).
O fluxo básico do tratamento de exceções segue três etapas:
1. Lançar (throw) — quando uma condição excepcional é detectada
2. Capturar (catch) — onde a exceção é tratada
3. Finalizar (finally) — bloco opcional que sempre executa
<?php
try {
// Código que pode lançar exceção
if (!file_exists('config.php')) {
throw new Exception('Arquivo de configuração não encontrado');
}
} catch (Exception $e) {
echo 'Erro: ' . $e->getMessage();
}
2. Lançando Exceções com throw
A sintaxe do throw é simples: você pode lançar qualquer objeto que implemente a interface \Throwable. Na prática, isso significa instâncias de \Exception ou suas subclasses.
<?php
function dividir($a, $b) {
if ($b == 0) {
throw new Exception('Divisão por zero não permitida', 1001);
}
return $a / $b;
}
Boas práticas ao lançar exceções:
- Use mensagens descritivas que ajudem na depuração
- Utilize códigos de erro para categorizar exceções
- Prefira exceções específicas em vez de genéricas
<?php
class DivisaoPorZeroException extends Exception {}
function dividir($a, $b) {
if ($b == 0) {
throw new DivisaoPorZeroException(
'Tentativa de divisão de ' . $a . ' por zero',
1001
);
}
return $a / $b;
}
3. Bloco try e catch: Capturando Exceções
A estrutura try + catch permite capturar e tratar exceções de forma controlada. Você pode ter múltiplos blocos catch para diferentes tipos de exceção.
<?php
try {
$arquivo = fopen('dados.txt', 'r');
if (!$arquivo) {
throw new Exception('Não foi possível abrir o arquivo');
}
$conteudo = fread($arquivo, filesize('dados.txt'));
fclose($arquivo);
} catch (Exception $e) {
echo 'Erro: ' . $e->getMessage() . "\n";
echo 'Código: ' . $e->getCode() . "\n";
echo 'Arquivo: ' . $e->getFile() . "\n";
echo 'Linha: ' . $e->getLine() . "\n";
}
A ordem de captura é crucial: sempre capture as exceções mais específicas primeiro.
<?php
try {
// código que pode lançar diferentes exceções
} catch (DivisaoPorZeroException $e) {
// Mais específico primeiro
echo 'Erro de divisão por zero';
} catch (InvalidArgumentException $e) {
// Depois o menos específico
echo 'Argumento inválido';
} catch (Exception $e) {
// Por último, o genérico
echo 'Erro genérico: ' . $e->getMessage();
}
Os métodos mais úteis do objeto exceção:
- getMessage() — retorna a mensagem da exceção
- getCode() — retorna o código numérico
- getFile() — retorna o arquivo onde a exceção foi lançada
- getLine() — retorna a linha onde a exceção foi lançada
- getTrace() — retorna o stack trace completo
4. Bloco finally: Execução Garantida
O bloco finally é executado sempre, independentemente de uma exceção ter sido lançada ou não. É ideal para liberação de recursos.
<?php
function processarArquivo($caminho) {
$arquivo = null;
try {
$arquivo = fopen($caminho, 'r');
if (!$arquivo) {
throw new Exception('Falha ao abrir arquivo');
}
// Processa o arquivo...
return fread($arquivo, 1024);
} catch (Exception $e) {
echo 'Erro: ' . $e->getMessage();
return null;
} finally {
if ($arquivo) {
fclose($arquivo);
echo 'Arquivo fechado com sucesso';
}
}
}
Importante: se houver um return dentro do try ou catch, o finally ainda será executado antes do retorno. Se o finally também tiver um return, ele sobrescreverá o retorno anterior.
5. Hierarquia de Exceções e Exceções Customizadas
Criar exceções personalizadas permite uma captura mais granular e semântica.
<?php
class ValidationException extends Exception {}
class DatabaseException extends Exception {}
class AuthenticationException extends Exception {}
class UsuarioService {
public function salvar($dados) {
if (empty($dados['email'])) {
throw new ValidationException('Email é obrigatório', 400);
}
try {
// Tenta salvar no banco
$this->db->insert('usuarios', $dados);
} catch (PDOException $e) {
throw new DatabaseException(
'Falha ao salvar usuário: ' . $e->getMessage(),
500,
$e
);
}
}
}
// Uso com captura específica
try {
$service = new UsuarioService();
$service->salvar($_POST);
} catch (ValidationException $e) {
http_response_code(400);
echo json_encode(['erro' => $e->getMessage()]);
} catch (DatabaseException $e) {
http_response_code(500);
error_log($e->getMessage());
echo 'Erro interno do servidor';
}
6. Exceções Não Capturadas e o Manipulador Global
Quando uma exceção não é capturada, o PHP gera um erro fatal. Você pode registrar um manipulador global com set_exception_handler().
<?php
function manipuladorGlobal($exception) {
http_response_code(500);
echo json_encode([
'erro' => 'Erro interno do servidor',
'mensagem' => $exception->getMessage(),
'arquivo' => $exception->getFile(),
'linha' => $exception->getLine()
]);
error_log(
'Exceção não capturada: ' . $exception->getMessage() .
' em ' . $exception->getFile() . ':' . $exception->getLine()
);
}
set_exception_handler('manipuladorGlobal');
// Esta exceção será capturada pelo manipulador global
throw new Exception('Exemplo de exceção não capturada');
Diferença importante: set_exception_handler() lida com exceções não capturadas, enquanto set_error_handler() lida com erros tradicionais do PHP (como warnings e notices).
7. Boas Práticas e Armadilhas Comuns
Evite capturar exceções genéricas sem necessidade:
<?php
// Ruim - captura genérica demais
try {
// código
} catch (Exception $e) {
// Trata tudo igual
}
// Bom - captura específica
try {
// código
} catch (InvalidArgumentException $e) {
// Trata apenas argumentos inválidos
}
Relançamento correto de exceções:
<?php
try {
// código
} catch (Exception $e) {
// Correto: preserva o stack trace original
throw $e;
// Incorreto: perde o stack trace original
// throw new Exception($e->getMessage());
}
Não use exceções para controle de fluxo normal:
<?php
// Ruim - exceção para fluxo normal
function buscarUsuario($id) {
if ($id <= 0) {
throw new Exception('ID inválido');
}
// ...
}
// Bom - validação antes de lançar exceção
function buscarUsuario($id) {
if ($id <= 0) {
return null; // ou lançar exceção apenas se for realmente excepcional
}
// ...
}
Sempre faça logging adequado:
<?php
try {
// código crítico
} catch (DatabaseException $e) {
error_log('Falha no banco: ' . $e->getMessage());
// Apenas mostra mensagem amigável ao usuário
echo 'Ocorreu um erro. Tente novamente mais tarde.';
}
O tratamento de exceções bem implementado torna seu código mais robusto, previsível e fácil de depurar. Use exceções para situações verdadeiramente excepcionais e sempre forneça mensagens claras e ações adequadas para cada tipo de erro.
Referências
- PHP Manual: Exceções — Documentação oficial completa sobre exceções em PHP, incluindo sintaxe e exemplos.
- PHP The Right Way: Erros e Exceções — Guia prático sobre boas práticas no tratamento de erros e exceções.
- PHP Manual: set_exception_handler — Documentação oficial do manipulador global de exceções.
- PHP Manual: Throwable Interface — Detalhes sobre a interface base para todos os objetos lançáveis.
- PHP Watch: Exception Handling Best Practices — Artigo detalhado com melhores práticas e armadilhas comuns no tratamento de exceções.
- Stack Overflow: When to use exceptions vs errors — Discussão técnica sobre quando usar exceções em vez de erros tradicionais.
- PHP Manual: Extending Exceptions — Guia oficial para criação de classes de exceção personalizadas.