如何从 Kint emulate/capture 不寻常的 PHP 操作数
How to emulate/capture unusual PHP operands from Kint
背景(介绍)
Kint 是一个 PHP 调试工具,可以更强大地替代 PHP 的 var_dump()
、print_r()
和 debug_backtrace()
。 Kint 的一个不寻常的特性(至少对于 PHP 而言)是能够以操作数的形式使用实时修饰符。以下是手册中关于该功能的说明:
There are a couple of real-time modifiers you can use:
- ~d($var) this call will output in plain text format.
- +d($var) will disregard depth level limits and output everything. Careful, this can hang your browser on large objects!
- !d($var) will expand the output automatically.
- -d($var) will attempt to ob_clean the previous output and flush after printing.
- You can combine modifiers too: ~+d($var)
如果您需要更多信息,有一个与此问题类似的older exiting SO question。
问题
- Kint 如何在不触发 PHP 错误的情况下添加这些操作数?
- 我如何 emulate/capture 使用这些操作数的任何调用?
在不加载 Kint 的情况下,如果您尝试使用这些操作数或创建自己的函数来捕获 Kint 操作数调用,您会得到 Fatal error: Uncaught Error: Unsupported operand types
。
重要提示:我正在使用 kint.phar
文件,我没有使用 composer 或任何类型的 CLI 用法。
我的用例(请不要从问题中分心)
我正在为那些好奇并进一步澄清我的问题的人添加此信息。我真诚地想学习和理解他们是如何做到这一点的,并希望为此得到答案。这个问题不是关于捍卫/批评/不同意我的用例:
为了安全和优化,我正在创建一个假的(空的)Kint class,它会在我的网站处于生产模式时加载。这确保了代码中意外遗留的任何 Kint 调用都不会触发致命错误,永远不会打印出任何内容,并且与加载真正的 Kint 相比使用更少的资源 class.
我知道您可以使用 Kint::$enabled_mode = false;
禁用 Kint,但我们不要关注它。这是我用来伪造 Kint class 的代码。所缺少的只是捕获使用这些非标准操作数的调用:
/**
* 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 ) );
“实时修饰符”都是有效的PHP 一元运算符:
-
是 arithmetic negation
+
是 arithmetic identity
!
是 logical negation
~
是 bitwise not
因此,完全允许使用这些运算符作为函数调用的前缀,只要函数return是运算符通常可以处理的值类型 :
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);
inside d()
函数中的代码通常无法判断其 return 值即将被 ~
运算符修改. (它不应该!这样做完全违反了词法和逻辑范围。)但是,Kint 转义了这个范围,重新解析源文件,找到 ~
,然后使用它作为修改输出的手段函数内的代码生成。
这种技术非常令人困惑,造成巨大的性能损失,违反范围,并提出安全问题......所有这些都是为了提供该语言已有的基本功能 - 函数参数的劣质实现。我永远不会让这个模块靠近我的任何生产服务器。
也就是说,我的建议是忘记尝试用 noops 覆盖 Kint 的运行时功能。而是构建您的管道,以便无法部署 Kint:
- 确保在撰写要求中使用
--dev
。
- 确保在部署脚本中使用
--no-dev
。
- 向您的 bootstrap 或前端控制器添加检查以在加载 Kint 时立即中止。
- 在部署前使用 PHPCS 中的“禁止函数”嗅探,以检测源代码中遗留的 Kint 函数的任何用法。
这里是 Kint 的原作者。
我解决了大多数关于操作数本身的实现细节的问题 in this answer。
现在进入 OP 用例:
我通常会做的是在生产中包含Kint!它是完全安全的 - 或者与任何其他作曲家包一样安全。在过去的 10 多年里,它经常更新。它包含在测试中,仅通过包含它甚至在禁用时调用它不会对性能产生可衡量的影响。
Kint::$enabled_mode = false;
此外,我喜欢用它来记录复杂的变量(其中 ofc 有很大的性能成本来支付冗长),以及无论如何都需要它在生产中出现的类似功能。
如链接答案中所述,操作数是实时调试时常见用例的简写。如果您想推出依赖于操作数的永久功能,我会将其重写为详细版本以提高可读性:
例如
$log = @d($var);
变成
$oldReturnValue = Kint::$return;
Kint::$return = true;
$log = Kint::dump($var);
Kint::$return = $oldReturnValue;
背景(介绍)
Kint 是一个 PHP 调试工具,可以更强大地替代 PHP 的 var_dump()
、print_r()
和 debug_backtrace()
。 Kint 的一个不寻常的特性(至少对于 PHP 而言)是能够以操作数的形式使用实时修饰符。以下是手册中关于该功能的说明:
There are a couple of real-time modifiers you can use:
- ~d($var) this call will output in plain text format.
- +d($var) will disregard depth level limits and output everything. Careful, this can hang your browser on large objects!
- !d($var) will expand the output automatically.
- -d($var) will attempt to ob_clean the previous output and flush after printing.
- You can combine modifiers too: ~+d($var)
如果您需要更多信息,有一个与此问题类似的older exiting SO question。
问题
- Kint 如何在不触发 PHP 错误的情况下添加这些操作数?
- 我如何 emulate/capture 使用这些操作数的任何调用?
在不加载 Kint 的情况下,如果您尝试使用这些操作数或创建自己的函数来捕获 Kint 操作数调用,您会得到 Fatal error: Uncaught Error: Unsupported operand types
。
重要提示:我正在使用 kint.phar
文件,我没有使用 composer 或任何类型的 CLI 用法。
我的用例(请不要从问题中分心)
我正在为那些好奇并进一步澄清我的问题的人添加此信息。我真诚地想学习和理解他们是如何做到这一点的,并希望为此得到答案。这个问题不是关于捍卫/批评/不同意我的用例:
为了安全和优化,我正在创建一个假的(空的)Kint class,它会在我的网站处于生产模式时加载。这确保了代码中意外遗留的任何 Kint 调用都不会触发致命错误,永远不会打印出任何内容,并且与加载真正的 Kint 相比使用更少的资源 class.
我知道您可以使用 Kint::$enabled_mode = false;
禁用 Kint,但我们不要关注它。这是我用来伪造 Kint class 的代码。所缺少的只是捕获使用这些非标准操作数的调用:
/**
* 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 ) );
“实时修饰符”都是有效的PHP 一元运算符:
-
是 arithmetic negation+
是 arithmetic identity!
是 logical negation~
是 bitwise not
因此,完全允许使用这些运算符作为函数调用的前缀,只要函数return是运算符通常可以处理的值类型 :
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);
inside d()
函数中的代码通常无法判断其 return 值即将被 ~
运算符修改. (它不应该!这样做完全违反了词法和逻辑范围。)但是,Kint 转义了这个范围,重新解析源文件,找到 ~
,然后使用它作为修改输出的手段函数内的代码生成。
这种技术非常令人困惑,造成巨大的性能损失,违反范围,并提出安全问题......所有这些都是为了提供该语言已有的基本功能 - 函数参数的劣质实现。我永远不会让这个模块靠近我的任何生产服务器。
也就是说,我的建议是忘记尝试用 noops 覆盖 Kint 的运行时功能。而是构建您的管道,以便无法部署 Kint:
- 确保在撰写要求中使用
--dev
。 - 确保在部署脚本中使用
--no-dev
。 - 向您的 bootstrap 或前端控制器添加检查以在加载 Kint 时立即中止。
- 在部署前使用 PHPCS 中的“禁止函数”嗅探,以检测源代码中遗留的 Kint 函数的任何用法。
这里是 Kint 的原作者。
我解决了大多数关于操作数本身的实现细节的问题 in this answer。
现在进入 OP 用例:
我通常会做的是在生产中包含Kint!它是完全安全的 - 或者与任何其他作曲家包一样安全。在过去的 10 多年里,它经常更新。它包含在测试中,仅通过包含它甚至在禁用时调用它不会对性能产生可衡量的影响。
Kint::$enabled_mode = false;
此外,我喜欢用它来记录复杂的变量(其中 ofc 有很大的性能成本来支付冗长),以及无论如何都需要它在生产中出现的类似功能。
如链接答案中所述,操作数是实时调试时常见用例的简写。如果您想推出依赖于操作数的永久功能,我会将其重写为详细版本以提高可读性:
例如
$log = @d($var);
变成
$oldReturnValue = Kint::$return;
Kint::$return = true;
$log = Kint::dump($var);
Kint::$return = $oldReturnValue;