PHP Depurar

Os piores bugs não mostram mensagem nenhuma: o programa roda até o fim e o resultado está errado. O total do carrinho dá R$ 90 quando deveria dar R$ 95. Para achar o problema, você precisa olhar dentro do programa enquanto ele roda: quanto vale esta variável aqui? Por qual caminho o programa chegou nesta linha? Isso se chama depurar (em inglês, debug, "tirar os insetos"). Nesta aula você vai aprender as ferramentas, das mais simples às mais poderosas, e um método para usá-las.

Você vai aprender
  • Um método para caçar bugs, em vez de mudar coisas ao acaso.
  • Inspecionar valores com var_dump, print_r e var_export, e quando usar cada um.
  • Parar o programa num ponto com exit/die.
  • Registrar sem atrapalhar a página, com error_log.
  • Ver o caminho das chamadas com debug_print_backtrace.
  • Depurar passo a passo com o Xdebug e um editor.
  • Escrever mensagens de erro que ajudam quem vai depurar depois.

Você já conhece var_dump e print_r da aula var_dump e print_r; aqui eles viram ferramentas de investigação. E já sabe ler as mensagens do PHP (Tipos de Erro).


A ideia: o detetive

Depurar é trabalho de detetive. O detetive não sai prendendo suspeitos ao acaso: ele junta pistas. No programa, as pistas são os valores das variáveis em cada ponto. O método:

  1. Reproduza o problema: descubra a entrada exata que causa o resultado errado.
  2. Diga o que esperava: "o total deveria ser 95".
  3. Olhe os valores no meio do caminho (com var_dump), do começo para o fim, até achar o primeiro ponto em que o valor real é diferente do esperado.
  4. Conserte a causa naquele ponto, e não o sintoma no final.
  5. Confirme rodando de novo, e remova os var_dump de investigação.

Um bug de verdade

O carrinho da lojinha tem 2 bolos de R$ 35,00 e 10 brigadeiros de R$ 2,50. O total deveria ser 70 + 25 = R$ 95,00:

<?php
$carrinho = [
    ['produto' => 'Bolo de cenoura', 'preco' => '35,00', 'qtd' => 2],
    ['produto' => 'Brigadeiro', 'preco' => '2,50', 'qtd' => 10],
];
$total = 0;
foreach ($carrinho as $item) {
    $total += $item['preco'] * $item['qtd'];
}
echo "Total: R$ " . number_format($total, 2, ',', '.') . "\n";
$ cd dep
$ php bug.php

Warning: A non-numeric value encountered in /home/ana/dep/bug.php on line 8

Warning: A non-numeric value encountered in /home/ana/dep/bug.php on line 8
Total: R$ 90,00                « errado: esperava 95,00

Há uma pista (o Warning da linha 8: "valor não numérico encontrado"), mas ela não diz qual valor. Vamos olhar com var_dump, colocado logo antes da conta suspeita:

foreach ($carrinho as $item) {
    var_dump($item['preco'], $item['preco'] * $item['qtd']);
    $total += $item['preco'] * $item['qtd'];
}
$ php bug2.php

Warning: A non-numeric value encountered in /home/ana/dep/bug2.php on line 8
string(5) "35,00"             « o preço é TEXTO, com vírgula
int(70)                        « 35 × 2 = 70: o primeiro deu certo por sorte

Warning: A non-numeric value encountered in /home/ana/dep/bug2.php on line 9

Warning: A non-numeric value encountered in /home/ana/dep/bug2.php on line 8
string(4) "2,50"
int(20)                        « 2 × 10 = 20: o ",50" foi jogado fora!

Warning: A non-numeric value encountered in /home/ana/dep/bug2.php on line 9
Total: R$ 90,00

Achamos: o preço é a string "2,50", e o PHP, ao fazer a conta, lê só o começo (2) e ignora o resto. É o mesmo problema da aula de CSV: vírgula decimal. O conserto vai na causa (converter o preço), e os var_dump saem:

foreach ($carrinho as $item) {
    $preco = (float) str_replace(',', '.', $item['preco']);
    $total += $preco * $item['qtd'];
}
$ php bug3.php
Total: R$ 95,00

Missão: refaça a investigação. Rode, coloque um var_dump($item['preco']) dentro do laço para ver o problema com seus próprios olhos, conserte a conversão do preço e apague o var_dump. O resultado deve ser Total: R$ 95,00, sem avisos.

<?php
$carrinho = [
    ['produto' => 'Bolo de cenoura', 'preco' => '35,00', 'qtd' => 2],
    ['produto' => 'Brigadeiro', 'preco' => '2,50', 'qtd' => 10],
];
$total = 0;
foreach ($carrinho as $item) {
    $total += $item['preco'] * $item['qtd'];
}
echo "Total: R$ " . number_format($total, 2, ',', '.') . "\n";
<?php
$carrinho = [
    ['produto' => 'Bolo de cenoura', 'preco' => '35,00', 'qtd' => 2],
    ['produto' => 'Brigadeiro', 'preco' => '2,50', 'qtd' => 10],
];
$total = 0;
foreach ($carrinho as $item) {
    $preco = (float) str_replace(',', '.', $item['preco']);
    $total += $preco * $item['qtd'];
}
echo "Total: R$ " . number_format($total, 2, ',', '.') . "\n";

As lupas: var_dump, print_r, var_export

O echo não serve para investigar: ele não mostra arrays, e esconde tipos. Veja o mesmo pedido em cada ferramenta:

<?php
$pedido = ['cliente' => 'Bia', 'itens' => 2, 'total' => 82.0, 'pago' => false, 'cupom' => null];
echo $pedido, "\n";
echo "--- print_r:\n";
print_r($pedido);
echo "--- var_dump:\n";
var_dump($pedido);
echo "--- var_export:\n";
var_export($pedido);
echo "\n";
$ php comparar.php

Warning: Array to string conversion in /home/ana/dep/comparar.php on line 3
Array                       « o echo só diz "Array"
--- print_r:
Array
(
    [cliente] => Bia
    [itens] => 2
    [total] => 82           « 82 ou 82.0? não dá para saber
    [pago] =>               « false aparece vazio
    [cupom] =>              « null também: impossível distinguir
)
--- var_dump:
array(5) {
  ["cliente"]=>
  string(3) "Bia"           « tipo e tamanho
  ["itens"]=>
  int(2)
  ["total"]=>
  float(82)
  ["pago"]=>
  bool(false)               « tudo explícito
  ["cupom"]=>
  NULL
}
--- var_export:
array (
  'cliente' => 'Bia',
  'itens' => 2,
  'total' => 82.0,
  'pago' => false,
  'cupom' => NULL,
)                           « é código PHP válido: dá para copiar e colar
FerramentaMostra o tipo?Bom para
var_dump($x)Sim, sempreInvestigar bugs: diferencia "3" de 3, false de null de "". Aceita vários valores: var_dump($a, $b).
print_r($x)NãoOlhar rapidamente a estrutura de um array grande. print_r($x, true) devolve o texto em vez de mostrar.
var_export($x)Sim (como código)Copiar um valor para um teste ou arquivo de configuração. Também aceita true para devolver o texto.
json_encode($x)Mais ou menosUma linha só, ótimo para registros (error_log).
Comparações que enganam

Muitos bugs são de tipo. Um valor que veio de um formulário é sempre texto:

$ php tipos.php      « $qtd = $_GET['qtd'] ?? '3';  var_dump($qtd === 3);  var_dump($qtd);
bool(false)          « por que não é 3?
string(1) "3"        « porque é o texto "3", e === compara o tipo também

Na dúvida, var_dump as duas pontas da comparação (veja Comparação).

Numa página web: use <pre>

No navegador, as quebras de linha do var_dump somem (o HTML junta os espaços), e o resultado vira uma linha só, ilegível. Envolva a saída em <pre> ("pré-formatado"), que mantém as quebras:

Missão: veja na aba Resultado como o var_dump fica embolado. Coloque um echo '<pre>'; antes dele e um echo '</pre>'; depois, e compare.

<?php
$pedido = ['cliente' => 'Bia', 'itens' => ['bolo', 'brigadeiro']];
var_dump($pedido);
?>
<p>Obrigado pela compra!</p>
<?php
$pedido = ['cliente' => 'Bia', 'itens' => ['bolo', 'brigadeiro']];
echo '<pre>';
var_dump($pedido);
echo '</pre>';
?>
<p>Obrigado pela compra!</p>

Parar ali: exit e die

Às vezes você quer ver um valor e parar, sem que o resto do programa rode (e encha a tela, ou grave algo no arquivo). exit ("sair") termina o programa na hora. die ("morrer") é exatamente a mesma coisa, com outro nome.

<?php
$config = ['loja' => 'Lojinha da Ana', 'frete' => 12.5];
var_dump($config);
exit;
echo "Isto não aparece\n";
$ php exit.php; echo "código: $?"
array(2) {
  ["loja"]=>
  string(14) "Lojinha da Ana"
  ["frete"]=>
  float(12.5)
}
código: 0

O famoso "var_dump($x); die;" é a técnica de depuração mais usada do mundo PHP. Ela também aceita um texto (mostrado antes de sair) ou um número (o código de saída, para scripts de terminal):

$ php die.php; echo "código: $?"      « exit("Erro: $arquivo não encontrado\n");
Erro: precos.csv não encontrado
código: 0                              « com texto, o código é 0
$ php exitcode.php; echo "código: $?"  « echo "Algo deu errado\n"; exit(3);
Algo deu errado
código: 3                              « com número, o programa sai com esse código
Não esqueça um die no código

Um var_dump ou exit esquecido num site no ar mostra dados internos a todos os visitantes, ou interrompe a página no meio. Antes de publicar, procure por eles: grep -rn "var_dump\|print_r\|die;" . (veja o grep no Guia Bash). Para falhas de verdade, use exceções, não die.


Registrar sem atrapalhar: error_log

Num site, um var_dump aparece para o visitante e pode quebrar o layout (ou um JSON de API). A alternativa é escrever no registro com a função error_log (você a viu em Tipos de Erro). Para registrar um array, transforme-o em texto antes, com print_r($x, true) ou json_encode:

<?php
$pedido = ['cliente' => 'Bia', 'total' => 82.0];
error_log('Pedido recebido: ' . print_r($pedido, true));
error_log('Pedido (JSON): ' . json_encode($pedido));
echo "Obrigado pela compra!\n";
$ php errorlog.php
Pedido recebido: Array          « sem arquivo de log configurado, no terminal vai para a saída de erros
(
    [cliente] => Bia
    [total] => 82
)

Pedido (JSON): {"cliente":"Bia","total":82}
Obrigado pela compra!
$ php errorlog.php 2>/dev/null      « jogando fora a saída de erros, sobra só a "página"
Obrigado pela compra!

Com o servidor embutido (php -S), o error_log aparece no terminal do servidor, e não na página. A Ana pediu esta página com o curl:

<?php
$pedido = ['cliente' => 'Bia', 'total' => 82.0];
error_log('Pedido recebido: ' . json_encode($pedido));
var_dump($pedido);
?>
<p>Obrigado pela compra!</p>
$ curl -s localhost:8000/
array(2) {                       « o var_dump foi para a página
  ["cliente"]=>
  string(3) "Bia"
  ["total"]=>
  float(82)
}
<p>Obrigado pela compra!</p>

E no terminal onde o php -S estava rodando:

[Fri Oct 16 21:10:42 2026] PHP 8.5.8 Development Server (http://localhost:8000) started
[Fri Oct 16 21:10:43 2026] 127.0.0.1:33700 Accepted
[Fri Oct 16 21:10:43 2026] Pedido recebido: {"cliente":"Bia","total":82}          « o error_log
[Fri Oct 16 21:10:43 2026] 127.0.0.1:33700 [200]: GET /
[Fri Oct 16 21:10:43 2026] 127.0.0.1:33700 Closing

Num servidor de verdade (Apache, Nginx), o registro vai para o arquivo configurado em error_log no php.ini, ou para o registro do servidor web. Para acompanhar em tempo real: tail -f /caminho/do/erros.log.


Por onde o programa passou: debug_print_backtrace

Às vezes o valor errado chega a uma função e você não sabe quem a chamou. debug_print_backtrace() mostra o rastro de chamadas até aquela linha, igual ao Stack trace das exceções:

<?php
function calcular_frete(string $cep): float
{
    debug_print_backtrace();
    return 12.5;
}
function fechar_pedido(array $pedido): float
{
    return $pedido['total'] + calcular_frete($pedido['cep']);
}
echo fechar_pedido(['total' => 82.0, 'cep' => '13010-000']), "\n";
$ php backtrace.php
#0 /home/ana/dep/backtrace.php(9): calcular_frete('13010-000')       « quem chamou calcular_frete, e com o quê
#1 /home/ana/dep/backtrace.php(11): fechar_pedido(Array)            « quem chamou fechar_pedido
94.5

Leia de cima para baixo: "estou em calcular_frete, chamada na linha 9 com '13010-000', que está em fechar_pedido, chamada na linha 11". A versão debug_backtrace() devolve o mesmo como array, para você registrar com error_log.


O depurador: Xdebug

Com var_dump, você precisa adivinhar onde olhar, editar o código, rodar, apagar… Um depurador faz isso sem mexer no código: você marca uma linha, o programa pausa ali, e você vê todas as variáveis, avança uma linha por vez e entra nas funções. Para PHP, o depurador é o Xdebug, uma extensão que conversa com o seu editor (VS Code, PhpStorm…).

Esta seção é ilustrativa

O Xdebug não existe no editor do guia nem no laboratório da Ana (que usa um PHP compilado sem ele). Os passos abaixo são os de uma instalação comum; as telas dependem do editor que você usar (veja Editores e IDEs).

1. Instalar

SistemaComo
Ubuntu / Debiansudo apt install php-xdebug
Fedorasudo dnf install php-pecl-xdebug3
macOS (Homebrew)pecl install xdebug
WindowsBaixe a DLL certa para a sua versão em xdebug.org/wizard (o site analisa o seu php -i e diz qual arquivo baixar) e acrescente zend_extension=xdebug ao php.ini

Confira com php -v: aparece uma linha with Xdebug v3….

2. Configurar

No php.ini (ou no arquivo do Xdebug em conf.d):

zend_extension=xdebug
xdebug.mode=debug            ; liga o modo de depuração passo a passo
xdebug.start_with_request=yes ; conecta ao editor em toda execução

O xdebug.mode também aceita develop (um var_dump mais bonito e mensagens de erro com mais detalhes) e coverage (para testes; veja PHPUnit). Vários modos se juntam com vírgula: develop,debug.

3. Depurar no editor (VS Code)

  1. Instale a extensão PHP Debug (da Xdebug).
  2. Abra a pasta do projeto e, na aba Executar e depurar, crie a configuração "Listen for Xdebug" (o editor fica "escutando" a porta 9003, a padrão do Xdebug 3).
  3. Clique à esquerda do número de uma linha para marcar um ponto de parada (breakpoint, a bolinha vermelha).
  4. Clique em "Iniciar depuração" (F5) e rode o programa: no terminal (php bug.php) ou abrindo a página no navegador com o php -S.
  5. O programa pausa na linha marcada. O painel Variáveis mostra todas as variáveis, com tipos (como um var_dump de tudo).
  6. Avance com os botões: Step Over (F10, próxima linha), Step Into (F11, entra na função chamada), Step Out (Shift+F11, termina a função atual) e Continue (F5, até o próximo ponto de parada).

No bug do carrinho, você marcaria a linha do $total +=, veria $item['preco'] = "2,50" no painel e entenderia o problema sem escrever nenhum var_dump.

FerramentaVantagemDesvantagem
var_dump + dieFunciona em qualquer lugar, sem instalar nadaPrecisa editar o código; fácil esquecer no código
error_logFunciona até em produção, sem o visitante verVocê precisa saber o que registrar
XdebugVê tudo, linha a linha, sem mexer no códigoPrecisa instalar e configurar; deixa o PHP mais lento (não use em produção)

Boas mensagens de erro

A depuração começa antes do bug: quando você escreve uma mensagem de erro (numa exceção, num error_log), está escrevendo para a pessoa que vai depurar depois, que pode ser você mesmo daqui a seis meses. Compare:

RuimBomPor quê
"Erro!""Estoque insuficiente de brigadeiro: pediu 50, há 30"Diz o que e com quais valores.
"Arquivo inválido""Linha 7 de alunos.csv tem 2 campos, esperava 3"Diz onde procurar.
"Falha no pagamento""Pagamento do pedido 1042 recusado: cartão vencido (código 51)"Identifica o caso e o motivo.
"Data errada""Data de nascimento '31/02/1997' não existe (use dd/mm/aaaa)"Mostra o valor recebido e o que era esperado.

E duas regras de segurança: a mensagem técnica vai para o registro; o visitante recebe uma mensagem amigável ("Não foi possível concluir o pedido. Tente de novo."). E nunca registre senhas, números de cartão ou dados pessoais desnecessários.


Bugs clássicos (e como o var_dump os revela)

= no lugar de ==
<?php
$estoque = 0;
if ($estoque = 5) {
    echo "Estoque: $estoque (era para ser 0!)\n";
}
$ php igual.php
Estoque: 5 (era para ser 0!)

O que acontece: = atribui 5 à variável (e o valor 5 conta como verdadeiro); não compara. Sem aviso nenhum.

Como consertar: if ($estoque === 5). Um var_dump($estoque) antes e depois do if mostra a variável mudando "sozinha".

Texto que parece número

"2,50", "3" vindo de formulário, " 10" com espaço: o echo mostra igual a um número, o var_dump mostra string(…).

Como consertar: converta e valide na entrada (Validar Dados).

"Array to string conversion"
Warning: Array to string conversion in /home/ana/dep/comparar.php on line 3
Array

O que significa: você deu echo num array.

Como consertar: para investigar, var_dump ou print_r; para mostrar ao usuário, percorra com foreach ou use implode(', ', $lista).

"Funciona no meu computador"

O que significa: algo é diferente no outro ambiente: a versão do PHP, uma extensão (intl, pdo_mysql), o fuso, as permissões, o php.ini.

Como consertar: compare php -v, php -m (extensões) e php -i dos dois lados; num site, uma página temporária com phpinfo(); (apague depois: ela revela muito sobre o servidor).


Referência

ItemO que faz
var_dump($a, $b, …)Mostra valores com tipos.
print_r($x) / print_r($x, true)Mostra a estrutura / devolve como texto.
var_export($x) / var_export($x, true)Mostra / devolve como código PHP.
exit;, exit("msg"), exit(3), dieTermina o programa (com texto ou código de saída).
error_log($texto)Escreve no registro de erros.
debug_print_backtrace() / debug_backtrace()Mostra / devolve o rastro de chamadas.
get_debug_type($x)O tipo do valor, como texto ("string", "int", nome da classe).
phpinfo()Página com toda a configuração do PHP (não deixe no ar).
Xdebug (xdebug.mode=debug)Depuração passo a passo no editor.

Exercícios

1. Ver o tipo

Complete para ver o valor e o tipo de $preco:

($preco);

2. Array no registro

Complete para escrever o array $pedido no registro de erros, como texto:

error_log(print_r($pedido, ));

3. Olhar e parar

Complete a técnica mais usada para inspecionar e interromper:

var_dump($dados);

4. Você precisa saber se $x é false, null ou "". Qual ferramenta mostra a diferença?

5. Num site no ar, como registrar um valor para investigar sem o visitante ver?

6. No Xdebug, o que faz o Step Into?

Missão final (detetive): os cupons promo10 e PROMO20, digitados pelos clientes, deveriam dar 10% e 20% de desconto em R$ 100, mas nenhum funciona. Use var_dump dentro da função para descobrir por quê (olhe o cupom recebido e as chaves do array), conserte a causa e remova os var_dump. Resultado esperado: R$ 90,00, R$ 80,00 e, para o cupom inexistente, Total sem desconto: R$ 100,00.

<?php
$cupons = ['PROMO10' => 10, 'PROMO20' => 20];

function total_com_cupom(array $cupons, float $total, string $cupom): string
{
    if (isset($cupons[$cupom])) {
        $final = $total * (100 - $cupons[$cupom]) / 100;
        return "Total com desconto: R$ " . number_format($final, 2, ',', '.');
    }
    return "Total sem desconto: R$ " . number_format($total, 2, ',', '.');
}

echo total_com_cupom($cupons, 100, 'promo10'), "\n";
echo total_com_cupom($cupons, 100, ' PROMO20 '), "\n";
echo total_com_cupom($cupons, 100, 'NATAL'), "\n";
<?php
$cupons = ['PROMO10' => 10, 'PROMO20' => 20];

function total_com_cupom(array $cupons, float $total, string $cupom): string
{
    $cupom = strtoupper(trim($cupom));
    if (isset($cupons[$cupom])) {
        $final = $total * (100 - $cupons[$cupom]) / 100;
        return "Total com desconto: R$ " . number_format($final, 2, ',', '.');
    }
    return "Total sem desconto: R$ " . number_format($total, 2, ',', '.');
}

echo total_com_cupom($cupons, 100, 'promo10'), "\n";
echo total_com_cupom($cupons, 100, ' PROMO20 '), "\n";
echo total_com_cupom($cupons, 100, 'NATAL'), "\n";

Se você usou var_dump($cupom), viu string(7) "promo10" (minúsculas) e string(9) " PROMO20 " (9 caracteres para uma palavra de 7: há espaços). O echo não teria mostrado os espaços.


Resumo

  • Depurar é investigar: reproduza, diga o que esperava, olhe os valores no caminho até achar onde divergem, conserte a causa.
  • var_dump mostra valor e tipo (a melhor lupa); print_r mostra a estrutura; var_export devolve código PHP. Na web, envolva em <pre>.
  • exit/die param o programa ali; não esqueça nenhum no código publicado.
  • error_log registra sem o visitante ver (no php -S, aparece no terminal do servidor).
  • debug_print_backtrace() mostra quem chamou quem.
  • O Xdebug pausa o programa num ponto de parada e mostra tudo, linha a linha, no editor.
  • Mensagens de erro boas dizem o que, onde e com quais valores.

Próxima aula: Banco de Dados: o Básico.