Proxy PHP Xdebug: quando os recursos padrão do Xdebug não são suficientes

Proxy PHP Xdebug: quando os recursos padrão do Xdebug não são suficientes


Para depurar programas PHP, use frequentemente o Xdebug . No entanto, os recursos padrão do IDE e do Xdebug nem sempre são suficientes. Alguns dos problemas podem ser resolvidos usando o proxy Xdebug - pydbgpproxy, mas ainda não todos. Portanto, implementei o proxy PHP Xdebug com base na estrutura assíncrona amphp.


Abaixo, mostrarei o que há de errado com o pydbgpproxy, o que está faltando e por que não o modifiquei. Também explicarei como o proxy PHP Xdebug funciona e mostrarei como estendê-lo usando um exemplo.


Pydbgpproxy vs PHP Xdebug proxy


O proxy Xdebug é um serviço intermediário entre o IDE e o Xdebug (solicitações de proxies do Xdebug para o IDE e vice-versa). Na maioria das vezes, é usado para depuração multiusuário . É quando você tem um servidor web e vários desenvolvedores.


Como proxy, o pydbgpproxy geralmente é usado. Mas ele tem alguns problemas:


  • nenhuma página oficial;
  • difícil encontrar onde fazer o download; acontece que isso pode ser feito aqui - de repente, o cliente de depuração remota Python;
  • Não encontrei o repositório oficial;
  • como conseqüência do parágrafo anterior, não está claro para onde levar a solicitação de recebimento;
  • proxy, como o nome indica, é escrito em Python, que nem todos os desenvolvedores de PHP conhecem, o que significa que expandir é um problema;
  • continuação do parágrafo anterior: se houver algum código no PHP, e ele precisar ser usado em proxy, ele deverá ser portado para Python, e a duplicação de código nem sempre será muito boa.

A busca por um proxy Xdebug escrito em PHP no GitHub e na Internet não obteve resultados. Então eu escrevi o proxy PHP Xdebug . Sob o capô, usei a estrutura assíncrona de amphp .


As principais vantagens do proxy PHP Xdebug sobre o pydbgpproxy:


  • O proxy PHP Xdebug é escrito em uma linguagem familiar aos desenvolvedores de PHP, o que significa:
    • é mais fácil resolver problemas nele;
    • é mais fácil expandir;
  • O proxy PHP Xdebug possui um repositório público, o que significa:
    • Você pode bifurcar e finalizar de acordo com suas necessidades;
    • Você pode enviar uma solicitação de recebimento com um recurso ausente ou uma solução para um problema.

Como trabalhar com o proxy PHP Xdebug


Instalação


O proxy PHP Xdebug pode ser instalado como uma dependência do desenvolvedor via compositor :


composer.phar require mougrim/php-xdebug-proxy --dev 

Mas se você não deseja arrastar dependências extras para o seu projeto, o proxy PHP Xdebug pode ser instalado como um projeto através do mesmo compositor:


 composer.phar create-project mougrim/php-xdebug-proxy cd php-xdebug-proxy 

O proxy Xdebug do PHP é extensível, mas o ext-dom é necessário por padrão (a extensão é ativada por padrão no PHP) para analisar XML e amphp / log para log assíncrono:


 composer.phar require amphp/log '^1.0.0' 

Lançamento


Proxy PHP Xdebug


O proxy inicia da seguinte maneira:


 bin/xdebug-proxy 

O proxy começará com as configurações padrão:


 Using config path /path/to/php-xdebug-proxy/config [2019-02-14 10:46:24] xdebug-proxy.NOTICE: Use default ide: 127.0.0.1:9000 array ( ) array ( ) [2019-02-14 10:46:24] xdebug-proxy.NOTICE: Use predefined ides array ( 'predefinedIdeList' => array ( 'idekey' => '127.0.0.1:9000', ), ) array ( ) [2019-02-14 10:46:24] xdebug-proxy.NOTICE: [Proxy][IdeRegistration] Listening for new connections on '127.0.0.1:9001'... array ( ) array ( ) [2019-02-14 10:46:24] xdebug-proxy.NOTICE: [Proxy][Xdebug] Listening for new connections on '127.0.0.1:9002'... array ( ) array ( ) 

No log, você pode ver que o proxy padrão:


  • escuta 127.0.0.1:9001 para conexões de login do IDE;
  • ouve 127.0.0.1:9002 para conexões Xdebug;
  • usa 127.0.0.1:9000 como o IDE padrão e o IDE predefinido com a chave idekey.

Configuração


Se você deseja configurar portas de escuta, etc., pode especificar o caminho para a pasta de configurações. Basta copiar a pasta de configuração :


 cp -r /path/to/php-xdebug-proxy/config /your/custom/path 

Existem três arquivos na pasta de configurações:


  • config.php :
     <?php return [ 'xdebugServer' => [ // host:port    Xdebug 'listen' => '127.0.0.1:9002', ], 'ideServer' => [ //  proxy    IDE,     IDE  , //    IDE  ,     . // IDE   ,  proxy    . 'defaultIde' => '127.0.0.1:9000', //  IDE    'idekey' => 'host:port', //   IDE  ,     . //  IDE ,   proxy  , //           proxy. 'predefinedIdeList' => [ 'idekey' => '127.0.0.1:9000', ], ], 'ideRegistrationServer' => [ // host:port     IDE, //     IDE,     . 'listen' => '127.0.0.1:9001', ], ]; 
  • logger.php : você pode configurar o logger; o arquivo deve retornar um objeto que é uma instância de \Psr\Log\LoggerInterface , o padrão é \Monolog\Logger com \Amp\Log\StreamHandler (para gravação sem bloqueio), exibe logs para stdout;
  • factory.php : você pode configurar as classes que são usadas no proxy; o arquivo deve retornar um objeto que é uma instância de Factory\Factory , o padrão é Factory\DefaultFactory .

Após copiar os arquivos, você pode editar e executar o proxy:


 bin/xdebug-proxy --configs=/your/custom/path/config 

Depuração


Muitos artigos foram escritos sobre como depurar código usando o Xdebug. Vou anotar os pontos principais.


No php.ini, as seguintes configurações devem estar na seção [xdebug] (corrija-as se forem diferentes das padrão):


  • idekey = idekey
  • remote_host = 127.0.0.1
  • remote_port = 9002
  • remote_enable = Ativado
  • remote_autostart = Ativado
  • remote_connect_back = Desativado

Então você pode executar o código PHP depurado:


 php /path/to/your/script.php 

Se você fez tudo certo, a depuração será iniciada a partir do primeiro ponto de interrupção no IDE. A depuração no modo php-fpm por vários desenvolvedores está além do escopo deste artigo, mas é descrita, por exemplo, aqui .


Recursos de proxy de extensão


Tudo o que examinamos acima, pydbgpproxy, também é capaz em um grau ou outro.


Agora vamos falar sobre o mais interessante no proxy PHP Xdebug. Os proxies podem ser expandidos usando sua própria fábrica (criada na configuração factory.php , veja acima). A fábrica deve implementar a interface Factory\Factory .


Os mais poderosos são os chamados preparadores de solicitações. Eles podem modificar solicitações do Xdebug para o IDE e vice-versa. Para adicionar um Factory\DefaultFactory::createRequestPreparers() consultas, você deve substituir o método Factory\DefaultFactory::createRequestPreparers() . O método retorna uma matriz de objetos que implementam a interface RequestPreparer\RequestPreparer . Ao fazer o proxy de uma solicitação do Xdebug para o IDE, elas são executadas na ordem direta; ao fazer o proxy de uma solicitação do IDE para o Xdebug, ela é revertida.


Os manipuladores de consulta podem ser usados, por exemplo, para alterar caminhos para arquivos (em pontos de interrupção e arquivos executáveis).


Depurar arquivos substituídos


Para dar um exemplo de preparador, farei uma pequena digressão. Nos testes de unidade, usamos soft-zomba ( GitHub ). Soft-zomba permite que você substitua funções, métodos estáticos, constantes, etc. em testes, é uma alternativa para runkit e uopz . Isso funciona reescrevendo arquivos PHP em tempo real. Da mesma forma, o AspectMock ainda funciona.


Mas os recursos padrão do Xdebug e do IDE permitem depurar reescritos (com um caminho diferente), em vez dos arquivos originais.


Vamos dar uma olhada no problema de depuração usando zombarias nos testes. Primeiro, considere o caso em que o código PHP é executado localmente.


As primeiras dificuldades aparecem no estágio de definição de pontos de interrupção (pontos de interrupção). No IDE, eles são instalados nos arquivos originais, não nos reescritos. Para colocar um ponto de interrupção no IDE, você precisa encontrar o arquivo reescrito real. O problema é agravado pelo fato de que cada vez que o arquivo original é alterado, um novo arquivo reescrito é criado, ou seja, para cada conteúdo de arquivo exclusivo, haverá um arquivo reescrito exclusivo.


Esse problema pode ser resolvido chamando a função xdebug_break() , que é semelhante à configuração de um ponto de interrupção. Nesse caso, não há necessidade de procurar um arquivo reescrito.


Agora considere a situação mais complicada: o aplicativo é executado em uma máquina remota.


Nesse caso, você pode montar a pasta com os arquivos reescritos, por exemplo, através do SSHFS. Se os caminhos local e remoto para a pasta forem diferentes, você ainda precisará registrar mapeamentos no IDE.


De uma forma ou de outra, esse método é um pouco diferente do usual e permite depurar apenas os arquivos copiados, mas não os originais. Mas ainda quero editar e depurar os mesmos arquivos originais.


O AspectMock solucionou o problema ativando o modo de depuração sem a capacidade de desativá-lo:


 public function init(array $options = []) { if (!isset($options['excludePaths'])) { $options['excludePaths'] = []; } $options['debug'] = true; $options['excludePaths'][] = __DIR__; parent::init($options); } 

Em um exemplo de teste simples, o modo de depuração é mais lento em 20%, mas não tenho testes suficientes do AspectMock para fornecer uma estimativa mais precisa de quão lento é. Se você tiver muitos testes no AspectMock, ficarei feliz em compartilhar a comparação nos comentários.


Usando o Xdebug com soft-zomba


Xdebug + zombarias


Agora que o problema está claro, considere como resolvê-lo usando o proxy Xdebug do PHP. A parte principal está na RequestPreparer\SoftMocksRequestPreparer .


No construtor da classe, defina o caminho para o script de inicialização de soft-mocks e execute-o (pressupõe-se que os soft-mocks estejam conectados como uma dependência, mas qualquer caminho pode ser passado para o construtor):


 public function __construct(LoggerInterface $logger, string $initScript = '') { $this->logger = $logger; if (!$initScript) { $possibleInitScriptPaths = [ // proxy   , soft-mocks —    __DIR__.'/../../vendor/badoo/soft-mocks/src/init_with_composer.php', // proxy  soft-mocks    __DIR__.'/../../../../badoo/soft-mocks/src/init_with_composer.php', ]; foreach ($possibleInitScriptPaths as $possiblInitScriptPath) { if (file_exists($possiblInitScriptPath)) { $initScript = $possiblInitScriptPath; break; } } } if (!$initScript) { throw new Error("Can't find soft-mocks init script"); } //  soft-mocks (       ..) require $initScript; } 

Xdebug + soft-zomba: do Xdebug para o IDE


Para preparar uma solicitação do Xdebug para o IDE, é necessário substituir o caminho do arquivo reescrito pelo arquivo original:


 public function prepareRequestToIde(XmlDocument $xmlRequest, string $rawRequest): void { $context = [ 'request' => $rawRequest, ]; $root = $xmlRequest->getRoot(); if (!$root) { return; } foreach ($root->getChildren() as $child) { //         : // - 'stack': https://xdebug.org/docs-dbgp.php#stack-get // - 'xdebug:message': https://xdebug.org/docs-dbgp.php#error-notification if (!in_array($child->getName(), ['stack', 'xdebug:message'], true)) { continue; } $attributes = $child->getAttributes(); if (isset($attributes['filename'])) { //         ,      $filename = $this->getOriginalFilePath($attributes['filename'], $context); if ($attributes['filename'] !== $filename) { $this->logger->info("Change '{$attributes['filename']}' to '{$filename}'", $context); $child->addAttribute('filename', $filename); } } } } 

Xdebug + soft-zomba: do IDE ao Xdebug


Para preparar uma solicitação do IDE para o Xdebug, você precisa substituir o caminho para o arquivo original pelo caminho para o reescrito:


 public function prepareRequestToXdebug(string $request, CommandToXdebugParser $commandToXdebugParser): string { //       [$command, $arguments] = $commandToXdebugParser->parseCommand($request); $context = [ 'request' => $request, 'arguments' => $arguments, ]; if ($command === 'breakpoint_set') { //    -f,          // . https://xdebug.org/docs-dbgp.php#id3 if (isset($arguments['-f'])) { $file = $this->getRewrittenFilePath($arguments['-f'], $context); if ($file) { $this->logger->info("Change '{$arguments['-f']}' to '{$file}'", $context); $arguments['-f'] = $file; //    $request = $commandToXdebugParser->buildCommand($command, $arguments); } } else { $this->logger->error("Command {$command} is without argument '-f'", $context); } } return $request; } 

Para que o Factory\DefaultFactory consultas funcione, você precisa criar sua classe de fábrica e herdá-la de Factory\DefaultFactory ou implementar a interface Factory\Factory . Para soft-zombarias, a Factory\SoftMocksFactory é semelhante a:


 class SoftMocksFactory extends DefaultFactory { public function createConfig(array $config): Config { //       return new SoftMocksConfig($config); } public function createRequestPreparers(LoggerInterface $logger, Config $config): array { $requestPreparers = parent::createRequestPreparers($logger, $config); return array_merge($requestPreparers, [$this->createSoftMocksRequestPreparer($logger, $config)]); } public function createSoftMocksRequestPreparer(LoggerInterface $logger, SoftMocksConfig $config): SoftMocksRequestPreparer { //     init-   return new SoftMocksRequestPreparer($logger, $config->getSoftMocks()->getInitScript()); } } 

Aqui você precisa de sua própria classe de configuração para poder especificar o caminho do script init de soft-mocks. O que é, você pode ver em Config \ SoftMocksConfig .


Restou apenas um pouco: criar uma nova fábrica e indicar o caminho para o script init de soft-mock. Como isso é feito pode ser visualizado no softMocksConfig .


API sem bloqueio


Como escrevi acima, o proxy PHP Xdebug usa amphp por baixo do capô, o que significa que uma API sem bloqueio deve ser usada para trabalhar com E / S. O Apmphp já possui muitos componentes que implementam essa API sem bloqueio. Se você deseja estender o proxy PHP Xdebug e usá-lo no modo multiusuário, certifique-se de usar APIs sem bloqueio.


Conclusões


O proxy PHP Xdebug ainda é um projeto bastante jovem, mas no Badoo ele já é usado ativamente para depurar testes usando soft-zomba.


Proxy do PHP Xdebug:


  • substitui pydbgpproxy na depuração multiusuário;
  • pode trabalhar com zombarias;
  • pode ser expandido:
    • Você pode substituir os caminhos dos arquivos provenientes do IDE e do Xdebug;
    • as estatísticas podem ser coletadas: no modo de depuração, pelo menos o contexto executável está disponível durante a depuração (valores de variáveis ​​e linha de código executável).

Se você usar o proxy Xdebug para outra coisa que não seja a depuração multiusuário, compartilhe seu caso e o proxy Xdebug que você usa nos comentários.


Se você usa o pydbgpproxy ou algum outro proxy Xdebug, tente o proxy PHP Xdebug, conte sobre seus problemas, compartilhe solicitações pull. Vamos desenvolver o projeto juntos! :)


PS Obrigado ao meu colega Yevgeny Makhrov aka eZH pela idéia de proxy smdbgpproxy !


Links novamente



Obrigado pela atenção!


Terei o maior prazer em comentários e sugestões.


Rinat Akhmadeev, Sr. Desenvolvedor PHP


UPD : tradução publicada do artigo para o inglês.

Source: https://habr.com/ru/post/pt442504/


All Articles