我最近在阅读这个线程,关于一些最糟糕的 PHP 实践。在第二个答案中有一个关于使用的小型讨论extract()
,我只是想知道所有的愤怒是关于什么的。
我个人使用它来分割给定的数组,例如$_GET
或$_POST
稍后我清理变量的位置,因为它们已方便地为我命名。
这是不好的做法吗?这里有什么风险?您对使用 有什么想法extract()
?
我发现这只是一种不好的做法,因为它可能导致未来的维护者(或几周后的你自己)不知道它们来自哪里的许多变量。考虑这种情况:
extract($someArray); // could be $_POST or anything
/* snip a dozen or more lines */
echo $someVariable;
是从哪里来$someVariable
的?谁能告诉我?
我没有看到从它们开始的数组中访问变量的问题,所以你真的需要提供一个很好的案例让我extract()
认为它是值得的。如果您真的担心输入一些额外的字符,那么只需执行以下操作:
$a = $someLongNameOfTheVariableArrayIDidntWantToType;
$a['myVariable'];
我认为这里关于它的安全方面的评论有些夸大其词。该函数可以采用第二个参数,该参数实际上使您可以很好地控制新创建的变量,包括不覆盖任何现有变量(EXTR_SKIP
),仅覆盖现有变量(以便您可以创建白名单)(EXTR_IF_EXISTS
),或为变量添加前缀( EXTR_PREFIX_ALL
)。
现在来吧。人们责怪工具而不是用户。
这就像反对,unlink()
因为你可以用它删除文件。extract()
与其他任何功能一样,请明智且负责任地使用它。但不要声称它本身很糟糕,那只是无知。
风险是:不要信任来自用户的数据,并且提取到当前符号表中意味着,您的变量可能会被用户提供的内容覆盖。
<?php
$systemCall = 'ls -lh';
$i = 0;
extract($_GET);
system($systemCall);
do {
print_r($data[$i];
$i++;
} while ($i != 3);
?>
(一个荒谬的例子)
但现在猜测或知道代码的恶意用户调用:
yourscript.php?i=10&systemCall=rm%20-rf
代替
yourscript.php?data[]=a&data[]=b&data[]=c
现在, $systemCall 和 $i 被覆盖,导致您的脚本首先删除您的数据然后挂起。
没有什么问题。否则不会实施。当您将变量传递(分配)给视图时,许多(MVC)框架都会使用它。你只需要小心使用它。在将这些数组传递给 extract() 之前对其进行清理,并确保它不会覆盖您的变量。不要忘记这个函数还接受更多参数!使用第二个和第三个参数,您可以控制发生碰撞时的行为。您可以覆盖、跳过或添加前缀。 http://www.php.net/extract
如果不小心使用,它可能会使与您一起工作的其他人感到困惑,请考虑:
<?php
$array = array('huh' => 'var_dump', 'whatThe' => 'It\'s tricky!', 'iDontGetIt' => 'This Extract Function');
extract($array);
$huh($whatThe, $iDontGetIt);
?>
产量:
string(12) "It's tricky!"
string(21) "This Extract Function"
在混淆中使用会很有用。但我无法克服“那个 var 是从哪里来的?” 我遇到的问题。
人们对提取物全力以赴,因为它有可能被滥用。在任何情况下,执行类似 extract($_POST) 的操作都不是一个好主意,即使您知道自己在做什么。但是,当您执行诸如将变量暴露给视图模板或类似的事情时,它确实有它的用途。基本上,只有在您非常确定自己有充分的理由这样做时才使用它,并且如果您想到将诸如 $_POST 之类的疯狂的东西传递给它,请了解如何使用提取类型参数。
我猜很多人不推荐使用它的原因是提取$_GET
和$_POST
(甚至$_REQUEST
)超全局变量在全局命名空间中注册变量,与这些数组中的每个键同名,这基本上是在模拟 REGISTER_GLOBALS = 1。
如果您在函数中提取,则变量将仅在该范围内可用。这通常在视图中使用。简单的例子:
//View.php
class View {
function render($filename = null) {
if ($filename !== null) {
$this->filename = $filename;
}
unset($filename);
extract($this->variables);
ob_start();
$this->returned = include($this->dir . $this->filename);
return ob_get_clean();
}
}
//test.php
$view = new View;
$view->filename = 'test.phtml';
$view->dir = './';
$view->variables = array('test' => 'tset');
echo $view->render('test.phtml');
var_dump($view->returned);
//test.phtml
<p><?php echo $test; ?></p>
使用一些替代目录,检查文件是否存在并定义变量和方法——您几乎复制了 Zend_View。
您还可以添加$this->outVariables = get_defined_vars(); 在 include 之后运行具有特定变量的代码并获得这些结果以用于旧的 php 代码。
我会让PHP 手册为我说话。
背景:与php.iniextract($_REQUEST)
中的设置相同register_globals = On
永远不要在全局范围内提取($_GET)。除此之外,它还有它的用途,比如调用一个可能(可能)有很多可选参数的函数。
这对 WordPress 开发人员来说应该有点熟悉:
function widget (Array $args = NULL)
{
extract($args);
if($before_widget) echo $before_widget;
// do the widget stuff
if($after_widget) echo $after_widget;
}
widget(array(
'before_widget' => '<div class="widget">',
'after_widget' => '</div>'
));
正如有人在另一个线程中指出的那样,这是使用 extract 的一种更安全的方法,只允许它提取您指定的变量,而不是提取数组包含的所有内容。
这具有双重目的,即记录从中产生的变量,因此追溯变量不会那么困难。
只要您以安全的方式使用提取物,它就是安全的。您想要做的是将数组的键过滤为您打算使用的键,如果您的方案需要它们存在,则可能检查所有这些键是否存在。
#Extract only the specified keys.
$extract=array_intersect_key(
get_data()
,$keys=array_flip(['key1','key2','key3','key4','key5'])
);
#Make sure all the keys exist.
if ($missing=array_keys(array_diff_key($keys,$extract))) {
throw new Exception('Missing variables: '.implode(', ',$missing));
}
#Everything is good to go, you may proceed.
extract($extract);
或者
#If you don't care to check that all keys exist, you could just do this.
extract(array_intersect_key(
get_data()
,array_flip(['key1','key2','key3','key4','key5'])
));
风险与 register_globals 相同。您可以让攻击者在您的脚本中设置变量,只需篡改请求即可。
每种方法的使用都可能导致某些情况,在这些情况下,它可能成为应用程序的故障点。我个人认为 extract() 不应该用于用户输入(不可预测)和未清理的数据。
甚至 CodeIgniter 核心代码也使用了提取,所以如果数据经过清理和处理,使用该方法一定没有害处。
我在 CodeIgniter 模型中使用了带有 EXTR_IF_EXISTS 开关并限制变量数量的提取,它工作得很好。
请注意,extract()
如果您正在处理用户数据(如请求的结果),那么这是不安全的,因此最好将此函数与标志EXTR_IF_EXISTS
和EXTR_PREFIX_ALL
.
如果使用得当,就可以放心使用
仅对先前的答案进行一些说明... extract() 只要您正确过滤输入就没有问题(正如其他人所说);否则你可能会遇到像这样的巨大安全问题:
<?php
// http://foobar.doo?isLoggedIn=1
$isLoggedIn = (new AdminLogin())->isLoggedIn(); // Let's assume this returns FALSE
extract($_GET);
if ($isLoggedIn) {
echo "Okay, Houston, we've had a problem here.";
} else {
echo "This is Houston. Say again, please.";
}
添加到@user10306 我想出了一个简单的函数来安全地加载 $_POST 变量。它只提取指定的变量
function diffArr(array $input, array $allowed) {
foreach ($input as $key => $v)
if(!in_array($key, $allowed)) unset($input[$key]);
return $input;
}
extract(diffArr($_POST, ['title', 'tagline', 'content', 'user_id']));
不再使用 extract() 的另一个很好的理由是 PHP 中有一种使用HHVM的势头,它声称可以使 PHP 快 10 倍。Facebook(谁创造了它)正在使用它,维基百科也在上面,据传 WordPress 正在研究它。
它仍然是一种阿尔法,所以它不是最大的问题