44

我最近在阅读这个线程,关于一些最糟糕的 PHP 实践。在第二个答案中有一个关于使用的小型讨论extract(),我只是想知道所有的愤怒是关于什么的。

我个人使用它来分割给定的数组,例如$_GET$_POST稍后我清理变量的位置,因为它们已方便地为我命名。

这是不好的做法吗?这里有什么风险?您对使用 有什么想法extract()

4

18 回答 18

70

我发现这只是一种不好的做法,因为它可能导致未来的维护者(或几周后的你自己)不知道它们来自哪里的许多变量。考虑这种情况:

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)。

于 2009-05-06T13:04:37.127 回答
42

现在来吧。人们责怪工具而不是用户。

这就像反对,unlink()因为你可以用它删除文件。extract()与其他任何功能一样,请明智且负责任地使用它。但不要声称它本身很糟糕,那只是无知。

于 2009-05-06T16:42:31.470 回答
18

风险是:不要信任来自用户的数据,并且提取到当前符号表中意味着,您的变量可能会被用户提供的内容覆盖。

<?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 被覆盖,导致您的脚本首先删除您的数据然后挂起。

于 2009-05-06T13:07:47.510 回答
9

没有什么问题。否则不会实施。当您将变量传递(分配)给视图时,许多(MVC)框架都会使用它。你只需要小心使用它。在将这些数组传递给 extract() 之前对其进行清理,并确保它不会覆盖您的变量。不要忘记这个函数还接受更多参数!使用第二个和第三个参数,您可以控制发生碰撞时的行为。您可以覆盖、跳过或添加前缀。 http://www.php.net/extract

于 2009-05-06T13:21:01.563 回答
6

如果不小心使用,它可能会使与您一起工作的其他人感到困惑,请考虑:

<?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 是从哪里来的?” 我遇到的问题。

于 2009-05-06T20:31:40.937 回答
6

人们对提取物全力以赴,因为它有可能被滥用。在任何情况下,执行类似 extract($_POST) 的操作都不是一个好主意,即使您知道自己在做什么。但是,当您执行诸如将变量暴露给视图模板或类似的事情时,它确实有它的用途。基本上,只有在您非常确定自己有充分的理由这样做时才使用它,并且如果您想到将诸如 $_POST 之类的疯狂的东西传递给它,请了解如何使用提取类型参数。

于 2009-05-07T02:59:33.470 回答
5

我猜很多人不推荐使用它的原因是提取$_GET$_POST(甚至$_REQUEST)超全局变量在全局命名空间中注册变量,与这些数组中的每个键同名,这基本上是在模拟 REGISTER_GLOBALS = 1。

于 2009-05-06T13:02:28.893 回答
4

如果您在函数中提取,则变量将仅在该范围内可用。这通常在视图中使用。简单的例子:

//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 代码。

于 2009-05-06T17:53:15.047 回答
3

我会让PHP 手册为我说话。

背景:与php.iniextract($_REQUEST)中的设置相同register_globals = On

于 2009-05-06T16:48:59.787 回答
2

永远不要在全局范围内提取($_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>'
));
于 2009-05-06T19:48:55.360 回答
2

正如有人在另一个线程中指出的那样,这是使用 extract 的一种更安全的方法,只允许它提取您指定的变量,而不是提取数组包含的所有内容。

这具有双重目的,即记录从中产生的变量,因此追溯变量不会那么困难。

于 2009-11-09T21:29:46.857 回答
2

只要您以安全的方式使用提取物,它就是安全的。您想要做的是将数组的键过滤为您打算使用的键,如果您的方案需要它们存在,则可能检查所有这些键是否存在。

#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'])
));
于 2016-03-29T16:54:10.427 回答
1

风险与 register_globals 相同。您可以让攻击者在您的脚本中设置变量,只需篡改请求即可。

于 2009-05-06T13:00:24.173 回答
1

每种方法的使用都可能导致某些情况,在这些情况下,它可能成为应用程序的故障点。我个人认为 extract() 不应该用于用户输入(不可预测)和未清理的数据。

甚至 CodeIgniter 核心代码也使用了提取,所以如果数据经过清理和处理,使用该方法一定没有害处。

我在 CodeIgniter 模型中使用了带有 EXTR_IF_EXISTS 开关并限制变量数量的提取,它工作得很好。

于 2015-07-02T10:09:17.780 回答
0

请注意,extract()如果您正在处理用户数据(如请求的结果),那么这是不安全的,因此最好将此函数与标志EXTR_IF_EXISTSEXTR_PREFIX_ALL.

如果使用得当,就可以放心使用

于 2017-05-03T13:55:03.617 回答
0

仅对先前的答案进行一些说明... 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.";
}
于 2018-12-12T07:50:24.180 回答
0

添加到@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']));
于 2021-03-26T12:14:54.787 回答
-2

不再使用 extract() 的另一个很好的理由是 PHP 中有一种使用HHVM的势头,它声称可以使 PHP 快 10 倍。Facebook(谁创造了它)正在使用它,维基百科也在上面,据传 WordPress 正在研究它。

HHVM 不允许 extract()

它仍然是一种阿尔法,所以它不是最大的问题

于 2015-01-28T22:36:03.427 回答