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.
- Um método para caçar bugs, em vez de mudar coisas ao acaso.
- Inspecionar valores com
var_dump,print_revar_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:
- Reproduza o problema: descubra a entrada exata que causa o resultado errado.
- Diga o que esperava: "o total deveria ser 95".
- 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. - Conserte a causa naquele ponto, e não o sintoma no final.
- Confirme rodando de novo, e remova os
var_dumpde 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
| Ferramenta | Mostra o tipo? | Bom para |
|---|---|---|
var_dump($x) | Sim, sempre | Investigar bugs: diferencia "3" de 3, false de null de "". Aceita vários valores: var_dump($a, $b). |
print_r($x) | Não | Olhar 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 menos | Uma linha só, ótimo para registros (error_log). |
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
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…).
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
| Sistema | Como |
|---|---|
| Ubuntu / Debian | sudo apt install php-xdebug |
| Fedora | sudo dnf install php-pecl-xdebug3 |
| macOS (Homebrew) | pecl install xdebug |
| Windows | Baixe 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)
- Instale a extensão PHP Debug (da Xdebug).
- 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).
- Clique à esquerda do número de uma linha para marcar um ponto de parada (breakpoint, a bolinha vermelha).
- Clique em "Iniciar depuração" (F5) e rode o programa: no terminal (
php bug.php) ou abrindo a página no navegador com ophp -S. - O programa pausa na linha marcada. O painel Variáveis mostra todas as variáveis, com tipos (como um
var_dumpde tudo). - 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.
| Ferramenta | Vantagem | Desvantagem |
|---|---|---|
var_dump + die | Funciona em qualquer lugar, sem instalar nada | Precisa editar o código; fácil esquecer no código |
error_log | Funciona até em produção, sem o visitante ver | Você precisa saber o que registrar |
| Xdebug | Vê tudo, linha a linha, sem mexer no código | Precisa 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:
| Ruim | Bom | Por 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".
"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).
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).
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
| Item | O 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), die | Termina 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:
2. Array no registro
Complete para escrever o array $pedido no registro de erros, como texto:
3. Olhar e parar
Complete a técnica mais usada para inspecionar e interromper:
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_dumpmostra valor e tipo (a melhor lupa);print_rmostra a estrutura;var_exportdevolve código PHP. Na web, envolva em<pre>.exit/dieparam o programa ali; não esqueça nenhum no código publicado.error_logregistra sem o visitante ver (nophp -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.