我知道直接在调用者范围内设置变量可能不是一个好主意。然而,PHPextract()
函数正是这样做的!我想编写自己的版本,extract()
但无法弄清楚如何实际设置调用者中的变量。有任何想法吗?我最接近的是修改调用者的args
using debug_backtrace()
,但这并不完全相同......
4 回答
您不能修改父作用域中的局部变量 - extract() 使用的方法不会被 PHP 公开。
此外,您从 debug_stacktrace() 返回的内容并没有神奇地链接到真正的堆栈。您无法修改它,希望您的修改生效!
您只能在 PHP 扩展中执行此操作。如果调用内部 PHP 函数,它将不会在新的 PHP 范围内运行(即不会创建新的符号表)。因此,您可以通过更改全局EG(active_symbol_table)
.
基本上,函数的核心会做类似 dos 的事情extract
,其核心是:
if (!EG(active_symbol_table)) {
zend_rebuild_symbol_table(TSRMLS_C);
}
//loop through the given array
ZEND_SET_SYMBOL_WITH_LENGTH(EG(active_symbol_table),
Z_STRVAL(final_name), Z_STRLEN(final_name) + 1, data, 1, 0);
但是,有一些细微差别。请参阅 的实现extract
,但请记住,执行您想要的功能的功能不需要那么复杂;中的大部分代码extract
用于处理它接受的几个选项。
您可以滥用 $GLOBALS 范围从函数的调用者读取和写入变量。请参见下面的示例函数,该函数从调用者范围读取和写入变量。
是的,我知道滥用 $GLOBAL 范围很脏,但是,嘿,我们是来解决问题的,不是吗?:)
function set_first_name($firstname) {
/* check if $firstname is defined in caller */
if(array_key_exists('firstname', $GLOBALS)) {
$firstname_was = $GLOBALS['firstname'];
} else {
$firstname_was = 'undefined';
}
/* set $firstname in caller */
$GLOBALS['firstname'] = $firstname;
/* show onscreen confirmation for debugging */
echo '<br>firstname was ' . $firstname_was . ' and now is: ' . $firstname;
}
set_first_name('John');
set_first_name('Michael');
该函数返回以下输出:
<br>firstname was undefined and now is: John
<br>firstname was John and now is: Michael
这取决于您需要执行此操作的程度。如果只是为了源美,那就另辟蹊径。如果出于某种原因,您真的需要弄乱父范围,总有办法。
解决方案 1
最安全的方法是实际使用 extract 本身来完成这项工作,因为它知道诀窍。假设您想创建一个函数来提取数组元素但所有名称都向后 - 很奇怪!-,让我们通过一个简单的数组到数组的转换来做到这一点:
function backwardNames($x) {
$out = [];
foreach($x as $key=>$val) {
$rev = strrev($key);
$out[$rev] = $val;
}
return $out;
}
extract(backwardNames($myArray));
这里没有魔法。
解决方案 2
如果您需要的不仅仅是extract,请使用eval和var_export。是的,我知道我知道,请大家冷静下来。不, eval 不是邪恶的。Eval 是一种电动工具,如果不小心使用它可能会很危险 - 所以要小心使用。(如果您只评估由 var_export 生成的内容,则不会出错 - 即使您将来自不受信任来源的值放入数组中,它也不会为入侵提供任何途径。数组元素表现良好。)
function makeMyVariables() {
$vars = [
"a" => 4,
"b" => 5,
];
$out = var_export($vars,1);
$out = "extract(".$out.");";
return $out;
}
eval(makeMyVariables()); // this is how you call it
// now $a is 4, $b is 5
这几乎是一样的,只是你可以在 eval 中做更多的事情。当然,它的速度要慢得多。
然而,事实上,没有办法通过一次调用来做到这一点。