Error handling: set_error_handler e tipos de erro

1. Introdução ao tratamento de erros no PHP

No PHP, erros e exceções são conceitos distintos, embora ambos representem situações anormais durante a execução. Erros são problemas tradicionais do PHP, enquanto exceções são objetos que podem ser "lançados" e "capturados" com try/catch. Historicamente, o PHP tratava erros de forma procedural, mas versões modernas permitem maior integração entre esses dois mundos.

Os tipos de erro mais comuns incluem:

  • E_NOTICE: Avisos sobre código que pode causar problemas (ex: variável não definida)
  • E_WARNING: Avisos mais sérios que não interrompem a execução (ex: include de arquivo inexistente)
  • E_ERROR: Erros fatais que interrompem o script (ex: memória insuficiente)

A função error_reporting() controla quais níveis de erro serão reportados:

// Reportar todos os erros exceto E_NOTICE
error_reporting(E_ALL & ~E_NOTICE);

// Reportar apenas erros fatais e warnings
error_reporting(E_ERROR | E_WARNING);

Por padrão, o PHP exibe erros na saída (em desenvolvimento) ou os registra em log (em produção), dependendo das configurações de display_errors e log_errors no php.ini.

2. A função set_error_handler()

A função set_error_handler() permite substituir o manipulador padrão de erros do PHP por uma função personalizada. Sua sintaxe é:

set_error_handler(
    callable $callback,
    int $error_levels = E_ALL
): ?callable

O callback deve ter a seguinte assinatura:

function handler(
    int $errno,
    string $errstr,
    string $errfile = '',
    int $errline = 0,
    array $errcontext = []
): bool
  • $errno: Nível do erro (constante como E_WARNING)
  • $errstr: Mensagem descritiva do erro
  • $errfile: Arquivo onde o erro ocorreu
  • $errline: Linha do erro
  • $errcontext: Array com todas as variáveis no escopo do erro (obsoleto no PHP 8.0+)

Se o callback retornar false, o manipulador padrão do PHP será executado após o callback.

Exemplo básico:

function meuHandler(int $errno, string $errstr, string $errfile, int $errline): bool {
    echo "Erro [$errno]: $errstr em $errfile na linha $errline\n";
    return true; // Suprime o manipulador padrão
}

set_error_handler('meuHandler');

// Teste
echo $variavelInexistente; // Gera E_NOTICE, mas nosso callback trata

3. Hierarquia e tipos de erro tratáveis

Nem todos os erros podem ser capturados pelo set_error_handler(). Erros capturáveis incluem:

  • E_WARNING, E_NOTICE, E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE
  • E_DEPRECATED, E_USER_DEPRECATED
  • E_STRICT (obsoleto no PHP 8.0)

Erros não capturáveis (fatais):

  • E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING
  • E_COMPILE_ERROR, E_COMPILE_WARNING

As constantes de erro possuem valores numéricos:

Constante Valor Descrição
E_ERROR 1 Erro fatal
E_WARNING 2 Aviso
E_NOTICE 8 Notificação
E_USER_ERROR 256 Erro gerado pelo usuário
E_ALL 32767 Todos os erros

Dentro do callback, você pode verificar o nível do erro:

function handler(int $errno, string $errstr): bool {
    switch ($errno) {
        case E_WARNING:
            echo "Aviso: $errstr";
            break;
        case E_NOTICE:
            echo "Nota: $errstr";
            break;
        case E_USER_ERROR:
            echo "Erro do usuário: $errstr";
            exit(1);
    }
    return true;
}

4. Criando manipuladores personalizados

Uma técnica poderosa é converter erros em exceções usando a classe ErrorException:

function errorToException(int $errno, string $errstr, string $errfile, int $errline): bool {
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}

set_error_handler('errorToException');

try {
    // Código que pode gerar warning
    $resultado = 10 / 0; // Division by zero
} catch (ErrorException $e) {
    echo "Exceção capturada: " . $e->getMessage();
}

Para um handler robusto que diferencia ambientes:

function customErrorHandler(int $errno, string $errstr, string $errfile, int $errline): bool {
    $isDev = $_ENV['APP_ENV'] === 'development';

    // Log sempre
    error_log("[$errno] $errstr em $errfile:$errline");

    if ($isDev) {
        // Exibe detalhes no desenvolvimento
        echo "<pre>Erro: $errstr\nArquivo: $errfile\nLinha: $errline</pre>";
    } else {
        // Em produção, mostra mensagem genérica
        echo "Ocorreu um erro interno. Tente novamente mais tarde.";
    }

    return true;
}

set_error_handler('customErrorHandler');

5. Restaurando o manipulador padrão

A função restore_error_handler() restaura o manipulador anterior da pilha:

function meuHandler($errno, $errstr) {
    echo "Handler personalizado\n";
    return true;
}

set_error_handler('meuHandler');
// ... código que usa o handler personalizado ...

restore_error_handler(); // Volta ao handler padrão

O PHP mantém uma pilha de manipuladores. Cada chamada a set_error_handler() empilha um novo handler, e restore_error_handler() desempilha. Isso permite múltiplos manipuladores em diferentes partes do código:

function handler1($errno, $errstr) { echo "Handler 1\n"; return true; }
function handler2($errno, $errstr) { echo "Handler 2\n"; return true; }

set_error_handler('handler1');
set_error_handler('handler2');

trigger_error("Teste"); // Chama handler2

restore_error_handler(); // Remove handler2
trigger_error("Teste"); // Chama handler1

6. Erros fatais com register_shutdown_function

O set_error_handler() não captura erros fatais como E_ERROR. Para esses casos, use register_shutdown_function() combinado com error_get_last():

function shutdownHandler() {
    $error = error_get_last();
    if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
        echo "Erro fatal capturado: {$error['message']}\n";
        echo "Em {$error['file']}:{$error['line']}\n";
    }
}

register_shutdown_function('shutdownHandler');

// Exemplo que gera erro fatal
// $obj = new NonExistentClass(); // Erro fatal: Class not found

Combinando ambas as funções para cobertura completa:

function errorHandler($errno, $errstr, $errfile, $errline) {
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}

function shutdownHandler() {
    $error = error_get_last();
    if ($error && in_array($error['type'], [E_ERROR, E_PARSE])) {
        echo "Erro fatal: {$error['message']}";
    }
}

set_error_handler('errorHandler');
register_shutdown_function('shutdownHandler');

7. Boas práticas e armadilhas comuns

Evite suprimir erros com @: O operador @ suprime erros, mas também impede o callback de ser executado. Prefira verificar condições antes de executar código propenso a erros.

// Ruim
$conteudo = @file_get_contents('arquivo.txt');

// Bom
if (file_exists('arquivo.txt')) {
    $conteudo = file_get_contents('arquivo.txt');
}

Cuidado com loops infinitos: Se seu callback gerar um erro, ele chamará a si mesmo recursivamente. Sempre verifique se o código do callback é seguro:

function safeHandler($errno, $errstr) {
    // Não use funções que possam gerar erros aqui
    static $recursionGuard = false;
    if ($recursionGuard) return false;
    $recursionGuard = true;

    error_log("Erro: $errstr");

    $recursionGuard = false;
    return true;
}

Performance: Manipuladores personalizados têm custo. Use-os apenas quando necessário e evite operações pesadas dentro do callback.

Compatibilidade: No PHP 8.x, o parâmetro $errcontext foi removido do callback. Certifique-se de que sua assinatura seja compatível.

8. Exemplo prático completo

Abaixo, um sistema completo de tratamento de erros:

<?php

class ErrorHandler {
    private static bool $initialized = false;

    public static function init(): void {
        if (self::$initialized) return;
        self::$initialized = true;

        set_error_handler([self::class, 'handleError']);
        register_shutdown_function([self::class, 'handleShutdown']);
    }

    public static function handleError(
        int $errno,
        string $errstr,
        string $errfile = '',
        int $errline = 0
    ): bool {
        $isDev = ($_ENV['APP_ENV'] ?? 'production') === 'development';

        // Log sempre
        $logMessage = sprintf(
            "[%s] Erro %d: %s em %s:%d",
            date('Y-m-d H:i:s'),
            $errno,
            $errstr,
            $errfile,
            $errline
        );
        error_log($logMessage);

        if ($isDev) {
            // Em desenvolvimento, exibe detalhes e converte para exceção
            throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
        }

        // Em produção, apenas loga e retorna false para o handler padrão
        return false;
    }

    public static function handleShutdown(): void {
        $error = error_get_last();
        if ($error === null) return;

        $fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR];
        if (!in_array($error['type'], $fatalTypes, true)) return;

        $logMessage = sprintf(
            "[FATAL] %s: %s em %s:%d",
            date('Y-m-d H:i:s'),
            $error['message'],
            $error['file'],
            $error['line']
        );
        error_log($logMessage);

        if (($_ENV['APP_ENV'] ?? 'production') === 'development') {
            echo "<h1>Erro Fatal</h1>";
            echo "<p>{$error['message']}</p>";
            echo "<p>Em {$error['file']}:{$error['line']}</p>";
        } else {
            echo "Ocorreu um erro interno. Já fomos notificados.";
        }
    }
}

// Inicialização
ErrorHandler::init();

// Testes
echo $variavelInexistente; // E_NOTICE tratado
$resultado = 10 / 0;       // Division by zero

Este exemplo demonstra:
- Inicialização segura com guarda contra múltiplas chamadas
- Tratamento diferenciado para desenvolvimento e produção
- Log centralizado de todos os erros
- Captura de erros fatais via shutdown function
- Conversão de erros em exceções no ambiente de desenvolvimento

Lembre-se: um bom tratamento de erros não apenas evita que o usuário veja mensagens técnicas, mas também fornece informações valiosas para depuração e monitoramento do sistema.

Referências