PHP get_define_vars() 不会在函数内打印超全局变量

kch*_*son 2 php

我正在调试一些遗留代码,并且想要使用一个预构建的函数,该函数本质上是get_defined_vars().

直接在调用文件中运行此代码会按预期打印变量数组:

print_r(get_defined_vars());
Run Code Online (Sandbox Code Playgroud)

但是,将其包装在我的函数的简化版本中会打印一个空数组:

function debugAllVars() {
    print_r(get_defined_vars());
}
debugAllVars();
Run Code Online (Sandbox Code Playgroud)

无论范围如何,我都希望“超全局”变量$_POST出现在输出中。

为什么输出完全为空?

IMS*_*SoP 5

get_defined_vars()打印调用范围的“符号表”中的所有变量。当您尝试将其包装为 时debugAllVars,您引入了一个新的作用域,其中有一个新的符号表。

对于这样的独立函数,符号表包括:

  • 函数的参数
  • global使用关键字导入到当前作用域的任何全局变量
  • 使用关键字在当前作用域中声明的任何静态变量static(即使未赋值)
  • 通过赋值隐式声明的任何变量
  • 通过引用隐式声明的任何变量(例如,如果尚未定义,$foo = &$bar则将隐式声明;不会)$bar$foo = $bar

值得注意的是,此列表不包括超全局变量,例如$_GET$_POST$GLOBALS。如果您get_defined_vars()在全局范围内(即在任何函数之外)运行,您将看到这些存在于符号表中,这也是魔术变量$GLOBALS所指向的内容。那么,为什么它们没有出现在每个范围中,如果没有,我们该如何使用它们呢?

为此,我们需要深入研究实现的内部结构,其中这些变量被称为“自动全局变量”而不是“超全局变量”。

原因的答案是性能:“自动全局”的天真实现将表现为每个函数自动在顶部读取一行global $_GET, $_POST, ...;。然而,这意味着在每个函数运行之前将所有这些变量复制到符号表中,即使它们没有被使用。

因此,这些变量在编译器中是特殊大小写的,同时将 PHP 代码转换为执行代码的 VM 使用的内部“操作码”。

使用源代码浏览器,我们可以看到它是如何工作的。

关键函数zend_is_auto_global位于zend_compile.c(取自当前的masterPHP 7.2):

zend_bool zend_is_auto_global(zend_string *name) /* {{{ */
{
    zend_auto_global *auto_global;

    if ((auto_global = zend_hash_find_ptr(CG(auto_globals), name)) != NULL) {
        if (auto_global->armed) {
            auto_global->armed = auto_global->auto_global_callback(auto_global->name);
        }
        return 1;
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这里,name是变量的名称,CG表示“编译器全局变量”,因此该函数的主要工作是说“如果给定的变量名位于名为 的编译器全局哈希中auto_globals,则返回 1”。额外的调用auto_global_callback允许变量“延迟加载”,并且仅在第一次引用时才填充。

该函数的主要用法似乎是这个条件,在zend_compile_simple_var_no_cv

if (name_node.op_type == IS_CONST && 
    zend_is_auto_global(Z_STR(name_node.u.constant))) {

    opline->extended_value = ZEND_FETCH_GLOBAL;
} else {
    opline->extended_value = ZEND_FETCH_LOCAL;
}
Run Code Online (Sandbox Code Playgroud)

换句话说,如果您引用的变量名位于超全局列表中,编译器会将操作码切换到不同的模式,以便在执行时,它会在全局而不是在本地查找该变量。