
为了调试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'
发射

代理开始如下:
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
设置文件夹中有三个文件:
复制文件后,您可以编辑并运行代理:
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 )。 软模拟允许您在测试中替换函数,静态方法,常量等,是runkit和uopz的替代方法。 通过即时重写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和轻弹

现在问题已经很清楚了,请考虑如何使用PHP Xdebug代理解决问题。 主要部分在RequestPreparer\SoftMocksRequestPreparer
。
在类构造函数中,定义到soft-mocks初始化脚本的路径并运行它(假定将soft-mocks作为依赖关系进行连接,但是任何路径都可以传递给构造函数):
public function __construct(LoggerInterface $logger, string $initScript = '') { $this->logger = $logger; if (!$initScript) { $possibleInitScriptPaths = [

要准备从Xdebug到IDE的请求,您需要用原始文件替换重写文件的路径:
public function prepareRequestToIde(XmlDocument $xmlRequest, string $rawRequest): void { $context = [ 'request' => $rawRequest, ]; $root = $xmlRequest->getRoot(); if (!$root) { return; } foreach ($root->getChildren() as $child) {

要准备从IDE到Xdebug的请求,您需要用重写的路径替换原始文件的路径:
public function prepareRequestToXdebug(string $request, CommandToXdebugParser $commandToXdebugParser): string {
为了使查询Factory\DefaultFactory
正常工作,您需要创建工厂类并从Factory\DefaultFactory
继承它,或实现Factory\Factory
接口。 对于软嘲笑, Factory\SoftMocksFactory
看起来像这样:
class SoftMocksFactory extends DefaultFactory { public function createConfig(array $config): Config {
在这里,您需要自己的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 : 将文章翻译成英文出版。