Em aplicações PHP reais, lidar com erros e exceções de forma dispersa em cada trecho de código é ineficiente e propenso a falhas. O tratamento global de erros permite centralizar a captura e resposta a erros, garantindo consistência e facilitando a manutenção. Nesta aula, exploraremos as funções set_error_handler e set_exception_handler, que permitem definir manipuladores globais para erros e exceções não capturados.

Além disso, discutiremos como registrar erros em logs (logging) e como configurar o tratamento de forma diferente para ambientes de desenvolvimento e produção, adotando boas práticas de segurança e usabilidade.

set_error_handler

A função set_error_handler permite definir uma função de callback que será executada sempre que um erro do PHP ocorrer (exceto erros fatais como E_ERROR, E_PARSE, etc.). Ela substitui o manipulador de erros padrão do PHP. A assinatura do callback é:

function handler(int $errno, string $errstr, string $errfile = '', int $errline = 0, array $errcontext = []): bool

Se o callback retornar false, o manipulador de erros padrão do PHP continuará sendo executado. Caso contrário, o erro é tratado pelo callback e a execução do script prossegue (a menos que você chame exit ou die).

Exemplo prático:

function meuManipuladorErro($errno, $errstr, $errfile, $errline) {
    echo "Erro [$errno]: $errstr em $errfile na linha $errline\n";
    return true;
}
set_error_handler('meuManipuladorErro');

echo $variavelInexistente; // Dispara um notice, mas será capturado pelo nosso handler

É importante notar que set_error_handler não captura exceções. Para exceções não capturadas, usamos set_exception_handler.

set_exception_handler

Exceções não capturadas (não tratadas por blocos try/catch) podem ser capturadas globalmente com set_exception_handler. O callback recebe um objeto Throwable (que abrange tanto Exception quanto Error).

Exemplo:

function meuManipuladorExcecao($exception) {
    echo "Exceção não capturada: " . $exception->getMessage() . "\n";
    // Registrar em log, exibir página de erro amigável, etc.
}
set_exception_handler('meuManipuladorExcecao');

throw new \RuntimeException('Algo deu errado!');

Após a execução do manipulador, o script é encerrado (a menos que você chame exit explicitamente).

Logging

Em produção, exibir erros na tela é inseguro e anti-profissional. Em vez disso, devemos registrar os erros em arquivos de log. O PHP possui a função error_log() para enviar mensagens para o log do sistema ou para um arquivo específico.

Exemplo de uso no manipulador:

function meuManipuladorErro($errno, $errstr, $errfile, $errline) {
    $mensagem = "[$errno] $errstr em $errfile:$errline";
    error_log($mensagem, 3, '/var/log/minha_app/erros.log');
    return true;
}

Você também pode usar bibliotecas como Monolog para logging mais avançado, com níveis (debug, info, warning, error) e múltiplos destinos (arquivo, banco, e-mail).

Produção vs desenvolvimento

Em desenvolvimento, queremos ver todos os erros detalhadamente para depuração. Em produção, devemos ocultar detalhes do usuário e registrar tudo em logs. A configuração pode ser feita com error_reporting e display_errors.

Exemplo de configuração condicional:

if (getenv('APP_ENV') === 'production') {
    error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
    ini_set('display_errors', '0');
    ini_set('log_errors', '1');
    ini_set('error_log', '/var/log/minha_app/php_errors.log');
    set_error_handler('manipuladorProducao');
    set_exception_handler('manipuladorExcecaoProducao');
} else {
    error_reporting(E_ALL);
    ini_set('display_errors', '1');
    set_error_handler('manipuladorDesenvolvimento');
    set_exception_handler('manipuladorExcecaoDesenvolvimento');
}

No manipulador de produção, você pode exibir uma página amigável de erro 500 e registrar os detalhes. No de desenvolvimento, pode exibir mensagens detalhadas com stack trace.

Boas práticas e observações finais

Sempre registre erros fatais (E_ERROR) com register_shutdown_function para capturar o último erro, pois set_error_handler não os captura. Use error_get_last() dentro da função de shutdown. Além disso, considere usar o operador @ com moderação, pois ele suprime erros silenciosamente. Prefira tratamento explícito com try/catch para exceções.

Referências

Exercícios

  1. Crie um manipulador de erros global que registre todos os erros (exceto os suprimidos por @) em um arquivo 'erros.log' com formato: [DATA_HORA] Nível: (E_WARNING, E_NOTICE, etc.) Mensagem: ... Arquivo: ... Linha: ...

    ✓ Resposta:
    function manipuladorErro($errno, $errstr, $errfile, $errline) {
        $niveis = [
            E_WARNING => 'E_WARNING',
            E_NOTICE => 'E_NOTICE',
            E_USER_ERROR => 'E_USER_ERROR',
            E_USER_WARNING => 'E_USER_WARNING',
            E_USER_NOTICE => 'E_USER_NOTICE',
            // outros níveis...
        ];
        $nivel = isset($niveis[$errno]) ? $niveis[$errno] : 'DESCONHECIDO';
        $data = date('Y-m-d H:i:s');
        $log = "[$data] Nível: $nivel Mensagem: $errstr Arquivo: $errfile Linha: $errline" . PHP_EOL;
        error_log($log, 3, 'erros.log');
        return true;
    }
    set_error_handler('manipuladorErro');
    
  2. Implemente um manipulador de exceções global que, em ambiente de desenvolvimento, exiba uma mensagem detalhada com stack trace, e em produção, exiba uma mensagem genérica "Ocorreu um erro interno" e registre os detalhes em log.

    ✓ Resposta:
    function manipuladorExcecao($exception) {
        if (getenv('APP_ENV') === 'production') {
            error_log($exception->__toString(), 3, '/var/log/app/erros.log');
            echo 'Ocorreu um erro interno';
        } else {
            echo '
    ' . $exception->__toString() . '
    '; } } set_exception_handler('manipuladorExcecao');
  3. Utilizando a função register_shutdown_function, capture erros fatais (E_ERROR) e registre-os em um log. Dica: use error_get_last() dentro da função de shutdown.

    ✓ Resposta:
    function capturaErroFatal() {
        $erro = error_get_last();
        if ($erro !== null && in_array($erro['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
            $log = sprintf("[%s] Erro Fatal: %s em %s:%d\n", date('Y-m-d H:i:s'), $erro['message'], $erro['file'], $erro['line']);
            error_log($log, 3, 'erros_fatais.log');
        }
    }
    register_shutdown_function('capturaErroFatal');
    
  4. Crie uma classe ErrorHandler que encapsule os manipuladores de erro e exceção, permitindo configurar o ambiente (dev/prod) e o caminho do arquivo de log.

    ✓ Resposta:
    class ErrorHandler {
        private $logFile;
        private $environment;
    
        public function __construct($logFile, $environment = 'development') {
            $this->logFile = $logFile;
            $this->environment = $environment;
        }
    
        public function register() {
            set_error_handler([$this, 'handleError']);
            set_exception_handler([$this, 'handleException']);
            register_shutdown_function([$this, 'handleShutdown']);
        }
    
        public function handleError($errno, $errstr, $errfile, $errline) {
            $this->log("Erro [$errno]: $errstr em $errfile:$errline");
            if ($this->environment === 'development') {
                echo "

    Erro: $errstr

    "; } return true; } public function handleException($exception) { $this->log($exception->__toString()); if ($this->environment === 'development') { echo '
    ' . $exception->__toString() . '
    '; } else { echo 'Ocorreu um erro interno. Tente novamente mais tarde.'; } } public function handleShutdown() { $erro = error_get_last(); if ($erro && in_array($erro['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) { $this->log('Erro Fatal: ' . $erro['message'] . ' em ' . $erro['file'] . ':' . $erro['line']); } } private function log($message) { $data = date('Y-m-d H:i:s'); $log = "[$data] $message" . PHP_EOL; error_log($log, 3, $this->logFile); } } // Uso: $handler = new ErrorHandler('/var/log/app/erros.log', getenv('APP_ENV') ?: 'development'); $handler->register();
  5. Escreva um script que force um erro de tipo (E_WARNING) passando um argumento não esperado para uma função que espera um inteiro, e verifique se o manipulador global o captura. Em seguida, force uma exceção não capturada e veja o manipulador de exceção agir.

    ✓ Resposta:
    // Configurar manipuladores globais (exemplo simples)
    set_error_handler(function($errno, $errstr) {
        echo "Capturado erro: $errstr\n";
        return true;
    });
    set_exception_handler(function($exception) {
        echo "Capturada exceção: " . $exception->getMessage() . "\n";
    });
    
    // Forçar E_WARNING: passando string onde se espera int
    function esperaInt(int $valor) {}
    esperaInt('não é número'); // Produz E_WARNING: Argument 1 passed to esperaInt() must be of the type int, string given
    
    // Forçar exceção não capturada
    throw new \InvalidArgumentException('Exceção proposital');