首页 > 解决方案 > 如何从 Kint 模拟/捕获不寻常的 PHP 操作数

问题描述

背景(介绍)
Kint是一种 PHP 调试工具,可作为 PHP 的var_dump()、、print_r()debug_backtrace(). Kint 的一个不寻常的——至少对于 PHP 来说——的特性是能够以操作数的形式使用实时修饰符。以下是手册对该功能的说明:

您可以使用几个实时修改器:

  • ~d($var) 此调用将以纯文本格式输出。
  • +d($var) 将忽略深度级别限制并输出所有内容。小心,这会使您的浏览器挂在大型物体上!
  • !d($var) 将自动扩展输出。
  • -d($var) 将尝试 ob_clean 先前的输出并在打印后刷新。
  • 你也可以组合修饰符:~+d($var)

如果您需要更多信息,有一个与此问题类似的较旧的现有 SO 问题。


问题

  1. Kint 如何在不触发 PHP 错误的情况下添加这些操作数?
  2. 如何模拟/捕获使用这些操作数的任何调用?

如果您尝试使用这些操作数或创建自己的函数来捕获您获得的 Kint 操作数调用,则无需加载 Kint Fatal error: Uncaught Error: Unsupported operand types

重要提示:我正在使用该kint.phar文件,并且没有使用 composer 或任何类型的 CLI 用法。



我的用例(请不要从问题中分心)
我正在为那些好奇的人添加此信息并进一步澄清我的问题。我真诚地想了解和了解他们是如何做到这一点的,并希望能得到答案。这个问题不是关于捍卫/批评/不同意我的用例:

为了安全和优化,我正在创建一个假的(空)Kint 类,当我的站点处于生产模式时加载该类。这确保了任何意外遗留在代码中的 Kint 调用都不会触发致命错误,永远不会打印出任何内容,并且与加载真正的 Kint 类相比,使用的资源更少。

我知道你可以禁用 Kint ,Kint::$enabled_mode = false;但不要专注于此。这是我用来伪造 Kint 类的代码。所缺少的只是捕获使用这些非标准操作数的调用:

/**
 * Fake class.
 */
class Kint {

    const STATIC_BLACKHOLE = '';

    public static $enabled_mode = false;

    public function blackhole( $a ) {
        return;
    }

    public function __call( $m, $a ) {
        return call_user_func_array( array( $this, $this->blackhole ), $a );
    }

    public static function __callStatic( $m, $a ) {
        return self::STATIC_BLACKHOLE;
    }

}
$kint = new Kint();

// Alias of Kint::dump().

/**
 * Fake function to catch d().
 *
 * @return void
 */
function d() {
    return;
}

// Kint::dump basic mode.

/**
 * Fake function to catch s().
 *
 * @return void
 */
function s() {
    return;
}

\define( 'KINT_DIR', '/classes/Kint' );
\define( 'KINT_WIN', DIRECTORY_SEPARATOR !== '/' );
\define( 'KINT_PHP70', ( \version_compare( PHP_VERSION, '7.0' ) >= 0 ) );
\define( 'KINT_PHP71', ( \version_compare( PHP_VERSION, '7.1' ) >= 0 ) );
\define( 'KINT_PHP72', ( \version_compare( PHP_VERSION, '7.2' ) >= 0 ) );
\define( 'KINT_PHP73', ( \version_compare( PHP_VERSION, '7.3' ) >= 0 ) );
\define( 'KINT_PHP74', ( \version_compare( PHP_VERSION, '7.4' ) >= 0 ) );
\define( 'KINT_PHP80', ( \version_compare( PHP_VERSION, '8.0' ) >= 0 ) );

标签: phpoperandskint

解决方案


“实时修饰符”都是有效的 PHP 一元运算符:

因此,完全可以使用这些运算符为函数调用添加前缀,只要函数返回运算符通常可以处理的值类型

function foo() {
    return 0;
}

// All of these work just fine, and generate no errors:
-foo();
+foo();
!foo();
~foo();

据我一眼看出,Kint 在其函数中所做的是用于debug_backtrace()获取调用函数的源文件。然后它打开该文件,读取它,定位调用行,并解析它以确定哪些(如果有的话)“实时修饰符”用于作为函数调用的前缀。即,考虑来源:

function d($var) {
    // dump $var
}

~d($GLOBALS);

函数内部的代码d()通常无法判断出它的返回值即将被~操作员修改。(而且它不应该!这样做完全违反了词法和逻辑范围。)但是,Kint 转义了这个范围,重新解析源文件,找到~,然后使用它作为修改输出的方法函数生成。

这种技术非常令人困惑,会造成巨大的性能损失,违反范围,并提出安全问题......所有这些都是为了提供该语言已经具有的基本功能的劣质实现 - 函数参数。我永远不会让这个模块靠近我的任何生产服务器。

也就是说,我的建议是忘记尝试用 noops 覆盖 Kint 的运行时功能。而是构建您的管道,以便无法部署 Kint:

  • 确保--dev在您的撰写要求中使用。
  • 确保--no-dev在部署脚本中使用。
  • 如果加载了 Kint,请向您的引导程序或前端控制器添加一个检查以立即中止。
  • 在部署之前使用 PHPCS 中的“禁止函数”嗅探,以检测源中遗留的 Kint 函数的任何用法。

推荐阅读