PHP Xdebug-Proxy: Wenn die Standardfunktionen von Xdebug nicht ausreichen

PHP Xdebug-Proxy: Wenn die Standardfunktionen von Xdebug nicht ausreichen


Verwenden Sie zum Debuggen von PHP-Programmen häufig Xdebug . Die Standardfunktionen von IDE und Xdebug reichen jedoch nicht immer aus. Einige der Probleme können mit dem Xdebug-Proxy - pydbgpproxy - gelöst werden, aber immer noch nicht alle. Daher habe ich den PHP Xdebug-Proxy basierend auf dem asynchronen Amphp-Framework implementiert.


Unter dem Schnitt werde ich Ihnen sagen, was mit pydbgpproxy nicht stimmt, was darin fehlt und warum ich es nicht geändert habe. Ich werde auch anhand eines Beispiels erklären, wie der PHP Xdebug-Proxy funktioniert, und zeigen, wie er erweitert werden kann.


Pydbgpproxy vs PHP Xdebug Proxy


Der Xdebug-Proxy ist ein Zwischendienst zwischen der IDE und Xdebug (Proxy-Anforderungen von Xdebug an die IDE und umgekehrt). Am häufigsten wird es für das Mehrbenutzer-Debugging verwendet . Dies ist der Fall, wenn Sie einen Webserver und mehrere Entwickler haben.


Als Proxy wird normalerweise pydbgpproxy verwendet. Aber er hat ein paar Probleme:


  • keine offizielle Seite;
  • schwer zu finden, wo man es herunterladen kann; Es stellt sich heraus, dass dies hier möglich ist - plötzlich Python Remote Debugging Client;
  • Ich habe das offizielle Repository nicht gefunden.
  • Infolge des vorherigen Absatzes ist nicht klar, wohin die Pull-Anfrage gebracht werden soll.
  • Proxy ist, wie der Name schon sagt, in Python geschrieben, was nicht allen PHP-Entwicklern bekannt ist, was bedeutet, dass die Erweiterung ein Problem darstellt.
  • Fortsetzung des vorherigen Absatzes: Wenn in PHP Code vorhanden ist und dieser im Proxy verwendet werden muss, muss er nach Python portiert werden, und das Duplizieren von Code ist immer nicht sehr gut.

Eine Suche nach einem Xdebug-Proxy, der in PHP auf GitHub und im Internet geschrieben wurde, ergab keine Ergebnisse. Also habe ich den PHP Xdebug Proxy geschrieben . Unter der Haube habe ich das asynchrone Amphp- Framework verwendet.


Die Hauptvorteile von PHP Xdebug Proxy gegenüber Pydbgpproxy:


  • Der PHP Xdebug-Proxy ist in einer Sprache geschrieben, die PHP-Entwicklern vertraut ist. Dies bedeutet:
    • es ist einfacher, Probleme darin zu lösen;
    • es ist einfacher zu erweitern;
  • Der PHP Xdebug-Proxy verfügt über ein öffentliches Repository. Dies bedeutet:
    • Sie können es nach Ihren Wünschen teilen und fertigstellen.
    • Sie können eine Pull-Anfrage mit einer fehlenden Funktion oder einer Lösung für ein Problem senden.

So arbeiten Sie mit dem PHP Xdebug-Proxy


Installation


Der PHP Xdebug-Proxy kann über Composer als Entwicklungsabhängigkeit installiert werden:


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

Wenn Sie jedoch keine zusätzlichen Abhängigkeiten in Ihr Projekt ziehen möchten, kann der PHP Xdebug-Proxy über denselben Composer als Projekt installiert werden:


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

Der PHP Xdebug-Proxy ist erweiterbar, aber ext-dom ist standardmäßig erforderlich (die Erweiterung ist in PHP standardmäßig aktiviert), um XML und amphp / log für die asynchrone Protokollierung zu analysieren :


 composer.phar require amphp/log '^1.0.0' 

Starten


PHP Xdebug-Proxy


Der Proxy startet wie folgt:


 bin/xdebug-proxy 

Der Proxy beginnt mit den Standardeinstellungen:


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

Aus dem Protokoll können Sie ersehen, dass der Standard-Proxy:


  • lauscht auf 127.0.0.1:9001 für IDE-Anmeldeverbindungen;
  • lauscht auf 127.0.0.1:9002 für Xdebug-Verbindungen;
  • verwendet 127.0.0.1:9000 als Standard-IDE und vordefinierte IDE mit Idekey-Schlüssel.

Konfiguration


Wenn Sie Überwachungsports usw. konfigurieren möchten, können Sie den Pfad zum Einstellungsordner angeben. Kopieren Sie einfach den Konfigurationsordner :


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

Der Einstellungsordner enthält drei Dateien:


  • 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 : Sie können den Logger konfigurieren. Die Datei sollte ein Objekt zurückgeben, das eine Instanz von \Psr\Log\LoggerInterface ist. Der Standardwert ist \Monolog\Logger mit \Amp\Log\StreamHandler (für nicht blockierende Aufzeichnung). Es werden Protokolle in stdout angezeigt.
  • factory.php : Sie können die Klassen konfigurieren, die im Proxy verwendet werden. Die Datei sollte ein Objekt zurückgeben, das eine Instanz von Factory\Factory ist. Der Standardwert ist Factory\DefaultFactory .

Nach dem Kopieren der Dateien können Sie den Proxy bearbeiten und ausführen:


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

Debuggen


Es wurden viele Artikel zum Debuggen von Code mit Xdebug geschrieben. Ich werde die Hauptpunkte beachten.


In der php.ini sollten sich die folgenden Einstellungen im Abschnitt [xdebug] (korrigieren Sie sie, wenn sie von den Standardeinstellungen abweichen):


  • idekey = idekey
  • remote_host = 127.0.0.1
  • remote_port = 9002
  • remote_enable = Ein
  • remote_autostart = Ein
  • remote_connect_back = Aus

Dann können Sie debuggten PHP-Code ausführen:


 php /path/to/your/script.php 

Wenn Sie alles richtig gemacht haben, beginnt das Debuggen am ersten Haltepunkt in der IDE. Das Debuggen im PHP-Fpm-Modus durch mehrere Entwickler geht über den Rahmen dieses Artikels hinaus, wird jedoch beispielsweise hier beschrieben .


Erweiterungs-Proxy-Funktionen


Alles, was wir oben untersucht haben, ist auch Pydbgpproxy bis zu dem einen oder anderen Grad.


Lassen Sie uns nun über den interessantesten PHP Xdebug-Proxy sprechen. Proxies können mit Ihrer eigenen Factory erweitert werden (erstellt in der Konfiguration factory.php , siehe oben). Die Factory muss die Factory\Factory Schnittstelle implementieren.


Am mächtigsten sind die sogenannten Request Preparer. Sie können Anforderungen von Xdebug an die IDE ändern und umgekehrt. Um einen Factory\DefaultFactory::createRequestPreparers() hinzuzufügen, müssen Sie die Factory\DefaultFactory::createRequestPreparers() -Methode Factory\DefaultFactory::createRequestPreparers() . Die Methode gibt ein Array von Objekten zurück, die die Schnittstelle RequestPreparer\RequestPreparer implementieren. Wenn eine Anforderung von Xdebug an die IDE weitergeleitet wird, werden sie in der direkten Reihenfolge ausgeführt. Wenn Sie eine Anforderung von der IDE an Xdebug weiterleiten, wird sie umgekehrt.


Abfragehandler können beispielsweise verwendet werden, um Pfade zu Dateien zu ändern (in Haltepunkten und ausführbaren Dateien).


Debuggen Sie überschriebene Dateien


Um ein Beispiel für einen Vorbereiter zu geben, werde ich einen kleinen Exkurs machen. In Unit-Tests verwenden wir Soft-Mocks ( GitHub ). Mit Soft-Mocks können Sie Funktionen, statische Methoden, Konstanten usw. in Tests ersetzen . Dies ist eine Alternative für Runkit und Uopz . Dies funktioniert, indem PHP-Dateien im laufenden Betrieb neu geschrieben werden. Ebenso funktioniert AspectMock immer noch.


Mit den Standardfunktionen von Xdebug und IDE können Sie jedoch das neu geschriebene Debugging (mit einem anderen Pfad) anstelle der Originaldateien debuggen.


Schauen wir uns das Debugging-Problem mit Soft-Mocks in Tests genauer an. Nehmen Sie zunächst den Fall, in dem der PHP-Code lokal ausgeführt wird.


Die ersten Schwierigkeiten treten beim Festlegen von Haltepunkten (Haltepunkten) auf. In der IDE werden sie in den Originaldateien installiert, nicht in den neu geschriebenen. Um einen Haltepunkt durch die IDE zu setzen, müssen Sie die tatsächlich neu geschriebene Datei finden. Das Problem wird durch die Tatsache verschärft, dass jedes Mal, wenn die Originaldatei geändert wird, eine neue neu geschriebene Datei erstellt wird, dh für jeden eindeutigen Dateiinhalt wird eine eindeutige neu geschriebene Datei erstellt.


Dieses Problem kann durch Aufrufen der Funktion xdebug_break() gelöst werden, die dem Festlegen eines Haltepunkts ähnelt. In diesem Fall muss nicht nach einer neu geschriebenen Datei gesucht werden.


Betrachten Sie die Situation nun als komplizierter: Die Anwendung wird auf einem Remotecomputer ausgeführt.


In diesem Fall können Sie den Ordner mit den neu geschriebenen Dateien beispielsweise über SSHFS bereitstellen. Wenn sich die lokalen und Remote-Pfade zum Ordner unterscheiden, müssen Sie weiterhin Zuordnungen in der IDE registrieren.


Auf die eine oder andere Weise unterscheidet sich diese Methode geringfügig von der üblichen und ermöglicht es Ihnen, nur die kopierten Dateien zu debuggen, nicht jedoch die Originaldateien. Trotzdem möchte ich dieselben Originaldateien bearbeiten und debuggen.


AspectMock hat das Problem umgangen, indem es den Debug-Modus aktiviert hat, ohne ihn deaktivieren zu können:


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

In einem einfachen Testbeispiel ist der Debug-Modus um 20 Prozent langsamer. Ich habe jedoch nicht genügend AspectMock-Tests, um genauer abzuschätzen, wie langsam er ist. Wenn Sie viele Tests mit AspectMock haben, würde ich mich freuen, wenn Sie den Vergleich in den Kommentaren teilen.


Verwenden von Xdebug mit Soft-Mocks


Xdebug + Soft-Mocks


Nachdem das Problem klar ist, überlegen Sie, wie Sie es mit dem PHP Xdebug-Proxy lösen können. Der Hauptteil befindet sich in der RequestPreparer\SoftMocksRequestPreparer .


Definieren Sie im Klassenkonstruktor den Pfad zum Soft-Mocks-Initialisierungsskript und führen Sie ihn aus (es wird davon ausgegangen, dass Soft-Mocks als Abhängigkeit verbunden sind, aber jeder Pfad kann an den Konstruktor übergeben werden):


 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: von Xdebug zu IDE


Um eine Anforderung von Xdebug an die IDE vorzubereiten, müssen Sie den Pfad zur neu geschriebenen Datei durch die Originaldatei ersetzen:


 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: von IDE zu Xdebug


Um eine Anforderung von der IDE an Xdebug vorzubereiten, müssen Sie den Pfad zur Originaldatei durch den Pfad zur neu geschriebenen ersetzen:


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

Damit der Factory\DefaultFactory funktioniert, müssen Sie Ihre Factory-Klasse erstellen und entweder von Factory\DefaultFactory erben oder die Factory\Factory Schnittstelle implementieren. Für Soft-Mocks sieht die Factory\SoftMocksFactory aus:


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

Hier benötigen Sie eine eigene Konfigurationsklasse, damit Sie den Pfad des Soft-Mocks-Init-Skripts angeben können. Was es ist, können Sie in Config \ SoftMocksConfig sehen .


Es blieb nur wenig übrig: eine neue Factory zu erstellen und den Pfad zum Soft-Mocks-Init-Skript anzugeben. Wie das geht, kann in softMocksConfig eingesehen softMocksConfig .


Nicht blockierende API


Wie ich oben geschrieben habe, verwendet der PHP Xdebug-Proxy Amphp unter der Haube, was bedeutet, dass eine nicht blockierende API verwendet werden muss, um mit E / A zu arbeiten. Apmphp verfügt bereits über viele Komponenten, die diese nicht blockierende API implementieren. Wenn Sie den PHP Xdebug-Proxy erweitern und im Mehrbenutzermodus verwenden möchten, müssen Sie nicht blockierende APIs verwenden.


Schlussfolgerungen


PHP Xdebug Proxy ist noch ein relativ junges Projekt, aber in Badoo wird es bereits aktiv zum Debuggen von Tests mit Soft-Mocks verwendet.


PHP Xdebug-Proxy:


  • ersetzt pydbgpproxy beim Debuggen für mehrere Benutzer;
  • kann mit Soft-Mocks arbeiten;
  • kann erweitert werden:
    • Sie können die Pfade zu Dateien ersetzen, die von der IDE und von Xdebug stammen.
    • Statistiken können gesammelt werden: Im Debug-Modus ist beim Debuggen mindestens der ausführbare Kontext verfügbar (Werte von Variablen und ausführbare Codezeile).

Wenn Sie den Xdebug-Proxy für etwas anderes als das Mehrbenutzer-Debugging verwenden, teilen Sie Ihren Fall und den Xdebug-Proxy, den Sie in den Kommentaren verwenden.


Wenn Sie pydbgpproxy oder einen anderen Xdebug-Proxy verwenden, versuchen Sie es mit dem PHP Xdebug-Proxy, informieren Sie sich über Ihre Probleme und teilen Sie Pull-Anforderungen. Lassen Sie uns das Projekt gemeinsam entwickeln! :) :)


PS Vielen Dank an meinen Kollegen Yevgeny Makhrov aka eZH für die Idee von Proxy smdbgpproxy !


Wieder Links



Vielen Dank für Ihre Aufmerksamkeit!


Ich freue mich über Kommentare und Vorschläge.


Rinat Akhmadeev, Sr. PHP-Entwickler


UPD : Veröffentlichte Übersetzung des Artikels ins Englische.

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


All Articles