如何从生产 PHP 代码中获取正确的调试上下文? print_r vs var_export vs var_dump 极端情况

How to get proper debug context from production PHP code? print_r vs var_export vs var_dump corner cases

要求

我们在网络服务器上有生产 PHP 代码 运行。在某些情况下,我们希望 丰富调试输出 (例如 error.log) 使用上下文 信息。上下文可以有任意字段和结构。

我们的目标是找到一个通用的调试生成函数,它不会在任何极端情况下生成警告。如果只是部分输出是可以接受的。

TL;DR

所有三个 PHP 标准函数都会在某些上下文中引起警告:

问题

有没有办法return将任意对象的上下文作为人类可读的字符串?

测试用例

// Dealing with recursion:
$recObj = new stdClass();
$recObj->recursion = $recObj;

print_r   ($recObj);  // works
var_export($recObj);  // throws: "Warning: var_export does not handle circular references"
var_dump  ($recObj);  // works


// dealing with mysqli
$mysqli = mysqli_connect('mysql', 'root', 'myPass', 'mysql');
print_r   ($mysqli);  // works
var_export($mysqli);  // works as in "does not throw warnings" but all values are null
var_dump  ($mysqli);  // works

// Try again with closed connection
mysqli_close($mysqli);
print_r   ($mysqli);  // throws: "Warning: print_r(): Couldn't fetch mysqli"
var_export($mysqli);  // works (again all values are null)
var_dump  ($mysqli);  // throws: Warning: var_dump(): Couldn't fetch mysqli

根据关闭的 mysqli 连接的相关性:如果您在错误处理程序或已注册的关闭函数(这是一个很好的位置)中进行上下文打印,一旦您到达该处理程序,mysqli 对象将已经自动关闭连接。

如您所见,none 的内置输出方法给出了能够 return 任何上下文的预期结果。

考虑的选项

  1. 使用@notation 可以抑制警告。但是,已注册的错误处理程序和关闭函数仍然会因错误而被调用,并且需要自定义逻辑来忽略该特定错误。这可能会隐藏真正的错误,如果处理像 sentry.io
  2. 这样的第 3 方错误跟踪系统,也会变得很烦人
  3. serialize() 函数在任何情况下都不会产生警告,但它缺乏人类可读性。
  4. json_encode() 函数比 serialize 更易读,但它在递归测试用例中 return 没有任何意义....

@BlackXero 的评论是正确的,对我有用。

我没有找到内置打印功能,它在包含具有关闭连接的 mysqli 对象时不会导致错误/警告(我实际上将其归类为错误/不需要的行为)。

我们最终通过

添加了Symfony Vardumper
composer require symfony/var-dumper

并编写一个小辅助函数来显示来自 cli 脚本或浏览器的正确且漂亮的输出:

use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;

class Debug {
    /**
     * Method provides a function which can handle all our corner-cases for producing 
     * debug output.
     *
     * The corner cases are:
     * - objects with recursion
     * - mysqli references (also to closed connections in error handling)
     *
     * The returned result will be:
     *  - formatted for CLI if the script is run from cli
     *  - HTML formatted otherwise
     *      - The HTML formatted output is collapsed by default. Use CTRL-left click to 
     *        expand/collapse all children
     *  - You can force html|cli formatting using the optional third parameter
     *
     * Uses the Symfony VarDumper composer module.
     *
     * @see https://github.com/symfony/var-dumper
     * @see 
     * @param mixed       $val    - variable to be dumped
     * @param bool        $return - if true, will return the result as string
     * @param string|null $format null|cli|html for forcing output format
     * @return bool|string
     */
    public static function varDump($val, $return = false, $format = null) {
        if (is_null($format)) {
            $format = php_sapi_name() == 'cli' ? 'cli' : 'html';
        }
        $cloner = new VarCloner();
        if ($format === 'cli') {
            $dumper = new CliDumper();
        } else {
            $dumper = new HtmlDumper();
        }

        $output = fopen('php://memory', 'r+b');
        $dumper->dump($cloner->cloneVar($val), $output);
        $res = stream_get_contents($output, -1, 0);

        if ($return) {
            return $res;
        } else {
            echo $res;
            return true;
        }
    }
}

那个方法

  • 可以处理我传递给它的所有输入而不会出现错误或警告
  • 格式适合 CLI 和 HTML
  • return 将结果作为字符串转发给外部错误跟踪系统,如 sentry

所以它符合我在最初问题中要求的所有方框。

感谢@BlackXero 正确理解问题并为我指明了正确的方向。