Todo programa encontra situações inesperadas: um arquivo que não existe, uma conexão de banco que falha, um valor inválido recebido do usuário. A questão não é se esses problemas vão acontecer — é quando. A diferença entre um software robusto e um frágil é precisamente como ele lida com essas situações.
O PHP oferece dois mecanismos principais para isso: o sistema tradicional de erros — com níveis como E_WARNING e E_NOTICE — e o sistema moderno de exceções com try, catch e finally. Entender ambos, e saber quando usar cada um, é essencial para qualquer desenvolvedor PHP sério.
O sistema de erros do PHP
Antes das exceções, o PHP lidava com problemas através de um sistema de níveis de erro. Esses ainda existem e aparecem com frequência:
<?php
declare(strict_types=1);
// Níveis de erro mais comuns:
// E_ERROR — erro fatal, encerra o script
// E_WARNING — aviso, script continua
// E_NOTICE — informativo, script continua
// E_DEPRECATED — uso de recurso obsoleto
// Configurando exibição de erros (apenas em desenvolvimento!)
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL); // reporta todos os níveis
// Em produção, NUNCA exiba erros — apenas registre no log
// ini_set('display_errors', '0');
// ini_set('log_errors', '1');
// ini_set('error_log', '/var/log/php_errors.log');
// error_log() — registra uma mensagem no log manualmente
error_log("Algo inesperado aconteceu na função processar()");
// trigger_error() — dispara um erro customizado
trigger_error("Valor fora do intervalo esperado", E_USER_WARNING);
A partir do PHP 8, muitos comportamentos que antes geravam E_WARNING ou E_NOTICE silenciosamente agora lançam TypeError ou ValueError — um avanço importante para detectar problemas mais cedo.
Exceções: try, catch e finally
O sistema de exceções é a abordagem moderna para tratar erros em PHP. Uma exceção é um objeto que representa uma situação excepcional — algo que interrompeu o fluxo normal do programa:
<?php
declare(strict_types=1);
// Estrutura básica
try {
// Código que pode lançar uma exceção
// Se uma exceção for lançada, o PHP sai imediatamente deste bloco
// e vai para o catch correspondente
$resultado = dividir(10, 0);
echo $resultado; // esta linha NÃO executa se dividir() lançar exceção
} catch (InvalidArgumentException $e) {
// Captura apenas InvalidArgumentException (e subclasses)
echo "Argumento inválido: " . $e->getMessage() . "\n";
} catch (RuntimeException $e) {
// Captura RuntimeException se o catch anterior não pegou
echo "Erro em tempo de execução: " . $e->getMessage() . "\n";
} catch (Exception $e) {
// Captura qualquer Exception não capturada acima
echo "Erro inesperado: " . $e->getMessage() . "\n";
} finally {
// SEMPRE executa — com ou sem exceção, com ou sem return
// Ideal para liberar recursos: fechar conexões, arquivos, etc.
echo "Bloco finally executado.\n";
}
function dividir(int $a, int $b): float
{
if ($b === 0) {
// throw lança a exceção — interrompe a função imediatamente
throw new InvalidArgumentException("Divisão por zero não é permitida.");
}
return $a / $b;
}
A hierarquia de exceções do PHP
O PHP tem uma hierarquia bem definida de classes de erro e exceção. Entender essa hierarquia é fundamental para capturar as exceções certas:
Throwable (interface)
├── Error (erros internos do PHP)
│ ├── TypeError
│ ├── ValueError
│ ├── ArithmeticError
│ │ └── DivisionByZeroError
│ ├── ParseError
│ └── OutOfMemoryError (PHP 8.2)
│
└── Exception (exceções de aplicação)
├── LogicException
│ ├── BadFunctionCallException
│ │ └── BadMethodCallException
│ ├── DomainException
│ ├── InvalidArgumentException
│ ├── LengthException
│ └── OutOfRangeException
│
└── RuntimeException
├── OutOfBoundsException
├── OverflowException
├── RangeException
├── UnderflowException
└── UnexpectedValueException
<?php
declare(strict_types=1);
// TypeError — lançado automaticamente com strict_types
function somar(int $a, int $b): int
{
return $a + $b;
}
try {
somar(1, "dois"); // TypeError automático com strict_types=1
} catch (TypeError $e) {
echo "Tipo errado: " . $e->getMessage() . "\n";
}
// Error vs Exception — ambos implementam Throwable
// Você pode capturar ambos com Throwable
try {
// código que pode lançar Error ou Exception
operacaoArriscada();
} catch (Throwable $e) {
// captura qualquer Error ou Exception
echo get_class($e) . ": " . $e->getMessage() . "\n";
}
// Capturar múltiplos tipos no mesmo catch — PHP 8
try {
processar($entrada);
} catch (InvalidArgumentException | ValueError $e) {
echo "Valor inválido: " . $e->getMessage() . "\n";
}
Criando exceções customizadas
Em projetos reais, você cria suas próprias exceções para representar erros específicos do domínio da aplicação. Isso torna o código mais expressivo e o tratamento de erros mais preciso:
<?php
declare(strict_types=1);
// Exceção base do domínio — todas as exceções da aplicação herdam desta
class AppException extends RuntimeException {}
// Exceções específicas do domínio
class UsuarioNaoEncontradoException extends AppException
{
public function __construct(int $id)
{
// Chama o construtor pai com uma mensagem descritiva
parent::__construct("Usuário com ID $id não encontrado.");
}
}
class SaldoInsuficienteException extends AppException
{
public function __construct(
private readonly float $saldoAtual,
private readonly float $valorSolicitado,
) {
parent::__construct(
"Saldo insuficiente. Disponível: R$ {$saldoAtual}. Solicitado: R$ {$valorSolicitado}."
);
}
public function getSaldoAtual(): float { return $this->saldoAtual; }
public function getValorSolicitado(): float { return $this->valorSolicitado; }
}
// Usando as exceções customizadas
function buscarUsuario(int $id): array
{
$usuarios = [1 => ["nome" => "Ana"], 2 => ["nome" => "Bruno"]];
if (!isset($usuarios[$id])) {
throw new UsuarioNaoEncontradoException($id);
}
return $usuarios[$id];
}
function realizarSaque(float $saldo, float $valor): float
{
if ($valor > $saldo) {
throw new SaldoInsuficienteException($saldo, $valor);
}
return $saldo - $valor;
}
// Tratamento preciso por tipo de exceção
try {
$usuario = buscarUsuario(99);
} catch (UsuarioNaoEncontradoException $e) {
echo $e->getMessage(); // "Usuário com ID 99 não encontrado."
}
try {
$novoSaldo = realizarSaque(100.0, 250.0);
} catch (SaldoInsuficienteException $e) {
echo $e->getMessage();
echo "Faltam R$ " . ($e->getValorSolicitado() - $e->getSaldoAtual()) . "\n";
}
finally — garantindo limpeza de recursos
O bloco finally é executado sempre — seja o código bem-sucedido, seja uma exceção lançada, seja um return executado dentro do try. É o lugar ideal para liberar recursos:
<?php
declare(strict_types=1);
function processarArquivo(string $caminho): string
{
$arquivo = null;
try {
$arquivo = fopen($caminho, 'r');
if ($arquivo === false) {
throw new RuntimeException("Não foi possível abrir: $caminho");
}
$conteudo = fread($arquivo, filesize($caminho));
if ($conteudo === false) {
throw new RuntimeException("Erro ao ler o arquivo: $caminho");
}
return $conteudo; // mesmo com return aqui...
} catch (RuntimeException $e) {
error_log($e->getMessage());
return ""; // ...ou com return no catch...
} finally {
// ...finally SEMPRE executa
// Garante que o arquivo será fechado em qualquer situação
if ($arquivo !== null && is_resource($arquivo)) {
fclose($arquivo);
}
echo "Arquivo processado (recurso liberado).\n";
}
}
Re-lançando exceções
Às vezes você quer capturar uma exceção, fazer algo com ela (como registrar no log), e então relançar para que o chamador possa tratá-la também. Ou quer envolver uma exceção de baixo nível em uma de mais alto nível:
<?php
declare(strict_types=1);
class DatabaseException extends RuntimeException {}
function buscarDoBanco(int $id): array
{
try {
// Simula uma operação de banco que pode falhar
if ($id <= 0) {
throw new InvalidArgumentException("ID deve ser positivo.");
}
// Simula falha de conexão
// $pdo->query("SELECT * FROM usuarios WHERE id = $id");
} catch (InvalidArgumentException $e) {
// Re-lança sem envolver — sobe para o chamador
throw $e;
} catch (\PDOException $e) {
// Envolve exceção de baixo nível em exceção de domínio
// O segundo argumento é a "exceção anterior" — preserva o stack trace original
throw new DatabaseException(
"Falha ao buscar usuário $id no banco.",
previous: $e // PHP 8: named argument
);
}
return [];
}
// Acessando a cadeia de exceções
try {
buscarDoBanco(-1);
} catch (DatabaseException $e) {
echo $e->getMessage() . "\n";
// $e->getPrevious() retorna a exceção original
if ($anterior = $e->getPrevious()) {
echo "Causa: " . $anterior->getMessage() . "\n";
}
}
Boas práticas no tratamento de erros
Capture exceções específicas, não genéricas. Capturar Exception em todo lugar esconde problemas. Capture o tipo mais específico possível e deixe as inesperadas borbulhar.
Não use exceções para controle de fluxo normal. Exceções são para situações excepcionais. Verificar se um usuário existe antes de buscá-lo é melhor do que lançar uma exceção e capturá-la em cada chamada.
Sempre registre exceções não tratadas. Qualquer exceção que chegue ao topo da pilha de chamadas deve ser registrada em log — nunca engolida silenciosamente.
Use finally para liberar recursos. Sempre que abrir um arquivo, conexão ou lock, use finally para garantir que será fechado.
<?php
declare(strict_types=1);
// ✗ Exceção para controle de fluxo — evite
function processarUsuario(int $id): void
{
try {
$usuario = buscarUsuario($id);
// processa...
} catch (UsuarioNaoEncontradoException $e) {
// simplesmente ignora — usuário não existe
}
}
// ✓ Verificação explícita — mais claro
function processarUsuario(int $id): void
{
if (!usuarioExiste($id)) {
return; // simplesmente não processa
}
$usuario = buscarUsuario($id);
// processa...
}
// ✗ Engolir exceção silenciosamente — muito perigoso
try {
operacaoCritica();
} catch (Exception $e) {
// nada aqui — você nunca vai saber que isso falhou!
}
// ✓ Sempre registre
try {
operacaoCritica();
} catch (Exception $e) {
error_log("[ERRO] " . $e->getMessage() . " em " . $e->getFile() . ":" . $e->getLine());
throw $e; // re-lança para o chamador tratar também
}
Resumo
| Conceito | O que aprendemos |
|---|---|
try / catch |
Tenta executar código e captura exceções por tipo |
finally |
Sempre executa — use para liberar recursos |
throw |
Lança uma exceção — interrompe o fluxo |
Throwable |
Interface raiz — captura Error e Exception |
Error |
Erros internos do PHP: TypeError, ValueError |
Exception |
Base para exceções de aplicação |
LogicException |
Problemas de lógica detectáveis em desenvolvimento |
RuntimeException |
Problemas que só aparecem em tempo de execução |
| Exceções customizadas | Estendem RuntimeException ou LogicException |
getPrevious() |
Acessa a exceção original em cadeias de exceção |
error_log() |
Registra mensagem no log do servidor |
Referências e leituras para aprofundar
-
Exceções — Manual oficial do PHP https://www.php.net/manual/pt_BR/language.exceptions.php Documentação completa do sistema de exceções, incluindo
finally, cadeias de exceção e a interfaceThrowable. -
Exceções predefinidas — Manual oficial do PHP https://www.php.net/manual/pt_BR/spl.exceptions.php Lista completa das exceções da SPL com descrição de quando usar cada uma.
-
Erros no PHP 8 — Manual oficial https://www.php.net/manual/pt_BR/language.errors.php8.php Como o sistema de erros mudou no PHP 8 — muitos warnings se tornaram
TypeErroreValueError. -
PHP: The Right Way — Exceptions https://phptherightway.com/#exceptions Seção sobre boas práticas com exceções, incluindo quando usar e quando evitar.
-
MARTIN, Robert C. Clean Code. Prentice Hall, 2008. Capítulo 7: Error Handling. https://www.oreilly.com/library/view/clean-code-a/9780136083238/ O capítulo mais relevante do livro para este artigo. Trata de usar exceções em vez de códigos de retorno, não engolir exceções e separar a lógica de negócio do tratamento de erros.
-
Fowler, Martin. Refactoring, 2ª ed. Capítulo: Replace Error Code with Exception. https://refactoring.com/catalog/replaceErrorCodeWithException.html Padrão de refatoração que descreve exatamente como migrar de códigos de retorno (
false,-1) para exceções.
No próximo artigo: Orientação a Objetos — classes, objetos, propriedades, métodos e os fundamentos do paradigma OOP em PHP.
Artigo 10 entregue — texto acima e HTML aqui.
Destaques deste artigo:
- Diagrama de fluxo visual mostrando a progressão try → throw → catch → finally com cores distintas por papel
- Árvore de hierarquia completa de
Throwablecom cores separandoError(vermelho),LogicException(âmbar) eRuntimeException(verde) - Box de distinção
LogicExceptionvsRuntimeException— um dos conceitos mais confundidos em PHP - Desafio do padrão
Resultinspirado em Rust/Go — introduz alternativa moderna às exceções para fluxos esperados