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.

Você vai aprender
  • Os níveis: Warning, Notice, Deprecated, Fatal error e Parse 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:

MensagemNível (constante)No carroO programa…
DeprecatedE_DEPRECATED"Isto vai deixar de funcionar numa versão futura"continua
NoticeE_NOTICE"Reparei numa coisa estranha"continua
WarningE_WARNINGLuz amarela: algo deu erradocontinua
Fatal errorE_ERROR (ou uma exceção não capturada)Luz vermelhapara
Parse errorE_PARSEO carro nem liganem 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 null para uma função do PHP que espera texto está obsoleto desde o PHP 8.1. Use strlen($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
Em desenvolvimento, sempre 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";
LinhaO 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.

No terminal: 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çãoDesenvolvimento (seu computador)Produção (o site no ar)
error_reportingE_ALLE_ALL (o modelo de produção tira E_DEPRECATED)
display_errorsOn: ver tudo na horaOff: o visitante nunca vê
display_startup_errorsOnOff
log_errorsOnOn: 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

ClasseFilha deQuando aconteceExemplo real
Throwable(interface, a raiz)Tudo o que pode ser lançado: Error e Exception
ErrorThrowableErros internos do PHPCall to undefined function calcular()
TypeErrorErrorValor do tipo errado num parâmetro ou retornostrlen(): Argument #1 ($string) must be of type string, array given
ArgumentCountErrorTypeErrorFaltam argumentosToo few arguments to function …, 1 passed … and exactly 2 expected
ValueErrorErrorTipo certo, valor impossívelstr_repeat(): Argument #2 ($times) must be greater than or equal to 0
ArithmeticErrorErrorErro de containtdiv(PHP_INT_MIN, -1)
DivisionByZeroErrorArithmeticErrorDivisão ou resto por zeroDivision by zero, Modulo by zero
UnhandledMatchErrorErrormatch sem braço para o valorUnhandled match case 3
CompileError / ParseErrorErrorErro de sintaxe em código carregado (include, eval)syntax error, unexpected token …
ExceptionThrowableA 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
Warning × exceção

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

A página fica em branco (a "tela branca")

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" aos montes depois de atualizar o PHP
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.

"Allowed memory size of … bytes exhausted"

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

ItemO 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ãoSilencia 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.phpConfere a sintaxe sem executar.
php -d config=valorMuda uma configuração só nesta execução.
php --ini, php -iQual php.ini e quais configurações estão valendo.

Exercícios

1. Todos os níveis

Complete para relatar todos os erros:

error_reporting();

2. Produção

Complete para esconder os erros do visitante:

ini_set('', '0');

3. Anotar no registro

Complete para escrever uma mensagem sua no registro de erros:

('Pagamento recusado no pedido 42');

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, Notice e Warning avisam e o programa continua; Fatal error para; Parse error impede o programa de começar.
  • Todo aviso é um bug: conserte em vez de esconder.
  • error_reporting(E_ALL) relata tudo; display_errors decide se aparece na tela; log_errors + error_log guardam num arquivo.
  • Desenvolvimento: mostrar tudo. Produção: display_errors=Off e log_errors=On.
  • No PHP 8, muitos erros são exceções da família Error (TypeError, ValueError, DivisionByZeroError…), filhas de Throwable, e podem ser capturadas.

Próxima aula: Exceções (try e catch).