Tratamento global de erros
A aula aborda o tratamento global de erros em PHP, ensinando a usar set_error_handler e set_exception_handler para capturar erros e exceções de forma centralizada. Também cobre práticas de logging e diferenças entre ambientes de produção e desenvolvimento.
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 = []): boolSe 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
- PHP: set_error_handler - Manual
- PHP: set_exception_handler - Manual
- PHP: error_log - Manual
- PHP: error_reporting - Manual
- PHP: register_shutdown_function - Manual
- PSR-3: Logger Interface
Exercícios
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');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');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');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();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');