Proxy PHP Xdebug: lorsque les fonctionnalités standard de Xdebug ne suffisent pas

Proxy PHP Xdebug: lorsque les fonctionnalités standard de Xdebug ne suffisent pas


Pour déboguer les programmes PHP, utilisez souvent Xdebug . Cependant, les fonctionnalités standard de l'IDE et de Xdebug ne sont pas toujours suffisantes. Certains des problèmes peuvent être résolus en utilisant le proxy Xdebug - pydbgpproxy, mais toujours pas tous. Par conséquent, j'ai implémenté le proxy PHP Xdebug basé sur le cadre asynchrone amphp.


Sous la coupe, je vais vous dire ce qui ne va pas avec pydbgpproxy, ce qui manque et pourquoi je ne l'ai pas modifié. Je vais également expliquer comment fonctionne le proxy PHP Xdebug et montrer comment l'étendre à l'aide d'un exemple.


Pydbgpproxy vs proxy PHP Xdebug


Le proxy Xdebug est un service intermédiaire entre l'IDE et Xdebug (les demandes de proxy de Xdebug à l'IDE et vice versa). Le plus souvent, il est utilisé pour le débogage multi-utilisateur . C'est quand vous avez un serveur Web et plusieurs développeurs.


En tant que proxy, pydbgpproxy est généralement utilisé. Mais il a quelques problèmes:


  • pas de page officielle;
  • difficile de trouver où le télécharger; il s'avère que cela peut être fait ici - tout d'un coup, Python Remote Debugging Client;
  • Je n'ai pas trouvé le dépôt officiel;
  • en raison du paragraphe précédent, il n'est pas clair où introduire la demande de retrait;
  • proxy, comme son nom l'indique, est écrit en Python, ce que tous les développeurs PHP ne connaissent pas, ce qui signifie que son expansion est un problème;
  • suite du paragraphe précédent: s'il y a du code en PHP, et qu'il devra être utilisé en proxy, alors il devra être porté en Python, et la duplication de code n'est pas toujours très bonne.

Une recherche d'un proxy Xdebug écrit en PHP sur GitHub et sur Internet n'a donné aucun résultat. J'ai donc écrit le proxy PHP Xdebug . Sous le capot, j'ai utilisé le cadre asynchrone amphp .


Les principaux avantages du proxy PHP Xdebug par rapport à pydbgpproxy:


  • Le proxy PHP Xdebug est écrit dans un langage familier aux développeurs PHP, ce qui signifie:
    • il est plus facile d'y résoudre des problèmes;
    • il est plus facile à étendre;
  • Le proxy PHP Xdebug a un référentiel public, ce qui signifie:
    • Vous pouvez le bifurquer et le finir selon vos besoins;
    • Vous pouvez envoyer une demande d'extraction avec une fonctionnalité manquante ou une solution à un problème.

Comment travailler avec le proxy PHP Xdebug


L'installation


Le proxy PHP Xdebug peut être installé en tant que dépendance de dev via le composeur :


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

Mais si vous ne souhaitez pas faire glisser des dépendances supplémentaires dans votre projet, le proxy PHP Xdebug peut être installé en tant que projet via le même compositeur:


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

Le proxy PHP Xdebug est extensible, mais ext-dom est requis par défaut (l'extension est activée par défaut en PHP) pour analyser XML et amphp / log pour la journalisation asynchrone:


 composer.phar require amphp/log '^1.0.0' 

Lancement


Proxy PHP Xdebug


Le proxy démarre comme suit:


 bin/xdebug-proxy 

Le proxy commencera avec les paramètres par défaut:


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

Dans le journal, vous pouvez voir que le proxy par défaut:


  • écoute 127.0.0.1:9001 pour les connexions de connexion IDE;
  • écoute 127.0.0.1:9002 pour les connexions Xdebug;
  • utilise 127.0.0.1:9000 comme IDE par défaut et IDE prédéfini avec clé idekey.

La configuration


Si vous souhaitez configurer des ports d'écoute, etc., vous pouvez spécifier le chemin d'accès au dossier des paramètres. Copiez simplement le dossier config :


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

Il y a trois fichiers dans le dossier des paramètres:


  • 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 : vous pouvez configurer l'enregistreur; le fichier doit renvoyer un objet qui est une instance de \Psr\Log\LoggerInterface , la valeur par défaut est \Monolog\Logger avec \Amp\Log\StreamHandler (pour l'enregistrement non bloquant), affiche les journaux sur stdout;
  • factory.php : vous pouvez configurer les classes utilisées dans le proxy; le fichier doit renvoyer un objet qui est une instance de Factory\Factory , la valeur par défaut est Factory\DefaultFactory .

Après avoir copié les fichiers, vous pouvez modifier et exécuter le proxy:


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

Débogage


De nombreux articles ont été écrits sur la façon de déboguer du code à l'aide de Xdebug. Je noterai les points principaux.


Dans php.ini, les paramètres suivants doivent se trouver dans la section [xdebug] (corrigez-les s'ils diffèrent des paramètres standard):


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

Ensuite, vous pouvez exécuter du code PHP débogué:


 php /path/to/your/script.php 

Si vous avez tout fait correctement, le débogage commencera à partir du premier point d'arrêt dans l'EDI. Le débogage en mode php-fpm par plusieurs développeurs dépasse le cadre de cet article, mais est décrit, par exemple, ici .


Fonctionnalités du proxy d'extension


Tout ce que nous avons examiné ci-dessus, pydbgpproxy est également capable à un degré ou à un autre.


Parlons maintenant du plus intéressant du proxy PHP Xdebug. Les proxys peuvent être étendus à l'aide de votre propre usine (créée dans la configuration factory.php , voir ci-dessus). L'usine doit implémenter l'interface Factory\Factory .


Les plus puissants sont les soi-disant préparateurs de requêtes. Ils peuvent modifier les requêtes de Xdebug vers l'IDE et vice versa. Pour ajouter un Factory\DefaultFactory::createRequestPreparers() requêtes, vous devez remplacer la méthode Factory\DefaultFactory::createRequestPreparers() . La méthode renvoie un tableau d'objets qui implémentent l'interface RequestPreparer\RequestPreparer . Lors du proxy d'une demande de Xdebug à l'IDE, ils sont exécutés dans l'ordre direct; lors du proxy d'une requête de l'IDE à Xdebug, il est inversé.


Les gestionnaires de requêtes peuvent être utilisés, par exemple, pour modifier les chemins d'accès aux fichiers (dans les points d'arrêt et les fichiers exécutables).


Déboguer les fichiers remplacés


Afin de donner un exemple de préparateur, je vais faire une petite digression. Dans les tests unitaires, nous utilisons des soft-mocks ( GitHub ). Soft-mocks vous permet de remplacer des fonctions, des méthodes statiques, des constantes, etc. dans les tests, est une alternative pour runkit et uopz . Cela fonctionne en réécrivant les fichiers PHP à la volée. De même, AspectMock fonctionne toujours.


Mais les fonctionnalités standard de Xdebug et IDE vous permettent de déboguer réécrit (avec un chemin différent), plutôt que les fichiers d'origine.


Examinons de plus près le problème de débogage à l'aide de soft-mocks dans les tests. Tout d'abord, prenons le cas où le code PHP est exécuté localement.


Les premières difficultés apparaissent au stade de la définition des points d'arrêt (points d'arrêt). Dans l'EDI, ils sont installés dans les fichiers d'origine, pas dans les fichiers réécrits. Pour mettre un point d'arrêt via l'EDI, vous devez trouver le fichier réécrit réel. Le problème est aggravé par le fait que chaque fois que le fichier d'origine est modifié, un nouveau fichier réécrit est créé, c'est-à-dire que pour chaque contenu de fichier unique, il y aura un fichier réécrit unique.


Ce problème peut être résolu en appelant la fonction xdebug_break() , qui est similaire à la définition d'un point d'arrêt. Dans ce cas, il n'est pas nécessaire de rechercher un fichier réécrit.


Considérez maintenant la situation plus compliquée: l'application s'exécute sur une machine distante.


Dans ce cas, vous pouvez monter le dossier avec les fichiers réécrits, par exemple, via SSHFS. Si les chemins d'accès local et distant vers le dossier sont différents, vous devez toujours enregistrer les mappages dans l'EDI.


D'une manière ou d'une autre, cette méthode est légèrement différente de la méthode habituelle et vous permet de déboguer uniquement les fichiers copiés, mais pas ceux d'origine. Mais je veux toujours éditer et déboguer les mêmes fichiers originaux.


AspectMock a résolu le problème en activant le mode débogage sans la possibilité de le désactiver:


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

Dans un exemple de test simple, le mode de débogage est par la suite plus lent de 20%. Mais je n'ai pas assez de tests AspectMock pour donner une estimation plus précise de sa lenteur. Si vous avez de nombreux tests sur AspectMock, je serai heureux si vous partagez la comparaison dans les commentaires.


Utiliser Xdebug avec des soft-mocks


Xdebug + soft-mocks


Maintenant que le problème est clair, réfléchissez à la manière de le résoudre à l'aide du proxy PHP Xdebug. La partie principale se trouve dans la RequestPreparer\SoftMocksRequestPreparer .


Dans le constructeur de classe, définissez le chemin d'accès au script d'initialisation de soft-mocks et exécutez-le (il est supposé que soft-mocks est connecté en tant que dépendance, mais n'importe quel chemin peut être transmis au constructeur):


 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-mocks: de Xdebug à IDE


Pour préparer une demande de Xdebug à l'IDE, vous devez remplacer le chemin d'accès au fichier réécrit par le fichier d'origine:


 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-mocks: de l'IDE à Xdebug


Pour préparer une demande de l'EDI à Xdebug, vous devez remplacer le chemin d'accès au fichier d'origine par le chemin d'accès au fichier réécrit:


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

Pour que le Factory\DefaultFactory requêtes fonctionne, vous devez créer votre classe d'usine et l'hériter de Factory\DefaultFactory ou implémenter l'interface Factory\Factory . Pour les soft-mocks, la Factory\SoftMocksFactory ressemble à ceci:


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

Ici, vous avez besoin de votre propre classe de configuration pour pouvoir spécifier le chemin du script d'initialisation des soft-mocks. Ce que c'est, vous pouvez le voir dans Config \ SoftMocksConfig .


Il ne restait plus que peu: créer une nouvelle usine et indiquer le chemin vers le script d'initialisation des soft-mocks. La softMocksConfig à softMocksConfig peut être consultée dans softMocksConfig .


API non bloquante


Comme je l'ai écrit ci-dessus, le proxy PHP Xdebug utilise amphp sous le capot, ce qui signifie qu'une API non bloquante doit être utilisée pour fonctionner avec les E / S. Apmphp possède déjà de nombreux composants qui implémentent cette API non bloquante. Si vous allez étendre le proxy PHP Xdebug et l'utiliser en mode multi-utilisateur, assurez-vous d'utiliser des API non bloquantes.


Conclusions


Le proxy PHP Xdebug est encore un projet assez récent, mais dans Badoo il est déjà activement utilisé pour déboguer des tests à l'aide de soft-mocks.


Proxy PHP Xdebug:


  • remplace pydbgpproxy dans le débogage multi-utilisateurs;
  • peut fonctionner avec des soft-mocks;
  • peut être étendu:
    • Vous pouvez remplacer les chemins d'accès aux fichiers provenant de l'IDE et de Xdebug;
    • des statistiques peuvent être collectées: en mode débogage, au moins le contexte exécutable est disponible lors du débogage (valeurs des variables et ligne de code exécutable).

Si vous utilisez le proxy Xdebug pour autre chose que le débogage multi-utilisateur, partagez votre cas et le proxy Xdebug que vous utilisez dans les commentaires.


Si vous utilisez pydbgpproxy ou un autre proxy Xdebug, essayez le proxy PHP Xdebug, informez-vous de vos problèmes, partagez les demandes de tirage. Développons le projet ensemble! :)


PS Merci à mon collègue Yevgeny Makhrov alias eZH pour l'idée du proxy smdbgpproxy !


Liens à nouveau



Merci de votre attention!


Je serai heureux de recevoir des commentaires et suggestions.


Rinat Akhmadeev, Sr. Développeur PHP


UPD : traduction publiée de l' article en anglais.

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


All Articles