PHP Tipos de Erro
Até aqui você já viu muitas mensagens do PHP: Warning, Fatal error, Deprecated… Elas não são todas iguais. Algumas são só um aviso e o programa continua; outras param tudo. Algumas devem aparecer na tela enquanto você programa e nunca na tela do cliente. Nesta aula você vai conhecer os níveis de erro do PHP, decidir quais mostrar e onde guardar os que não devem aparecer.
- Os níveis:
Warning,Notice,Deprecated,Fatal erroreParse error, com exemplos reais de cada um. - Quais param o programa e quais não.
- Escolher o que é relatado com
error_reporting. - Mostrar (
display_errors) ou esconder os erros, e a diferença entre desenvolvimento e produção. - Guardar os erros num arquivo (
log_errors,error_log), inclusive mensagens suas. - Por que no PHP 8 muitos erros viraram exceções (
Error,TypeError,ValueError,DivisionByZeroError).
A aula Lendo Erros do PHP, do Nível 1, ensinou a ler uma mensagem (tipo, texto, arquivo e linha). Aqui o assunto é o que fazer com elas.
A ideia: o painel do carro
O painel de um carro tem luzes de cores diferentes. A luz amarela do óleo diz "cuide disso logo", mas o carro continua andando. A luz vermelha do motor diz "pare agora". E tem o aviso do manual: "este modelo de pneu sai de linha no ano que vem". O PHP faz o mesmo:
| Mensagem | Nível (constante) | No carro | O programa… |
|---|---|---|---|
Deprecated | E_DEPRECATED | "Isto vai deixar de funcionar numa versão futura" | continua |
Notice | E_NOTICE | "Reparei numa coisa estranha" | continua |
Warning | E_WARNING | Luz amarela: algo deu errado | continua |
Fatal error | E_ERROR (ou uma exceção não capturada) | Luz vermelha | para |
Parse error | E_PARSE | O carro nem liga | nem começa |
Cada nível, com um exemplo real
Warning: algo deu errado, mas o PHP seguiu
<?php echo "Início\n"; echo $nome; $precos = ['bolo' => 35]; echo $precos['torta']; echo "Fim\n";
$ cd erros $ php warning.php; echo "código: $?" Início Warning: Undefined variable $nome in /home/ana/erros/warning.php on line 3 Warning: Undefined array key "torta" in /home/ana/erros/warning.php on line 5 Fim « continuou até o fim código: 0 « e terminou "com sucesso"
Variável que não existe, chave de array que não existe, arquivo que não abre (você viu em Arquivos): o PHP usa null no lugar, avisa e continua. É perigoso justamente por isso: o programa segue com um valor vazio, e o problema aparece mais adiante, longe da causa. Trate todo Warning como um bug a consertar.
Notice: "reparei numa coisa"
No PHP 8 sobraram poucos Notice (a maioria virou Warning). Um que ainda aparece:
<?php
$fim = end(explode(',', 'a,b,c'));
echo $fim, "\n";
$ php notice.php Notice: Only variables should be passed by reference in /home/ana/erros/notice.php on line 2 c
"Só variáveis deveriam ser passadas por referência": a função end quer mexer no array que recebe, e por isso pede uma variável, não o resultado direto de outra função. Funcionou, mas o PHP reclamou. O conserto: $partes = explode(',', 'a,b,c'); $fim = end($partes);.
Deprecated: "isto vai sair de linha"
Um aviso de que você está usando algo que o PHP pretende remover ou mudar. Hoje funciona; numa versão futura, pode virar erro.
<?php $texto = null; echo strlen($texto), "\n"; $n = (integer) "42"; echo $n, "\n";
$ php deprecated.php Deprecated: Non-canonical cast (integer) is deprecated, use the (int) cast instead in /home/ana/erros/deprecated.php on line 4 Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in /home/ana/erros/deprecated.php on line 3 0 42
- Linha 3: passar
nullpara uma função do PHP que espera texto está obsoleto desde o PHP 8.1. Usestrlen($texto ?? ''). - Linha 4: o nome longo
(integer)está obsoleto desde o PHP 8.5; use(int)(veja Conversão de Tipos).
Repare na ordem: o aviso da linha 4 apareceu antes do da linha 3. Ele é detectado quando o PHP lê o arquivo (antes de executar), enquanto o da linha 3 só aparece quando a linha roda. Ao atualizar o PHP de versão, a primeira coisa a fazer é rodar o sistema e caçar os Deprecated.
Fatal error: o programa para
<?php echo "Antes\n"; calcular_frete(10); echo "Depois\n";
$ php fatal.php; echo "código: $?"
Antes
Fatal error: Uncaught Error: Call to undefined function calcular_frete() in /home/ana/erros/fatal.php:3
Stack trace:
#0 {main}
thrown in /home/ana/erros/fatal.php on line 3
código: 255 « "Depois" nunca apareceu
Chamar uma função que não existe não tem como "seguir em frente": o PHP para. Repare no Uncaught Error ("erro não capturado"): no PHP 8, a maioria dos erros fatais é, na verdade, uma exceção que ninguém tratou. Isso tem uma consequência boa, que você vai ver no fim da aula. Alguns poucos fatais continuam sendo "fatais de verdade", sem exceção, como o de memória esgotada:
$ php -d memory_limit=16M memoria.php; echo "código: $?" « um laço que enche um array
Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 1052672 bytes) in /home/ana/erros/memoria.php on line 4
Stack trace:
#0 {main}
código: 255
"O tamanho de memória permitido, de 16 MB, se esgotou". O -d memory_limit=16M mudou a configuração só para essa execução (o padrão costuma ser 128 MB).
Parse error: o programa nem começa
<?php echo "Linha 1\n"; echo "Linha 2\n" echo "Linha 3\n";
$ php parse.php; echo "código: $?" Parse error: syntax error, unexpected token "echo", expecting "," or ";" in /home/ana/erros/parse.php on line 4 código: 255 « nem a "Linha 1" apareceu $ php -l parse.php Parse error: syntax error, unexpected token "echo", expecting "," or ";" in parse.php on line 4 Errors parsing parse.php
Antes de executar, o PHP lê o arquivo inteiro para entender a "gramática". Um ponto e vírgula esquecido na linha 3 é percebido na linha 4 ("encontrei echo, esperava , ou ;"). Por isso, num Parse error, olhe também a linha de cima. O php -l (lint, "verificar") só confere a sintaxe, sem executar nada.
Missão: rode e veja os dois Warning. Depois conserte o programa, sem esconder os avisos: use o operador ?? (aula ?? e ?->) para que a falta do nome mostre visitante e a falta do preço mostre 0.
<?php $precos = ['bolo' => 35]; echo "Cliente: " . $nome . "\n"; echo "Torta: R$ " . $precos['torta'] . "\n";
<?php $precos = ['bolo' => 35]; echo "Cliente: " . ($nome ?? 'visitante') . "\n"; echo "Torta: R$ " . ($precos['torta'] ?? 0) . "\n";
Escolher o que relatar: error_reporting
Cada nível é um número (uma "constante"), e error_reporting diz ao PHP quais níveis ele deve levar em conta:
$ php constantes.php « mostra o valor de cada constante E_ERROR 1 E_WARNING 2 E_PARSE 4 E_NOTICE 8 E_DEPRECATED 8192 E_USER_ERROR 256 E_USER_WARNING 512 E_USER_NOTICE 1024 E_USER_DEPRECATED 16384 E_ALL 30719
E_ALL é "todos". As de E_USER_… são para erros que você dispara com trigger_error (mais abaixo). Para combinar, usa-se & (e) e ~ (não): E_ALL & ~E_DEPRECATED = "todos, menos os Deprecated".
<?php error_reporting(E_ALL & ~E_WARNING); echo $nome; echo "Sem aviso de Warning\n"; error_reporting(E_ALL); echo $nome; echo "\n", error_reporting(), " ", E_ALL, "\n";
$ php reporting.php Sem aviso de Warning « o Warning da linha 3 foi ignorado Warning: Undefined variable $nome in /home/ana/erros/reporting.php on line 6 30719 30719 « chamada sem argumento, error_reporting() diz o valor atual
E_ALL
Desligar níveis de erro não conserta nada: só esconde. A regra é relatar tudo (E_ALL, o padrão do PHP 8) e decidir apenas onde as mensagens aparecem, que é o assunto da próxima seção.
Mostrar ou não: display_errors
display_errors ("mostrar os erros") decide se as mensagens vão para a saída: o terminal, ou a página que o visitante vê. No terminal, você já conhece o resultado. Num site, a mensagem vai no meio do HTML, formatada com tags. A Ana rodou esta página no servidor embutido (php -S):
<?php $precos = ['bolo' => 35]; ?> <p>Torta: R$ <?= $precos['torta'] ?></p>
$ curl -s localhost:8000/ <p>Torta: R$ <br /> <b>Warning</b>: Undefined array key "torta" in <b>/home/ana/erros/web/index.php</b> on line <b>4</b><br /> </p>
No navegador, o visitante veria Warning em negrito, com o caminho completo do arquivo no servidor. Para você, programando, isso é ótimo. Para o cliente da lojinha, é feio e perigoso: revela como o site é organizado por dentro (pastas, nomes de arquivos, às vezes trechos de SQL), o que ajuda quem quer atacá-lo.
Você pode mudar display_errors dentro do programa com ini_set ("definir configuração"):
<?php
ini_set('display_errors', '0');
echo $nome;
echo "O aviso não apareceu\n";
$ php display.php O aviso não apareceu
O erro continua acontecendo; só não é mostrado. Mas então como você vai saber que ele aconteceu? Com o registro (log).
Guardar num arquivo: log_errors e error_log
<?php
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/erros.log');
echo $nome;
error_log('Pedido 42 sem endereço');
echo "Página normal para o visitante\n";
| Linha | O que faz |
|---|---|
ini_set('display_errors', '0') | Não mostra os erros na saída. |
ini_set('log_errors', '1') | Liga o registro de erros. |
ini_set('error_log', __DIR__ . '/erros.log') | Diz em que arquivo registrar. Sem isso, vai para o registro do servidor (ou, no terminal, para a saída de erros). |
error_log('Pedido 42 sem endereço') | A função error_log escreve uma mensagem sua no mesmo registro. Ótima para anotar o que aconteceu sem mostrar nada ao visitante. |
$ php log.php Página normal para o visitante « o visitante não vê nada de errado $ cat erros.log [16-Oct-2026 23:59:51 UTC] PHP Warning: Undefined variable $nome in /home/ana/erros/log.php on line 5 [16-Oct-2026 23:59:51 UTC] Pedido 42 sem endereço
Cada linha tem a data e a hora (em UTC: o fuso, de novo, como em Datas e Horas), o nível com o prefixo PHP e a mensagem. Em produção, alguém (ou um programa) olha esse arquivo regularmente.
PHP Warning: e Warning:
Com log_errors ligado e sem arquivo definido, o PHP de linha de comando manda o registro para a saída de erros (stderr) e a mensagem normal para a saída padrão. Por isso às vezes você vê cada aviso duas vezes:
$ php -d log_errors=1 warning.php Início PHP Warning: Undefined variable $nome in /home/ana/erros/warning.php on line 3 « o registro (stderr) Warning: Undefined variable $nome in /home/ana/erros/warning.php on line 3 « o display (stdout) PHP Warning: Undefined array key "torta" in /home/ana/erros/warning.php on line 5 Warning: Undefined array key "torta" in /home/ana/erros/warning.php on line 5 Fim $ php -d log_errors=1 warning.php 2>/dev/null « jogando o stderr fora, sobra só o display Início Warning: Undefined variable $nome in /home/ana/erros/warning.php on line 3 Warning: Undefined array key "torta" in /home/ana/erros/warning.php on line 5 Fim
O PHP do laboratório da Ana e o do editor do guia têm log_errors desligado; em outras instalações (como o PHP do apt no Ubuntu e no Debian), o php.ini liga o log_errors, e aí você verá as mensagens com o prefixo PHP, às vezes em dobro. Confira a sua com o php -i mostrado logo abaixo.
Desenvolvimento × produção
As configurações moram no php.ini. O PHP vem com dois modelos prontos, php.ini-development e php.ini-production, e a diferença essencial é esta:
| Configuração | Desenvolvimento (seu computador) | Produção (o site no ar) |
|---|---|---|
error_reporting | E_ALL | E_ALL (o modelo de produção tira E_DEPRECATED) |
display_errors | On: ver tudo na hora | Off: o visitante nunca vê |
display_startup_errors | On | Off |
log_errors | On | On: tudo vai para o registro |
error_log | (opcional) | Um arquivo fora da pasta pública, como /var/log/php/erros.log |
Para ver o que está valendo na sua máquina, use php --ini (qual php.ini foi carregado) e php -i (todas as configurações). No laboratório da Ana:
$ php --ini | head -2 Configuration File (php.ini) Path: "/usr/local/etc/php" Loaded Configuration File: (none) « nenhum php.ini: valores padrão $ php -i | grep -E '^(display_errors|display_startup_errors|error_reporting|log_errors|error_log|html_errors) ' display_errors => STDOUT => STDOUT display_startup_errors => On => On error_log => no value => no value error_reporting => no value => no value « "sem valor" = o padrão, E_ALL html_errors => Off => Off log_errors => Off => Off
ini_set ou php.ini?
O php.ini é o lugar certo para a configuração do servidor. ini_set no começo do programa é útil quando você não tem acesso ao php.ini (hospedagem compartilhada). Mas atenção: ini_set só vale depois que o arquivo começou a rodar, então não esconde um Parse error do próprio arquivo.
Missão: configure o programa como em produção: não mostrar erros, registrar no arquivo erros.log ao lado do script. Acrescente uma mensagem sua no registro, Estoque do bolo acabando, com error_log. No fim, o programa já mostra o conteúdo do registro (só para você conferir).
<?php // configure aqui echo "Página normal\n"; echo "Olá, $cliente\n"; echo "--- conteúdo do erros.log:\n"; echo @file_get_contents(__DIR__ . '/erros.log');
<?php
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/erros.log');
error_log('Estoque do bolo acabando');
echo "Página normal\n";
echo "Olá, $cliente\n";
echo "--- conteúdo do erros.log:\n";
echo @file_get_contents(__DIR__ . '/erros.log');
Esconder um aviso com @ (e por que evitar)
O operador @ na frente de uma expressão silencia os avisos daquela expressão. O erro ainda pode ser consultado com error_get_last():
<?php
$conteudo = @file_get_contents('nao-existe.txt');
var_dump($conteudo);
print_r(error_get_last());
$ php arroba.php
bool(false)
Array
(
[type] => 2 « 2 = E_WARNING
[message] => file_get_contents(nao-existe.txt): Failed to open stream: No such file or directory
[file] => /home/ana/erros/arroba.php
[line] => 2
)
O @ só é aceitável quando você trata a falha logo em seguida (como no if ($conteudo === false)). Espalhar @ para "limpar a tela" é o caminho mais rápido para um bug impossível de achar. Prefira perguntar antes (file_exists) ou usar exceções.
Os seus próprios avisos: trigger_error
$ php user.php « trigger_error('Estoque baixo', E_USER_WARNING); echo "continua\n";
Warning: Estoque baixo in /home/ana/erros/user.php on line 2
continua
Hoje, para erros do seu programa, o normal é lançar uma exceção própria, e trigger_error ficou para casos raros (usar E_USER_ERROR com ele está obsoleto desde o PHP 8.4).
No PHP 8, erros viraram exceções
Nas versões antigas, muitos problemas graves eram "fatais de verdade": o programa morria e não havia o que fazer. No PHP 7 e, principalmente, no PHP 8, eles passaram a ser exceções da família Error. A diferença prática é enorme: uma exceção pode ser capturada com try/catch, e o programa decide o que fazer (mostrar uma mensagem amigável, registrar, tentar outra coisa).
<?php
$testes = [
fn() => strlen([]),
fn() => str_repeat('x', -1),
fn() => 10 % 0,
fn() => intdiv(10, 0),
fn() => 10 / 0,
fn() => calcular(),
fn() => match (3) { 1 => 'um', 2 => 'dois' },
fn() => (function (int $a, int $b) { return $a + $b; })(1),
fn() => null->nome(),
];
foreach ($testes as $t) {
try {
$t();
} catch (Throwable $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
}
Não se preocupe com o fn() => (são arrow functions, usadas aqui só para guardar cada teste sem executá-lo) nem com o try/catch (a próxima aula). Olhe a saída: cada problema virou uma exceção com nome e mensagem.
$ php excecoes.php
TypeError: strlen(): Argument #1 ($string) must be of type string, array given
ValueError: str_repeat(): Argument #2 ($times) must be greater than or equal to 0
DivisionByZeroError: Modulo by zero
DivisionByZeroError: Division by zero
DivisionByZeroError: Division by zero
Error: Call to undefined function calcular()
UnhandledMatchError: Unhandled match case 3
ArgumentCountError: Too few arguments to function {closure:{closure:/home/ana/erros/excecoes.php:10}:10}(), 1 passed in /home/ana/erros/excecoes.php on line 10 and exactly 2 expected
Error: Call to a member function nome() on null
A família Error
| Classe | Filha de | Quando acontece | Exemplo real |
|---|---|---|---|
Throwable | (interface, a raiz) | Tudo o que pode ser lançado: Error e Exception | |
Error | Throwable | Erros internos do PHP | Call to undefined function calcular() |
TypeError | Error | Valor do tipo errado num parâmetro ou retorno | strlen(): Argument #1 ($string) must be of type string, array given |
ArgumentCountError | TypeError | Faltam argumentos | Too few arguments to function …, 1 passed … and exactly 2 expected |
ValueError | Error | Tipo certo, valor impossível | str_repeat(): Argument #2 ($times) must be greater than or equal to 0 |
ArithmeticError | Error | Erro de conta | intdiv(PHP_INT_MIN, -1) |
DivisionByZeroError | ArithmeticError | Divisão ou resto por zero | Division by zero, Modulo by zero |
UnhandledMatchError | Error | match sem braço para o valor | Unhandled match case 3 |
CompileError / ParseError | Error | Erro de sintaxe em código carregado (include, eval) | syntax error, unexpected token … |
Exception | Throwable | A outra família: problemas "do programa", que você lança e captura (JsonException, DateMalformedStringException, as suas) | veja Exceções |
Quando ninguém captura, a exceção vira o Fatal error: Uncaught … que você já conhece:
$ php div.php « echo 10 / 0;
Fatal error: Uncaught DivisionByZeroError: Division by zero in /home/ana/erros/div.php:2
Stack trace:
#0 {main}
thrown in /home/ana/erros/div.php on line 2
$ php typeerror.php « function dobro(int $n): int { … } echo dobro("abc");
Fatal error: Uncaught TypeError: dobro(): Argument #1 ($n) must be of type int, string given, called in /home/ana/erros/typeerror.php on line 6 and defined in /home/ana/erros/typeerror.php:2
Stack trace:
#0 /home/ana/erros/typeerror.php(6): dobro('abc')
#1 {main}
thrown in /home/ana/erros/typeerror.php on line 2
Repare que 10 / 0 é uma exceção (para o programa), mas um arquivo que não abre é só um Warning (segue com false). Os Warning não são exceções e não são pegos pelo catch. Se você quiser tratá-los do mesmo jeito, existe uma receita com set_error_handler que transforma todo aviso numa ErrorException:
<?php
set_error_handler(function (int $nivel, string $msg, string $arquivo, int $linha) {
throw new ErrorException($msg, 0, $nivel, $arquivo, $linha);
});
try {
$x = file_get_contents('nao-existe.txt');
} catch (ErrorException $e) {
echo "Peguei: ", $e->getMessage(), "\n";
}
$ php handler.php Peguei: file_get_contents(nao-existe.txt): Failed to open stream: No such file or directory
Muitos frameworks fazem exatamente isso por você.
Quando dá errado
O que significa: houve um Fatal error ou Parse error com display_errors desligado. O PHP parou e não mostrou nada.
Como consertar: olhe o registro de erros (o error_log configurado, ou o do servidor, como /var/log/apache2/error.log). No seu computador, ligue display_errors. Rode php -l arquivo.php para achar erros de sintaxe.
Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in /home/ana/erros/deprecated.php on line 3
O que significa: o código usa coisas que a versão nova considera obsoletas. Hoje funciona.
Como consertar: corrija um por um (aqui, strlen($texto ?? '')). Não desligue E_DEPRECATED no desenvolvimento: é o aviso antecipado do que vai quebrar na próxima versão.
O que significa: o programa tentou usar mais memória que o memory_limit. Quase sempre é um laço sem fim enchendo um array, ou um arquivo enorme lido inteiro.
Como consertar: procure o laço; leia arquivos grandes em partes (fgets). Aumentar o memory_limit só resolve se o programa realmente precisa de mais memória.
Referência
| Item | O que faz |
|---|---|
error_reporting(E_ALL) | Quais níveis são relatados (sem argumento: devolve o valor atual). |
ini_set('display_errors', '0'|'1') | Mostrar ou não os erros na saída. |
ini_set('log_errors', '1') | Registrar os erros. |
ini_set('error_log', $arquivo) | Onde registrar. |
error_log($mensagem) | Escreve uma mensagem sua no registro. |
@expressão | Silencia os avisos daquela expressão. |
error_get_last() | O último erro (array com type, message, file, line). |
trigger_error($msg, E_USER_WARNING) | Dispara um aviso seu. |
set_error_handler($funcao) | Uma função sua recebe os avisos (por exemplo, para virar ErrorException). |
php -l arquivo.php | Confere a sintaxe sem executar. |
php -d config=valor | Muda uma configuração só nesta execução. |
php --ini, php -i | Qual php.ini e quais configurações estão valendo. |
Exercícios
1. Todos os níveis
Complete para relatar todos os erros:
2. Produção
Complete para esconder os erros do visitante:
3. Anotar no registro
Complete para escrever uma mensagem sua no registro de erros:
4. Qual destas mensagens não para o programa?
5. Num site no ar (produção), qual é a configuração recomendada?
6. No PHP 8, intdiv(10, 0) lança qual exceção?
Missão final: cada função de teste provoca um erro diferente. Hoje o programa morre no primeiro. Envolva a chamada de cada teste num try { … } catch (Throwable $e) { … } que mostre get_class($e) . ': ' . $e->getMessage(), para os três aparecerem e o programa chegar ao Fim do teste.
<?php
function teste1() { return 100 / 0; }
function teste2() { return strlen([1, 2]); }
function teste3() { return str_repeat('-', -5); }
foreach (['teste1', 'teste2', 'teste3'] as $teste) {
$teste();
}
echo "Fim do teste\n";
<?php
function teste1() { return 100 / 0; }
function teste2() { return strlen([1, 2]); }
function teste3() { return str_repeat('-', -5); }
foreach (['teste1', 'teste2', 'teste3'] as $teste) {
try {
$teste();
} catch (Throwable $e) {
echo get_class($e) . ': ' . $e->getMessage() . "\n";
}
}
echo "Fim do teste\n";
Resumo
Deprecated,NoticeeWarningavisam e o programa continua;Fatal errorpara;Parse errorimpede o programa de começar.- Todo aviso é um bug: conserte em vez de esconder.
error_reporting(E_ALL)relata tudo;display_errorsdecide se aparece na tela;log_errors+error_logguardam num arquivo.- Desenvolvimento: mostrar tudo. Produção:
display_errors=Offelog_errors=On. - No PHP 8, muitos erros são exceções da família
Error(TypeError,ValueError,DivisionByZeroError…), filhas deThrowable, e podem ser capturadas.
Próxima aula: Exceções (try e catch).