PHP

Tratamento de Erros e Exceções Já leu

11 min de leitura

Tratamento de Erros e Exceções
Todo programa encontra situações inesperadas: um arquivo que não existe, uma conexão de banco que falha, um valor inválido re

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


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 Throwable com cores separando Error (vermelho), LogicException (âmbar) e RuntimeException (verde)
  • Box de distinção LogicException vs RuntimeException — um dos conceitos mais confundidos em PHP
  • Desafio do padrão Result inspirado em Rust/Go — introduz alternativa moderna às exceções para fluxos esperados
Comentários

Mais em PHP

Exceções Avançadas
Exceções Avançadas

Tratamento de erros &eacute; onde c&oacute;digo bom se separa de c&oacute;dig...

Herança, Interfaces e Traits
Herança, Interfaces e Traits

No artigo anterior aprendemos a criar classes com propriedades, m&eacute;todo...

Arrays em Profundidade
Arrays em Profundidade

Arrays s&atilde;o a estrutura de dados mais usada no PHP. Praticamente tudo q...