Debugando o Magento 2 com Xdebug 3 e PhpStorm
O Magento é difícil de acompanhar só lendo código. Uma chamada a um repository passa por um interceptor, vários plugins, um resource model e talvez um observer antes de fazer qualquer coisa. Logar com var_dump pode te levar lá, uma hora. Um step debugger te leva em minutos, porque você vê a call stack real e os valores reais em cada passo.
Este artigo cobre o Xdebug 3 com PhpStorm para o Magento 2.4.8 num ambiente Docker, incluindo as armadilhas que fazem breakpoints serem ignorados em silêncio.
Como o Xdebug conecta
Com o Xdebug 3, o processo PHP conecta para fora, na sua IDE, e não o contrário. Quando uma requisição começa com um trigger de debug, o Xdebug abre uma conexão com xdebug.client_host na porta xdebug.client_port, onde o PhpStorm está escutando. O PhpStorm então mapeia os caminhos de arquivo informados pelo PHP para arquivos do seu projeto e para nos breakpoints.
Então três coisas precisam bater: o PHP precisa saber onde está a IDE, a IDE precisa escutar na mesma porta, e os caminhos dentro do container precisam estar mapeados para os caminhos da sua máquina.
Configuração do PHP
Uma configuração mínima para desenvolvimento local:
zend_extension=xdebug
xdebug.mode=debug
xdebug.start_with_request=trigger
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.idekey=PHPSTORM
xdebug.log=/tmp/xdebug.log
xdebug.log_level=3
start_with_request=triggersó inicia uma sessão de debug quando a requisição traz um trigger, como o cookieXDEBUG_SESSION. As outras requisições rodam normalmente, o que importa porque o Magento com uma sessão de debug ativa é lento.host.docker.internalresolve para a máquina host de dentro de um container no Docker Desktop. No Linux, talvez você precise adicioná-lo como extra host ou usar o IP da bridge.- O log é o primeiro lugar para olhar quando nada acontece. Remova ou reduza o nível quando tudo funcionar, já que ele cresce rápido.
O Xdebug deixa o PHP mais lento mesmo sem sessão ativa. Um bom setup mantém ele num pool PHP-FPM ou container separado e roteia só as requisições de debug para lá. O Warden faz exatamente isso: requisições com o cookie XDEBUG_SESSION vão para um container php-debug e todo o resto vai para o normal. O setup do ambiente está no artigo sobre o Warden.
Configuração do PhpStorm
Porta de debug
Em Settings, PHP, Debug, defina a porta do Xdebug como 9003 e confirme que "Can accept external connections" está ligado. Depois ative "Start Listening for PHP Debug Connections" com o ícone de telefone na barra de ferramentas.
Server e path mappings
Em Settings, PHP, Servers, adicione um server:
- Name: o nome de host da sua loja, por exemplo
app.myshop.test - Host e port: o mesmo host, porta 443 para HTTPS
- Use path mappings: ligado. Mapeie a raiz do projeto na sua máquina para o caminho no container, normalmente
/var/www/html
O nome do server importa. Em requisições web o PhpStorm compara com o header host. Na CLI ele usa a variável PHP_IDE_CONFIG, mostrada abaixo. Se o mapeamento está errado, o PhpStorm ou não para, ou abre uma janela perguntando qual arquivo local corresponde ao caminho remoto.
Debugando requisições web
Instale uma extensão de navegador que define o cookie XDEBUG_SESSION, por exemplo as extensões Xdebug helper disponíveis para Chrome e Firefox, e ative na aba da sua loja. Defina um breakpoint e recarregue a página.
Para clientes de API como Postman ou um cliente GraphQL, adicione o header de cookie manualmente, ou adicione o parâmetro de query XDEBUG_SESSION_START=PHPSTORM na URL:
curl -k -H "Cookie: XDEBUG_SESSION=PHPSTORM" \
-H "Content-Type: application/json" \
-d '{"query":"{ storeConfig { store_name } }"}' \
https://app.myshop.test/graphql
A armadilha do full page cache
Se a página está em cache, o PHP pode nunca rodar o código que você quer depurar. O Varnish serve a página sem chegar ao PHP. O full page cache nativo chega ao PHP, mas devolve a resposta em cache antes de a maior parte da aplicação rodar. Para depurar a storefront, desabilite o tipo de cache full_page localmente ou desligue o Varnish no ambiente. O mesmo vale para o block cache quando você depura a saída de um block.
Debugando CLI, cron e consumers
Para comandos, defina o trigger e o nome do server no ambiente, dentro do container:
export XDEBUG_SESSION=1
export PHP_IDE_CONFIG="serverName=app.myshop.test"
php bin/magento indexer:reindex catalog_product_price
No Warden, o warden debug abre um shell no container de debug, onde dá para definir isso. Sem o container de debug, confirme que o PHP CLI que você roda tem o Xdebug carregado com php -m | grep -i xdebug.
Cron jobs são mais fáceis de depurar chamando a classe do job direto do que esperando o cron:run pegá-los. Escreva um pequeno comando CLI ou um teste que chame o método execute() e depure isso. Para consumers de message queue, rode um único consumer com limite, para ele sair depois de uma mensagem:
php bin/magento queue:consumers:start product_action_attribute.update --max-messages=1
Publique uma mensagem de teste primeiro e depois inicie o consumer com o debugger escutando. Mais sobre consumers em consumers do RabbitMQ.
Onde colocar o breakpoint
Interceptors e plugins
Classes com plugins são substituídas em runtime por classes Interceptor geradas em generated/code. Um breakpoint no método original ainda funciona, porque o interceptor chama o método pai no final. Se você quer ver quais plugins rodam e em que ordem, entre no método do interceptor e siga o ___callPlugins.
Quando um breakpoint num plugin nunca é atingido, confira se o plugin está realmente registrado: a area certa no di.xml, o nome certo da classe e do método, e o código gerado atualizado depois da mudança.
Seguindo eventos
Para ver quais observers rodam para um evento, ponha um breakpoint em Magento\Framework\Event\Manager::dispatch() com uma condição no nome do evento, por exemplo $eventName === 'sales_order_place_after'. Breakpoints condicionais são a funcionalidade mais útil do PhpStorm para o Magento, porque muitos métodos do core rodam centenas de vezes por requisição.
Descobrindo onde um valor muda
Quando um preço ou um total da quote está errado, ponha um breakpoint onde você vê o valor errado e suba pela call stack. O painel de frames deixa você clicar em qualquer frame anterior e inspecionar as variáveis dele. Para totais, os collectors rodam numa ordem específica, então parar no collect() de cada collector mostra onde o valor dá errado pela primeira vez. Veja custom totals collectors para saber como eles são ligados.
Timeouts
Uma requisição pausada num breakpoint vai estourar timeouts no nginx e no PHP-FPM se você demorar o bastante. Você verá um 504 no navegador enquanto o PhpStorm ainda está pausado. Em ambientes locais, aumente o fastcgi_read_timeout no nginx e o request_terminate_timeout no pool FPM usado para debug. O max_execution_time não conta o tempo parado no debugger do mesmo jeito, mas definir como 0 localmente evita surpresas.
Quando ainda não funciona
- Confira o
php -vou ophpinfo()no processo que você está depurando e confirme que o Xdebug 3 está carregado commode=debug. - Leia o log do Xdebug. Uma linha "could not connect to debugging client" indica problema de host ou porta, muitas vezes firewall ou um
client_hosterrado. - Confirme que o trigger chega ao PHP. Proxies como o Varnish podem remover cookies ou fazer vary neles.
- Confira os path mappings. Se o PhpStorm pergunta qual arquivo abrir, o mapeamento está errado.
- Garanta que você não está olhando uma saída em cache.
Além do step debugging
O Xdebug também tem um modo de profiling, mas para problemas de performance um profiler dedicado dá resultados mais claros com menos overhead. Veja profiling do Magento com Blackfire para isso. Para bugs que só acontecem em produção, onde você não consegue anexar um debugger, bom logging é a ferramenta: um arquivo de log dedicado por integration, com contexto útil, como descrito no artigo sobre custom logger neste blog.
FAQ
Por que o PhpStorm nunca para no meu breakpoint?
As causas mais comuns são o cookie de trigger ausente, path mappings errados entre o container e o projeto, a página sendo servida do full page cache ou do Varnish, ou o Xdebug conectando no host errado. O log do Xdebug costuma dizer qual delas é.
Qual porta o Xdebug 3 usa?
9003 por padrão. O Xdebug 2 usava a 9000, que conflita com o PHP-FPM. Garanta que o PhpStorm escute na mesma porta do xdebug.client_port.
Como depuro um comando bin/magento?
Defina XDEBUG_SESSION=1 e PHP_IDE_CONFIG com o nome do server configurado no PhpStorm no ambiente do comando, e rode dentro do container com o PhpStorm escutando.
O Xdebug deve ser instalado em produção?
Não. Mesmo ocioso ele adiciona overhead, e um debugger habilitado num servidor público é um risco de segurança. Use localmente, e use um profiler como Blackfire ou New Relic em produção.
Atendo como freelancer em Magento e Adobe Commerce, incluindo caçar bugs difíceis em código próprio e de terceiros. Se você precisa de ajuda com isso, fale comigo.
Artigos relacionados
Ambiente local do Magento 2 com Docker e Warden
Monte um ambiente local do Magento 2.4.8 com Docker e Warden: env-init, o arquivo .env, instalar ou importar um projeto, Xdebug e os comandos do dia a dia.
Erros comuns do Magento 2 e como corrigir
Correções para erros clássicos do Magento 2: Area code is not set, Class does not exist, Invalid Form Key, indexers travados, unserialize e 404 em estáticos.
Arquivo de log customizado no Magento 2 com Monolog (sem classes extras)
Grave logs do Magento 2 em um arquivo próprio com Monolog usando virtual types no di.xml, além de handlers customizados, níveis de log, contexto de exceções e rotação.
Magento 2: tipos de cache, clean, flush e cache type próprio
O que cada cache type do Magento 2 guarda, a diferença real entre cache:clean e cache:flush, caches invalidados e como criar seu próprio cache type.