如何在PHP中使用调试器
#php #调试 #xdebug

本文最初是由Mauro ChojrinHoneybadger Developer Blog上撰写的。

错误很烦人。它们是迄今为止任何软件开发项目中最耗时的一部分,尽管任何开发人员都希望得到什么,但没有办法永远消除它们。

作为开发人员可以做的是利用现有的工具和方法,这些工具和方法有助于减少用于追捕和修复它们的时间。

不幸的是,对于PHP开发人员而言,该语言的内置工具是平庸的,这可能是一个夸大其词。想想var_dumpprint_rdumpdie的动态二人组。

在本文中,您将准确了解什么是调试器,PHP生态系统中的选项以及如何安装和使用。

让我们开始。

什么是调试器

通常,调试器是一种工具,可以帮助找到错误的原因。特别是,每个工具都有自己的功能集,但其目的始终是执行调试。

不要感到困惑;调试器还不能解决您的问题,但这肯定会帮助您查明问题。

在我们进行更深入的挖掘之前,让我们快速查看您的工具包的工具:

  • 错误消息
  • 日志文件

这些肯定非常重要,但是不会非常有用。更重要的是,您仅在之后获得信息完全执行,这使得很难获得发生错误时发生的事情的上下文。这意味着您必须重新创建程序中程序的动态流。

调试器将允许您在程序运行时看到变量的内容,而不是在最后一行运行后。

使用这样的工具的另一个重要优势是能够检查运行时值而无需添加代码

我想强调最后一点,因为许多开发人员认为它并不重要。在战斗中,您输入了最终用户不应该看到的调试消息并不少见,例如:

<?php

if ($var === INVALID_VALUE) {
    echo 'Arrrrrrrrgghhh!!! $var = INVALID_VALUE! Oh god why oh why???';
}

然后,经过数小时彻底检查代码后,您最终理解它,修复并将其称为一天。但是,当演示时间到来时,您会发现自己有这样的屏幕:

Screen containing debugging message

或更糟...

我希望您现在意识到,不篡改您的代码而看到发生的事情的能力非常有价值。

实际上,调试器是集成开发环境(IDE)的插件或插件。就PHP而言,这有点棘手,因为PHP是一种解释的语言,通常在Web服务器的顶部运行。基本上,每个PHP调试器都有两个部分:服务器(调试器本身)和客户端(IDE)。

可用于PHP的可访问者

PHP调试景观中有一些严肃的参赛者:

phpdbg

PHPDBG是具有内置调试功能的交互式PHP解释器。自5.6版以来,它已与每个PHP安装捆绑在一起。在您无法访问适当的图形编辑器(例如远程服务器)的情况下,它可能会派上用场。

要开始它,您需要做的就是按以下内容发出命令:

phpdbg -e /path/to/your/php/script

会向您显示这样的提示:

[Welcome to phpdbg, the interactive PHP debugger, v8.0.12]
To get help using phpdbg type "help" and press enter
[Please report bugs to <http://bugs.php.net/report.php>]
[Successful compilation of /path/to/your/php/script]
prompt>

从那里,您可以逐步浏览程序,设置断点等。

<?php

try {
    $conn = new PDO('sqlite:dt.sq3');
} catch (PDOException $exception) {
    die($exception->getMessage());
}

$sql = "SELECT * FROM products";
$st = $conn
    ->query($sql);

if ($st) {
    $rs = $st->fetchAll(PDO::FETCH_FUNC, fn($id, $name, $price) => [$id, $name, $price] );

    echo json_encode([
        'data' => $rs,
    ]);
} else {
    var_dump($conn->errorInfo());
    die;
}

您想在json编码进行之前查看$rs的内容。您可以使用此命令开始调试会话:

phpdbg -e get_data.php

然后,在第14行上放置一个断点:

prompt> b 14
[Breakpoint #0 added at get_data.php:14]

接下来,使用此命令将脚本运行到该点:

prompt> run
[Breakpoint #0 at get_data.php:14, hits: 1]
>00014:     $rs = $st->fetchAll(PDO::FETCH_FUNC, fn($id, $name, $price) => [$id, $name, $price] );
 00015: 
 00016:     echo json_encode([

再迈出一个步骤:

prompt> step
[L14      0x7fa306058d80 INIT_METHOD_CALL<2>     $st                  "fetchAll"                                get_data.php]
[L14      0x7fa306058da0 SEND_VAL_EX             10                   1                                         get_data.php]
[L14      0x7fa306058dc0 DECLARE_LAMBDA_FUNCTION<64> "\000{closure}/hom"+                      ~7                   get_data.php]
[L14      0x7fa306058de0 SEND_VAL_EX             ~7                   2                                         get_data.php]
[L14      0x7fa306058e00 EXT_FCALL_BEGIN                                                                        get_data.php]
[L14      0x7fa306058e20 DO_FCALL                                                          @8                   get_data.php]
[L14      0x7fa306092060 EXT_STMT                                                                               get_data.php]
[L14      0x7fa306092080 INIT_ARRAY<12>          $id                  NEXT                 ~0                   get_data.php]
[L14      0x7fa3060920a0 ADD_ARRAY_ELEMENT       $name                NEXT                 ~0                   get_data.php]
[L14      0x7fa3060920c0 ADD_ARRAY_ELEMENT       $price               NEXT                 ~0                   get_data.php]
[L14      0x7fa3060920e0 RETURN                  ~0                                                             get_data.php]
[L14      0x7fa306092060 EXT_STMT                                                                               get_data.php]
[L14      0x7fa306092080 INIT_ARRAY<12>          $id                  NEXT                 ~0                   get_data.php]
[L14      0x7fa3060920a0 ADD_ARRAY_ELEMENT       $name                NEXT                 ~0                   get_data.php]
[L14      0x7fa3060920c0 ADD_ARRAY_ELEMENT       $price               NEXT                 ~0                   get_data.php]
[L14      0x7fa3060920e0 RETURN                  ~0                                                             get_data.php]
[L14      0x7fa306092060 EXT_STMT                                                                               get_data.php]
[L14      0x7fa306092080 INIT_ARRAY<12>          $id                  NEXT                 ~0                   get_data.php]
[L14      0x7fa3060920a0 ADD_ARRAY_ELEMENT       $name                NEXT                 ~0                   get_data.php]
[L14      0x7fa3060920c0 ADD_ARRAY_ELEMENT       $price               NEXT                 ~0                   get_data.php]
[L14      0x7fa3060920e0 RETURN                  ~0                                                             get_data.php]
[L14      0x7fa306058e40 EXT_FCALL_END                                                                          get_data.php]
[L14      0x7fa306058e60 ASSIGN                  $rs                  @8                                        get_data.php]
[L16      0x7fa306058e80 EXT_STMT                                                                               get_data.php]
>00016:     echo json_encode([
 00017:         'data' => $rs,
 00018:     ]);

然后,为自己评估结果:

prompt> ev $rs 
Array
(
    [0] => Array
        (
            [0] => 1
            [1] => Chair
            [2] => 20.0
        )

    [1] => Array
        (
            [0] => 2
            [1] => Shoe
            [2] => 1.0
        )

    [2] => Array
        (
            [0] => 3
            [1] => Candle
            [2] => 0.75
        )

)

这当然很好,但是如果您问我,那不是很实际。

Zenddebugger

我今天要告诉您的第二个工具是Zenddebugger。 Zenddebugger是一种PHP扩展,旨在与ZendStudio结合使用,ZendZend开发和维护。

Zendstudio是基于Eclipse PDT的商业产品。

在加利方面,它得到了PHP背后的公司Zend的支持,因此,如果您使用他们的产品,您就可以选择良好和及时的支持。但是,这是商业产品意味着您可以遇到许可问题。

至于Zenddebugger的细节,可以在Zendstudio外部使用,并且可以很好地工作,但是它的目的是作为Zend软件包的一部分。

xdebug

最后但并非最不重要的是xdebug。 Xdebug也是必须在开发服务器上安装的PHP扩展名,以启用调试功能。它是由Derick Rethans创建的,并且一直保持到今天。

对Xdebug的主要批评曾经是它的安装和配置过程。但是,由于版本3.0,这就是过去,如以下各节所示。

如何安装Xdebug

Xdebug的安装当然取决于您的特定平台。但是,尽管有所不同,但它还是很简单的。如果您使用的是ubuntu或类似的味道,那么您只需要运行以下

sudo apt-get install php-xdebug

如果您没有机会使用软件包管理器,则仍然可以通过PECL轻松安装它:

pecl install xdebug

另外,您可以直接从git获取源代码并自己编译:

git clone git://github.com/xdebug/xdebug.git

如果您想走这条路,至少要有this。信不信由你,这是不久前完成它的唯一方法。

二进制二进制后,您需要通过php.ini文件启用xdebug。您需要更改的确切文件取决于您的特定配置。每个可用的扩展名可能都有一个巨型php.ini或一个。

如果您不知道要为用例选择哪一个,则可以使用一个简单的命令找到答案:

php --ini

您找到了要查找的文件(理想情况下,像xdebug.ini一样),这就是您应该放入的文件:

zend_extension = xdebug

接下来,要使用Xdebug,您需要至少建立执行模式来启用它:

xdebug.mode = develop,debug,trace

现在,重新启动您的Web服务器,您可以开始调试!

关于Xdebug的模式,有很多细微差别。如果您想了解更多信息,请阅读this article并观看this video

示例:为Xdebug配置VSCODE

正如我之前提到的,这个过程有两个方面:

  • 服务器
  • 客户

到目前为止,您已经学会了如何安装和配置服务器,但是如果这就是您的全部,恐怕什么都不会改变。

任何IDE都可以用Xdebug来调试PHP,但是由于它是免费的和受欢迎的,我会
VSCode为例。

如果您还没有安装VSCODE,则可以将其获取here

首次启动IDE时,您会看到这样的屏幕:

Initial VS Code screen.

如果单击调试图标,则会查看以下内容:

A triangle pointing to the right with a bug on top of it

您还会看到这样的屏幕:

A screen prompting to configure the IDE for debugging

要开始调试,您需要创建调试配置。单击“创建启动.json文件”以开始创建一个文件。您会看到类似于以下内容的下拉列表:

Options to select a debugging environment

此时前进的最佳方法是选择“安装扩展”,您将被发送到这样的屏幕:

List of debugging extensions available for VS Code

如果您在@category:debuggers旁边键入php,则将仅将选项列表减少到适用于PHP开发的插件。仍然有很多选择。作为一个很好的经验法则,下载最多的一个可能是您最好的镜头。实际上,该列表由此标准排序,因此,如果您选择第一个标准,则将在正确的轨道上。

单击安装:

Blue button that reads "Install" next to the first extension's description

完成后,您会看到类似于以下内容的屏幕:

PHP Debug configuration screen

从那里,您可以看到服务器端的扩展名和一些安装说明的许多详细信息。

现在,返回到调试屏幕,然后再次单击“创建启动.json文件”。您会看到相同的弹出窗口,但是这次,有一个新选项可用:

Menu showing "PHP" as a new option

选择此选项,您将编辑文件launch.json,它应该像这样:

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Listen for Xdebug",
      "type": "php",
      "request": "launch",
      "port": 9003
    },
    {
      "name": "Launch currently open script",
      "type": "php",
      "request": "launch",
      "program": "${file}",
      "cwd": "${fileDirname}",
      "port": 0,
      "runtimeArgs": [
        "-dxdebug.start_with_request=yes"
      ],
      "env": {
        "XDEBUG_MODE": "debug,develop",
        "XDEBUG_CONFIG": "client_port=${port}"
      }
    },
    {
      "name": "Launch Built-in web server",
      "type": "php",
      "request": "launch",
      "runtimeArgs": [
        "-dxdebug.mode=debug",
        "-dxdebug.start_with_request=yes",
        "-S",
        "localhost:0"
      ],
      "program": "",
      "cwd": "${workspaceRoot}",
      "port": 9003,
      "serverReadyAction": {
        "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started",
        "uriFormat": "http://localhost:%s",
        "action": "openExternally"
      }
    }
  ]
}

此配置现在应该足够了,但是如果您需要更多细微的设置,则可以始终在此屏幕右下方使用“添加配置...”按钮并进行所需的更改。

>

现在我们已经完成了Xdebug的设置,让我们在行动中查看它。

示例用法

在本节中,您将看到Xdebug最典型的两个用法:调试Web应用程序并调试CLI应用程序。出于演示目的,我将使用位于this repository中的代码

使用Xdebug和VS代码调试PHP Web应用程序

首先回到调试屏幕。在左上方,您会看到一个下拉的“运行和调试”:

Run and debug options dropdown

从那里选择“启动内置Web服务器”:

Launch Built-in web server

您会立即被带到浏览器。到目前为止,还没有太多惊喜,对吗?坚持,稍等。事情即将变得令人兴奋。

返回VS代码并打开文件get_data.php

Code for the file get_data.php

如果将鼠标向右移动到行号的左侧,则会看到一个小的红色圆圈出现。在第9行中单击它:

A small red circle next to the number 9

恭喜。您刚刚建立了第一个断点。

要检查一下,请返回您的浏览器并刷新页面。您将自动发送回您的IDE,但是这次,您会找到以下屏幕:

Execution stopped at line 9

发生了什么事?脚本执行被搁置,等待您的操作。如果您切换到浏览器,您会发现屏幕尚未完全绘制。这是Ajax呼叫未完成的结果。现在,您可以花所有的时间在完成此请求之前先检查服务器上的

例如,如果您查看左上面板(变量),您将看到脚本可用的每个变量的值,直到这一点:执行行1-8,但在执行行之前9:

A panel showing the variable contents

就在其上方,您有几个按钮可以继续执行脚本,如您所见:

Buttons to continue the program execution

特别是可用的三个选项:

  • 一步
  • 进入
  • 走出去

使用其中任何一个,您将能够进一步移动执行,从而有效地允许整个脚本的逐步执行。当您向前推进时,左上方面板中显示的变量值将根据程序的动态流进行更新。除非您选择这样做,否则这意味着整个地方都不会更多。

使用Xdebug和VS代码调试PHP CLI应用程序

在调试CLI应用程序时,情况确实与我们刚刚做的事情相似。

假设您要以CLI脚本而不是通过Web服务器运行get_data.php。为了正确调试它,您需要从先前探索的工作流程中更改所需的只是您要使用的调试配置。

转到调试屏幕,然后从配置下拉列表中选择“启动当前打开脚本”:

Dropdown option "Launch currently open script" selected

然后,如果您点击播放按钮,您将立即看到执行方式如何在第9行停止。

运行CLI应用程序时,通过命令行参数将一些参数馈送到它是很常见的。您可以通过在launch.json文件中添加args键来指定此类参数,例如:

{
  "name": "Launch currently open script",
  "type": "php",
  "request": "launch",
  "program": "${file}",
  "cwd": "${fileDirname}",
  "port": 0,
  "runtimeArgs": [
    "-dxdebug.start_with_request=yes"
  ],
  "env": {
    "XDEBUG_MODE": "debug,develop",
    "XDEBUG_CONFIG": "client_port=${port}"
  },
  "args": [
    "argOne"
  ]
}

同样,您可以通过env密钥添加环境变量定义。这可能是具有多种启动配置的原因。

Xdebug的高级用法

到目前为止,我已经向您展示了Xdebug的基本用法。实际上,它是一种相当复杂和强大的工具。关闭之前,我想花点时间告诉您一些您可以利用的一些高级功能:

  • 有条件断点:仅在满足条件时,才能在代码中的某个点中断执行。该条件表示为简单的php布尔表达。
  • 手表:复杂表达式的实时评估。您可以将变量面板视为手表的最简单形式。
  • 分析:执行时间和瓶颈分析。这是一个广泛的话题,应该有自己的文章。我只是想提一下,以便您知道这是可能的。

结论

使用echovar_dumpprint_r之类的旧黑暗时代已经结束。对于任何想要认真对待自己的工艺并将虫子狩猎时间减少到绝对最低限度的PHP开发人员,都有很棒的工具。

问题是您是否会采用一个。