Proxy PHP Xdebug: cuando las características estándar de Xdebug no son suficientes

Proxy PHP Xdebug: cuando las características estándar de Xdebug no son suficientes


Para la depuraci√≥n, los programas PHP a menudo usan Xdebug . Sin embargo, las caracter√≠sticas est√°ndar del IDE y Xdebug no siempre son suficientes. Algunos de los problemas pueden resolverse utilizando el proxy Xdebug - pydbgpproxy, pero a√ļn no todos. Por lo tanto, implement√© el proxy PHP Xdebug basado en el marco asincr√≥nico amphp.


Debajo del corte, le diré lo que está mal con pydbgpproxy, lo que falta y por qué no lo modifiqué. También explicaré cómo funciona el proxy Xdebug de PHP y mostraré cómo extenderlo con un ejemplo.


Pydbgpproxy vs proxy PHP Xdebug


El proxy Xdebug es un servicio intermedio entre el IDE y Xdebug (solicitudes de proxy de Xdebug al IDE y viceversa). Muy a menudo se utiliza para la depuración multiusuario . Esto es cuando tienes un servidor web y varios desarrolladores.


Como proxy, generalmente se usa pydbgpproxy. Pero tiene algunos problemas:


  • sin p√°gina oficial;
  • dif√≠cil de encontrar donde descargarlo; Resulta que esto se puede hacer aqu√≠ : de repente, el cliente de depuraci√≥n remota de Python;
  • No encontr√© el repositorio oficial;
  • como consecuencia del p√°rrafo anterior, no est√° claro d√≥nde llevar la solicitud de extracci√≥n;
  • proxy, como su nombre lo indica, est√° escrito en Python, que no todos los desarrolladores de PHP saben, lo que significa que expandirlo es un problema;
  • continuaci√≥n del p√°rrafo anterior: si hay alg√ļn c√≥digo en PHP, y necesitar√° usarse en proxy, entonces tendr√° que ser portado a Python, y la duplicaci√≥n de c√≥digo siempre no es muy buena.

La b√ļsqueda de un proxy Xdebug escrito en PHP en GitHub y en Internet no arroj√≥ resultados. Entonces escrib√≠ el proxy Xdebug de PHP . Debajo del cap√≥, utilic√© el marco asincr√≥nico amphp .


Las principales ventajas del proxy PHP Xdebug sobre pydbgpproxy:


  • El proxy PHP Xdebug est√° escrito en un lenguaje familiar para los desarrolladores de PHP, lo que significa:
    • es m√°s f√°cil resolver problemas en √©l;
    • es m√°s f√°cil de expandir;
  • El proxy PHP Xdebug tiene un repositorio p√ļblico, lo que significa:
    • Puede bifurcarlo y terminarlo seg√ļn sus necesidades;
    • Puede enviar una solicitud de extracci√≥n con una funci√≥n faltante o una soluci√≥n a un problema.

Cómo trabajar con proxy Xdebug de PHP


Instalación


El proxy PHP Xdebug se puede instalar como una dependencia de desarrollo a través del compositor :


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

Pero si no desea arrastrar dependencias adicionales a su proyecto, entonces el proxy Xdebug de PHP se puede instalar como un proyecto a través del mismo compositor:


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

El proxy Xdebug de PHP es extensible, pero se requiere ext-dom de forma predeterminada (la extensión está habilitada de forma predeterminada en PHP) para analizar XML y amphp / log para el registro asincrónico:


 composer.phar require amphp/log '^1.0.0' 

Lanzamiento


Proxy PHP Xdebug


El proxy comienza de la siguiente manera:


 bin/xdebug-proxy 

Proxy comenzará con la configuración predeterminada:


 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 ( ) 

Desde el registro puede ver que el proxy predeterminado:


  • escucha 127.0.0.1:9001 para conexiones de inicio de sesi√≥n IDE;
  • escucha 127.0.0.1:9002 para conexiones Xdebug;
  • utiliza 127.0.0.1:9000 como IDE predeterminado e IDE predefinido con la clave idekey.

Configuracion


Si desea configurar puertos de escucha, etc., puede especificar la ruta a la carpeta de configuración. Simplemente copie la carpeta de configuración :


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

Hay tres archivos en la carpeta de configuración:


  • 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 : puedes configurar el registrador; el archivo debe devolver un objeto que sea una instancia de \Psr\Log\LoggerInterface , el valor predeterminado es \Monolog\Logger con \Amp\Log\StreamHandler (para la grabaci√≥n sin bloqueo), muestra los registros en stdout;
  • factory.php : puedes configurar las clases que se usan en proxy; el archivo debe devolver un objeto que sea una instancia de Factory\Factory , el valor predeterminado es Factory\DefaultFactory .

Después de copiar los archivos, puede editar y ejecutar el proxy:


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

Depuración


Se han escrito muchos artículos sobre cómo depurar código usando Xdebug. Anotaré los puntos principales.


En php.ini, las siguientes configuraciones deben estar en la sección [xdebug] ( [xdebug] si difieren de las estándar):


  • idekey = idekey
  • remote_host = 127.0.0.1
  • remote_port = 9002
  • remote_enable = On
  • remote_autostart = On
  • remote_connect_back = Apagado

Luego puede ejecutar el código PHP depurado:


 php /path/to/your/script.php 

Si hizo todo bien, la depuración comenzará desde el primer punto de interrupción en el IDE. La depuración en modo php-fpm por parte de varios desarrolladores está más allá del alcance de este artículo, pero se describe, por ejemplo, aquí .


Funciones de proxy de extensión


Todo lo que examinamos anteriormente, pydbgpproxy también es capaz de un grado u otro.


Ahora hablemos de lo más interesante en PHP Xdebug proxy. Los proxies se pueden ampliar utilizando su propia fábrica (creada en la configuración factory.php , ver arriba). La fábrica debe implementar la interfaz Factory\Factory .


Los más poderosos son los llamados preparadores de solicitudes. Pueden modificar las solicitudes de Xdebug al IDE y viceversa. Para agregar un Factory\DefaultFactory::createRequestPreparers() consultas, debe anular el método Factory\DefaultFactory::createRequestPreparers() . El método devuelve una matriz de objetos que implementan la interfaz RequestPreparer\RequestPreparer . Al enviar una solicitud de Xdebug al IDE, se ejecutan en el orden directo; al enviar una solicitud del IDE a Xdebug, se invierte.


Los manejadores de consultas se pueden usar, por ejemplo, para cambiar rutas a archivos (en puntos de interrupción y archivos ejecutables).


Depurar archivos sobrescritos


Para dar un ejemplo de preparador, har√© una peque√Īa digresi√≥n. En las pruebas unitarias, utilizamos simulacros ( GitHub ). Soft-mocks le permite reemplazar funciones, m√©todos est√°ticos, constantes, etc. en las pruebas, es una alternativa para runkit y uopz . Esto funciona reescribiendo archivos PHP sobre la marcha. Del mismo modo, AspectMock todav√≠a funciona.


Pero las características estándar de Xdebug e IDE le permiten depurar reescritas (que tienen una ruta diferente), en lugar de los archivos originales.


Echemos un vistazo más de cerca al problema de depuración usando simulacros en las pruebas. Primero, tome el caso donde el código PHP se ejecuta localmente.


Las primeras dificultades aparecen en la etapa de establecer puntos de interrupci√≥n (puntos de interrupci√≥n). En el IDE, se instalan en los archivos originales, no en los reescritos. Para poner un punto de interrupci√≥n a trav√©s del IDE, debe encontrar el archivo reescrito real. El problema se agrava por el hecho de que cada vez que se cambia el archivo original, se crea un nuevo archivo reescrito, es decir, para cada contenido de archivo √ļnico habr√° un archivo reescrito √ļnico.


Este problema se puede resolver llamando a la función xdebug_break() , que es similar a establecer un punto de interrupción. En este caso, no hay necesidad de buscar un archivo reescrito.


Ahora considere la situación más complicada: la aplicación se ejecuta en una máquina remota.


En este caso, puede montar la carpeta con los archivos reescritos, por ejemplo, a trav√©s de SSHFS. Si las rutas locales y remotas a la carpeta son diferentes, a√ļn debe registrar las asignaciones en el IDE.


De una forma u otra, este m√©todo es ligeramente diferente del habitual y le permite depurar solo los archivos copiados, pero no los originales. Pero a√ļn as√≠ quiero editar y depurar los mismos archivos originales.


AspectMock solucionó el problema al habilitar el modo de depuración sin la capacidad de deshabilitarlo:


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

En un ejemplo de prueba simple, el modo de depuración es más lento en un 20 por ciento, pero no tengo suficientes pruebas de AspectMock para dar una estimación más precisa de lo lento que es. Si tiene muchas pruebas en AspectMock, me alegrará si comparte la comparación en los comentarios.


Usando Xdebug con simulacros


Xdebug + simulacros


Ahora que el problema está claro, considere cómo resolverlo utilizando el proxy Xdebug de PHP. La parte principal está en la RequestPreparer\SoftMocksRequestPreparer .


En el constructor de la clase, defina la ruta al script de inicializaci√≥n de simulacros blandos y ejec√ļtelo (se supone que los simulacros blandos est√°n conectados como una dependencia, pero se puede pasar cualquier ruta al constructor):


 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 + simulacros: de Xdebug a IDE


Para preparar una solicitud de Xdebug al IDE, debe reemplazar la ruta al archivo reescrito con el archivo 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 + simulacros: de IDE a Xdebug


Para preparar una solicitud del IDE a Xdebug, debe reemplazar la ruta al archivo original con la ruta a la reescrita:


 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 el Factory\DefaultFactory consultas funcione, debe crear su clase de fábrica y heredarla de Factory\DefaultFactory , o implementar la interfaz Factory\Factory . Para los Factory\SoftMocksFactory , la Factory\SoftMocksFactory ve así:


 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()); } } 

Aquí necesita su propia clase de configuración para que pueda especificar la ruta del script de inicio de simulacros. Lo que es, puede ver en Config \ SoftMocksConfig .


Solo quedaba un poco: crear una nueva fábrica e indicar la ruta al script de inicio de simulacros. Cómo se hace esto se puede ver en softMocksConfig .


API sin bloqueo


Como escrib√≠ anteriormente, el proxy Xdebug de PHP usa amphp debajo del cap√≥, lo que significa que se debe usar una API sin bloqueo para trabajar con E / S. Apmphp ya tiene muchos componentes que implementan esta API sin bloqueo. Si va a extender el proxy PHP Xdebug y usarlo en modo multiusuario, aseg√ļrese de usar API sin bloqueo.


Conclusiones


El proxy PHP Xdebug todavía es un proyecto bastante joven, pero en Badoo ya se usa activamente para depurar pruebas usando simulacros.


Proxy PHP Xdebug:


  • reemplaza pydbgpproxy en la depuraci√≥n multiusuario;
  • puede trabajar con simulacros blandos;
  • se puede ampliar:
    • Puede reemplazar las rutas a los archivos que provienen del IDE y de Xdebug;
    • se pueden recopilar estad√≠sticas: en modo de depuraci√≥n, al menos el contexto ejecutable est√° disponible al depurar (valores de variables y l√≠nea de c√≥digo ejecutable).

Si usa el proxy Xdebug para algo que no sea la depuración multiusuario, comparta su caso y el proxy Xdebug que usa en los comentarios.


Si usa pydbgpproxy o alg√ļn otro proxy Xdebug, pruebe el proxy Xdebug de PHP, informe sobre sus problemas, comparta solicitudes de extracci√≥n. ¬°Desarrollemos el proyecto juntos! :)


PD: ¡Gracias a mi colega Yevgeny Makhrov, también conocido como eZH, por la idea de proxy smdbgpproxy !


Enlaces de nuevo



Gracias por su atencion!


Estaré encantado de comentarios y sugerencias.


Rinat Akhmadeev, Sr. Desarrollador PHP


UPD : Traducción publicada del artículo al inglés.

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


All Articles