Em resumo: O PHP comunica problemas de duas maneiras: como erros clássicos, como avisos e notificações, controlados através de error_reporting, e como exceções, que se apanham com try e catch. Desde o PHP 7, também muitos erros internos lançam um objeto da classe Error; em conjunto com Exception, implementa a interface Throwable. Em produção, vale isto: registar os erros e nunca os mostrar no navegador. No PHP 8.5, por causa do PHP2Shell, fatal_error_backtraces=0 faz também parte das medidas provisórias.
Dois mundos: erros clássicos e exceções
O PHP vem de uma época em que os erros eram sobretudo emitidos como mensagem: notificações (E_NOTICE), avisos (E_WARNING), funções obsoletas (E_DEPRECATED) e erros fatais (E_ERROR). Muitas destas mensagens deixam o script continuar, muitas vezes com dados errados ou vazios.
As exceções funcionam de outra forma. Interrompem o fluxo normal e saltam para o bloco catch adequado seguinte. Se nenhum for encontrado, o script termina. Para quem decide, a diferença é importante: um aviso silencioso pode fazer com que uma encomenda seja guardada de forma incompleta, sem que ninguém repare. Uma exceção torna o problema visível e tratável.
Um benefício subestimado das mensagens clássicas são as notificações do tipo E_DEPRECATED. Anunciam que um recurso da linguagem deixará de existir numa versão futura do PHP. Quem as recolhe no registo e as avalia com regularidade sabe, antes de uma atualização, que pontos têm de ser ajustados, e não precisa de testar durante muito tempo perante uma vulnerabilidade de segurança urgente.
A hierarquia Throwable
Tudo o que se pode lançar com throw e apanhar com catch implementa Throwable. Por baixo há dois ramos:
| Classe | Finalidade | Exemplos de subclasses |
|---|---|---|
Exception |
erros previsíveis da aplicação e das extensões | ErrorException, PDOException, classes próprias |
Error |
erros internos do PHP | TypeError, ValueError, ArgumentCountError, ArithmeticError, DivisionByZeroError, UnhandledMatchError |
As classes de erro próprias derivam de Exception, não de Error. Desde o PHP 8.0, a maioria das funções internas lança, perante parâmetros inválidos, um TypeError ou ValueError, em vez de apenas emitir um aviso. strlen([]) é um desses casos.
try, catch e finally na prática
<?php
declare(strict_types=1);
final class ZahlungFehlgeschlagen extends RuntimeException {}
function bezahlen(Bestellung $b, Zahlungsdienst $dienst, LoggerInterface $log): bool
{
try {
$dienst->belasten($b->betrag(), $b->referenz());
return true;
} catch (ZahlungFehlgeschlagen $e) {
$log->warning('Zahlung abgelehnt', ['bestellung' => $b->id(), 'exception' => $e]);
return false;
} catch (TimeoutException | VerbindungsFehler) {
// desde o PHP 8.0, a variável no catch é opcional
$log->error('Zahlungsdienst nicht erreichbar', ['bestellung' => $b->id()]);
return false;
} finally {
$dienst->verbindungSchliessen();
}
}
Várias classes podem ser reunidas num só catch com |. O bloco finally corre sempre, tenha ou não ocorrido um erro. Desde o PHP 8.0, o throw é além disso uma expressão e pode, por exemplo, surgir a seguir a ??: $id = $_GET['id'] ?? throw new InvalidArgumentException('id fehlt');.
Handlers globais: set_error_handler e set_exception_handler
Duas funções apanham o que de outro modo escaparia. O set_exception_handler() regista uma função para os Throwables não apanhados. Segundo o manual, o script termina depois de ela ser chamada; serve, portanto, para o registo e para uma página de erro neutra, não para continuar a trabalhar.
Revelou-se útil um handler que escreve no registo o erro completo, com a hora e um identificador aleatório, e mostra ao visitante apenas uma página neutra com esse identificador. O suporte consegue então encontrar o caso no registo, sem que detalhes internos cheguem ao exterior.
O set_error_handler() apanha erros clássicos. O manual mostra como convertê-los numa ErrorException, para que os avisos sejam tratados como exceções:
<?php
set_error_handler(function (int $nr, string $text, string $datei, int $zeile): bool {
if (!(error_reporting() & $nr)) {
return false; // excluído por error_reporting
}
if ($nr === E_DEPRECATED || $nr === E_USER_DEPRECATED) {
return false; // não escalar avisos sobre recursos obsoletos
}
throw new ErrorException($text, 0, $nr, $datei, $zeile);
});
Os erros fatais, como um excesso de memória, não se podem apanhar por este caminho. O que acontece por dentro num excesso explica-o a página memory_limit e Out-of-Memory.
Configurar a apresentação de erros e o registo
As definições mais importantes, com os valores predefinidos segundo o manual:
| Definição | Predefinição | Recomendação para produção |
|---|---|---|
error_reporting |
E_ALL desde o PHP 8.0.0 |
E_ALL |
display_errors |
1 |
Off |
display_startup_errors |
1 desde o PHP 8.0.0 |
Off |
log_errors |
0 |
On |
error_log |
vazio (registo do servidor web ou da SAPI) | ficheiro próprio fora da raiz web |
fatal_error_backtraces |
1, novo no PHP 8.5.0 |
temporariamente 0 (PHP2Shell) |
Desde o PHP 8.2.0, o error_log_mode define as permissões do ficheiro de registo, por predefinição 0644. Se os registos contiverem dados pessoais ou caminhos, é sensato usar um valor mais restritivo, para que outros utilizadores do servidor não os possam ler. Com ignore_repeated_errors é possível agrupar mensagens idênticas do mesmo ficheiro e da mesma linha, para que um ciclo de erros não inunde o registo.
O php.ini-production fornecido já define o display_errors como Off. Segundo o manual, o ini_set('display_errors', ...) em tempo de execução não tem efeito sobre erros fatais, e os erros de sintaxe ocorrem antes de qualquer ini_set() chegar a correr. Estes valores pertencem, por isso, à configuração do servidor. Os fundamentos constam do artigo Desativar a apresentação de erros em produção; como manter registos sem fuga de dados, no artigo Registar em segurança.
Tratamento de erros e PHP2Shell
Para o PHP 8.5, o php.net indica como uma das medidas provisórias contra o PHP2Shell definir fatal_error_backtraces=0 e reiniciar depois o PHP. As restantes medidas dizem respeito a memory_limit, post_max_size e session.upload_progress.enabled. Nenhuma delas substitui a atualização para o PHP 8.5.12, 8.4.27, 8.3.36 ou 8.2.35.
; apenas PHP 8.5, no php.ini ou no pool FPM, depois reiniciar todos os workers
fatal_error_backtraces = 0
Depois do PHP2Shell, os registos de erros são também um importante meio de prova. Se, a partir de 20/10/2026, se acumularem erros de memória ou workers interrompidos, isso pode indicar tentativas de ataque, mas pode também ter causas inofensivas. Conserve os registos e avalie-os em conjunto com os registos de acesso, como descrito no guia Avaliar os registos do servidor web após um ataque.
Como ajuda concretamente a PHP SOS?
- Verificamos as definições efetivas de apresentação de erros e de registo em cada site e ajustamo-las para produção.
- Aplicamos as medidas provisórias do PHP2Shell de acordo com a versão do PHP e reiniciamos todos os workers.
- Preservamos os registos de erros e de acesso a partir de 20/10/2026 e avaliamo-los quanto a anomalias.
- No âmbito da monitorização de segurança, comunicamos acumulações invulgares de erros, em vez de as deixar perder-se no registo.
Perguntas frequentes
Qual é a diferença entre Error e Exception?
Ambas implementam a interface Throwable. A Exception destina-se a erros que uma aplicação espera e trata, por exemplo uma ligação à base de dados falhada. O Error representa erros internos do PHP, como um TypeError ou uma divisão por zero.
O display_errors deve estar ativo em produção?
Não. As mensagens de erro no navegador revelam caminhos, consultas e, por vezes, dados de acesso. Em produção, o display_errors deve estar em Off e o log_errors em On, para que os erros fiquem no registo e não cheguem ao visitante.
É possível apanhar um excesso de memória com try/catch?
Não. Se um script excede o memory_limit, o PHP termina-o com um erro fatal que não pode ser apanhado como exceção. Aparece apenas no registo de erros, desde que o log_errors esteja ativo.
O que é o fatal_error_backtraces?
É uma definição que existe desde o PHP 8.5.0 e que acrescenta um backtrace aos erros fatais. Está ativa por predefinição. Como medida provisória contra o PHP2Shell, o php.net indica, para o PHP 8.5, defini-la como 0 e reiniciar depois o PHP.