PHP Xdebug代理:当Xdebug的标准功能不够用时

PHP Xdebug代理:当Xdebug的标准功能不够用时


为了调试PHP程序,通常使用Xdebug 。 但是,IDE和Xdebug的标准功能并不总是足够的。 使用Xdebug代理-pydbgpproxy可以解决一些问题,但还不是全部。 因此,我基于amphp异步框架实现了PHP Xdebug代理


削减后,我将告诉您pydbgpproxy的问题是什么,它缺少什么以及为什么我没有对其进行修改。 我还将通过示例说明PHP Xdebug代理的工作原理,并展示如何扩展它。


Pydbgpproxy vs PHP Xdebug代理


Xdebug代理是IDE和Xdebug之间的中间服务(代理从Xdebug到IDE的请求,反之亦然)。 通常,它用于多用户调试 。 这是当您有一个Web服务器和几个开发人员时。


作为代理,通常使用pydbgpproxy。 但是他有一些问题:


  • 没有官方页面;
  • 很难找到下载位置; 事实证明,这可以在这里完成-突然,Python远程调试客户端;
  • 我没有找到官方资料库;
  • 作为上一段的结果,不清楚将拉取请求提交到何处;
  • 顾名思义,代理是用Python编写的,并不是所有PHP开发人员都知道,这意味着扩展它是一个问题。
  • 上一段的继续:如果PHP中有任何代码,并且需要在代理中使用,则必须将其移植到Python,并且重复代码始终不是很好。

在GitHub和Internet上搜索用PHP编写的Xdebug代理均未返回任何结果。 所以我写了PHP Xdebug代理 。 在后台 ,我使用了amphp异步框架。


PHP Xdebug代理相对于pydbgpproxy的主要优点:


  • PHP Xdebug代理使用PHP开发人员熟悉的语言编写,这意味着:
    • 解决其中的问题更容易;
    • 更容易扩展;
  • PHP Xdebug代理具有一个公共存储库,这意味着:
    • 您可以根据需要分叉并完成它;
    • 您可以发送缺少功能或问题解决方案的请求请求。

如何使用PHP Xdebug代理


安装方式


可以通过composer将PHP Xdebug代理作为dev依赖项安装:


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

但是,如果您不想将额外的依赖项拖到您的项目中,则可以通过相同的编辑器将PHP Xdebug代理作为项目安装:


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

PHP Xdebug代理是可扩展的,但默认情况下需要ext-dom (在PHP中默认启用扩展名)来解析XML,而amphp / log则用于异步日志记录


 composer.phar require amphp/log '^1.0.0' 

发射


PHP Xdebug代理


代理开始如下:


 bin/xdebug-proxy 

代理将从默认设置开始:


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

从日志中,您可以看到默认代理:


  • 侦听127.0.0.1:9001的IDE登录连接;
  • 监听127.0.0.1:9002的Xdebug连接;
  • 使用127.0.0.1:9000作为默认IDE和带有idekey键的预定义IDE。

构型


如果要配置监听端口等,则可以指定设置文件夹的路径。 只需复制config文件夹:


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

设置文件夹中有三个文件:


  • 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 :您可以配置记录器; 该文件应返回一个对象,该对象是\Psr\Log\LoggerInterface的实例,默认值为\Monolog\Logger with \Amp\Log\StreamHandler (用于非阻塞记录),将日志显示到stdout;
  • factory.php :您可以配置代理中使用的类; 该文件应返回一个对象,该对象是Factory\Factory的实例,默认值为Factory\DefaultFactory

复制文件后,您可以编辑并运行代理:


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

侦错


关于如何使用Xdebug调试代码的文章很多。 我会指出要点。


在php.ini中,以下设置应位于[xdebug]部分中(如果它们与标准设置不同,请更正它们):


  • idekey = idekey
  • remote_host = 127.0.0.1
  • remote_port = 9002
  • remote_enable =开
  • remote_autostart =开
  • remote_connect_back =关

然后,您可以运行调试的PHP代码:


 php /path/to/your/script.php 

如果一切正确,那么调试将从IDE中的第一个断点开始。 几个开发人员在php-fpm模式下进行调试超出了本文的范围,但例如在此处进行了描述。


扩展代理功能


上面我们检查的所有内容pydbgpproxy都可以达到某一程度。


现在让我们谈谈PHP Xdebug代理中最有趣的部分。 可以使用您自己的工厂(在factory.php配置中创建,请参见上文)扩展代理。 工厂必须实现Factory\Factory接口。


最强大的是所谓的请求准备器。 他们可以修改从Xdebug到IDE的请求,反之亦然。 要添加查询Factory\DefaultFactory::createRequestPreparers() ,必须重写Factory\DefaultFactory::createRequestPreparers()方法。 该方法返回实现RequestPreparer\RequestPreparer接口的对象数组。 当将Xdebug的请求代理到IDE时,它们以直接顺序执行;当将IDE的请求代理到Xdebug时,请求被反向。


查询处理程序可用于例如更改文件的路径(在断点和可执行文件中)。


调试覆盖的文件


为了举例说明准备者,我将做一点题外话。 在单元测试中,我们使用软包装GitHub )。 软模拟允许您在测试中替换函数,静态方法,常量等,是runkituopz的替代方法。 通过即时重写PHP文件来工作。 同样, AspectMock仍然有效。


但是Xdebug和IDE的标准功能允许您调试重写的文件(具有不同的路径),而不是原始文件。


让我们仔细看看在测试中使用软模拟的调试问题。 首先,以PHP代码在本地执行为例。


第一个困难出现在设置断点(断点)的阶段。 在IDE中,它们安装在原始文件中,而不是重写文件中。 要通过IDE设置断点,您需要找到实际的重写文件。 由于每次更改原始文件都会创建一个新的重写文件,因此事实变得更加复杂,也就是说,对于每个唯一文件内容,都会有一个唯一的重写文件。


可以通过调用xdebug_break()函数来解决此问题,该函数类似于设置断点。 在这种情况下,无需搜索重写的文件。


现在考虑情况更加复杂:应用程序在远程计算机上运行。


在这种情况下,您可以使用重写的文件挂载该文件夹,例如通过SSHFS。 如果文件夹的本地和远程路径不同,则仍然需要在IDE中注册映射。


无论哪种方式,此方法都与通常的方法略有不同,并且仅允许您调试复制的文件,而不能调试原始文件。 但是我仍然想编辑和调试相同的原始文件。


AspectMock通过启用调试模式而不禁用它来解决该问题:


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

在一个简单的测试示例中,调试模式的速度要慢20%,但我没有足够的AspectMock测试来更准确地估计它的速度。 如果您在AspectMock上进行了许多测试,如果您在评论中分享比较信息,我将非常高兴。


结合使用Xdebug和轻弹


Xdebug +软装


现在问题已经很清楚了,请考虑如何使用PHP Xdebug代理解决问题。 主要部分在RequestPreparer\SoftMocksRequestPreparer


在类构造函数中,定义到soft-mocks初始化脚本的路径并运行它(假定将soft-mocks作为依赖关系进行连接,但是任何路径都可以传递给构造函数):


 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 +软包装:从Xdebug到IDE


要准备从Xdebug到IDE的请求,您需要用原始文件替换重写文件的路径:


 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 +软包装:从IDE到Xdebug


要准备从IDE到Xdebug的请求,您需要用重写的路径替换原始文件的路径:


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

为了使查询Factory\DefaultFactory正常工作,您需要创建工厂类并从Factory\DefaultFactory继承它,或实现Factory\Factory接口。 对于软嘲笑, Factory\SoftMocksFactory看起来像这样:


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

在这里,您需要自己的config类,以便您可以指定soft-mocks初始化脚本的路径。 它是什么,您可以在Config \ SoftMocksConfig中看到。


剩下的只有一点:创建一个新的工厂并指出软嘲笑初始化脚本的路径。 可以在softMocksConfig查看如何完成此操作。


非阻塞API


正如我在上面所写,PHP Xdebug代理在后台使用amphp,这意味着必须使用非阻塞API来处理I / O。 Apmphp已经拥有许多实现此非阻塞API的组件。 如果要扩展PHP Xdebug代理并在多用户模式下使用它,请确保使用非阻塞API。


结论


PHP Xdebug代理仍然是一个相当年轻的项目,但是在Badoo中,它已经被积极地用于使用软模拟调试测试。


PHP Xdebug代理:


  • 在多用户调试中替换pydbgpproxy;
  • 可以和小混蛋一起工作;
  • 可以扩展:
    • 您可以替换来自IDE和Xdebug的文件的路径。
    • 可以收集统计信息:在调试模式下,调试时至少可以使用可执行上下文(变量的值和可执行代码行)。

如果您将Xdebug代理用于除多用户调试以外的任何其他功能,请在注释中共享您的案例和您使用的Xdebug代理。


如果您使用pydbgpproxy或其他Xdebug代理,请尝试使用PHP Xdebug代理,告知您的问题,共享请求请求。 让我们一起开发项目! :)


PS感谢我的同事Yevgeny Makhrov aka eZH的代理smdbgpproxy的想法!


再次链接



感谢您的关注!


我将很乐意提出意见和建议。


里纳特·阿赫玛德耶夫(Rinat Akhmadeev) PHP开发人员


UPD文章翻译成英文出版。

Source: https://habr.com/ru/post/zh-CN442504/


All Articles