extract()有什么问题?

47

最近我在阅读这个帖子,其中讨论了一些 PHP 最糟糕的实践。

在第二个回答中,有一个小讨论关于使用extract(),我想知道所有的争议是关于什么的。

我个人使用它来分解给定的数组,如 $_GET$_POST,然后稍后再对变量进行清理,因为它们已经很方便地为我命名了。

这是不好的做法吗?这里面存在什么风险?您对使用extract()的看法是什么?


你自己在 $_GET 或者 $_POST 中使用它的例子可能就是人们反对它的原因。PHP 开发者移除了 register_globals,因为它会导致安全问题,而 extract() 函数则模拟了这种已经被弃用/移除的行为。(如果你有一些旧的代码库不想升级,那么了解这点还是很有好处的。) - TecBrat
你可以查看这个链接:https://dev59.com/Nm435IYBdhLWcg3woRnG如果想要了解更多关于提取规则的信息,可以参考这个链接:https://phptutors.wordpress.com/2013/04/17/php-extract-function-example/ - Hassan Amir Khan
18个回答

72

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

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


当您使用unpack()时,extract()非常有用。 - cleong
1
当解析CSV或Excel文件时,extract函数也非常有用,您可以知道每个列应该包含什么内容,每一行/列作为值数组读入。但是,出于@nickf已经描述的原因,我通常避免使用它。简而言之:伴随着强大的功能就有伟大的责任。 - Sean the Bean

44

现在来说,人们总是责怪工具而不是使用者。

这就像反对unlink()因为你可以用它删除文件一样。extract()就像任何其他函数一样,请明智和负责任地使用它。但是不要声称它本身就是不好的,那只是无知。


22
遗憾的是,有时候明智地使用某物的唯一方式就是不使用它。 - Dan Lugg
8
有时候,明智地运用建议“有时候明智地使用某物的唯一方法就是不使用它”,就是不使用它。@DanLugg - Pacerier
1
@Pacerier 我看到你做了什么。但说真的,extract()varvars(因为它们是相关的)很棘手。它们会在程序中神奇地引入难以跟踪的变量。如果你正在使用它们,为什么不直接使用关联数组呢?这样更加清晰;你只有一个入口点,而不是将局部作用域视为一个入口点。 - Dan Lugg
@DanLugg,因为有时候/很多时候,“你的程序”并不完全由“你的代码”组成。所以现在有一个.php文件是由其他人编写的,你被迫将其包含,并且它要求你在包含之前设置变量$a $b $c到$zz。而这些变量的值将来自另一个来源。你会写一百行变量声明吗?还是你会提取一个数组?创建一个新的作用域并在其中提取所有变量更加清晰,毕竟,没有办法让那些提取的变量泄漏出专门为它创建的作用域。 - Pacerier
@Pacerier 好的,也许吧。但我会质疑那些依赖于 $a$zz 这样命名的变量的代码质量。我的意思是,传统继承下来的代码可能有点乱,但我宁愿用最合理的方式解决这个问题并彻底搞定(例如,将获取值的“捕获”封装到某个容器中,并通过一个明智的 API 公开它们)。神奇的可用变量只会带来麻烦,无论是你还是别人写的代码。就背景而言,我对超全局变量也有类似的看法。 - Dan Lugg
2
@DanLugg,尝试以这种方式“修复”旧代码是极其危险的,特别是当它们不是由您编写时。实际上,我上面提出的解决方案是“修复”旧代码最干净的方法。我们只需要在该函数之上使用extract的额外facet模式即可。不仅是最干净的,而且最便宜的(例如,每月2000美元分解为每小时10美元),并且最可靠。是否有比我上面提到的解决方案更干净更便宜更可靠的替代方案? - Pacerier

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


6
你可以使用EXTR_SKIP来避免这种情况。 - nickf

9

这个函数本身没有问题,否则就不会被实现了。许多(MVC)框架在向视图(Views)传递(分配)变量时使用它。你只需要小心使用,对传递给extract()的数组进行清理并确保它不会覆盖你的变量。不要忘记该函数还接受几个参数! 使用第二个和第三个参数,您可以控制冲突发生时的行为。您可以覆盖、跳过或添加前缀。 http://www.php.net/extract


18
没问题,不然就不会被实现了。goto魔术引号 - Travesty3

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"

这可以用于混淆代码。但是我遇到了“那个变量从哪里来?”的问题,无法解决。


6

人们对于提取(extract)的使用有些过于激动,因为它有被滥用的潜在风险。无论什么情况下,像 extract($_POST) 这样的做法都不是一个好主意,即使你知道自己在做什么。然而,在将变量暴露给视图模板或类似情况下,它确实有其用途。基本上,只有在非常确定你有充分理由这样做,并且了解如何使用提取类型参数时,才应该使用它,以防止传递像 $_POST 这样的疯狂数据。


5

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


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();,以使用特定变量运行代码并获取这些变量的结果,以供旧的php代码使用。


3
我会让PHP手册代替我说话。
背景:在 PHP 中,extract($_REQUEST) 相当于在 php.ini 中设置 register_globals = On

1
别提取超全局变量,就没问题了。正确使用提取是一个强大的工具。 - OIS
2
@OIS:如果您传递其可选参数的参数,我会同意。不过,直接访问您想要的内容似乎是更好的主意。 - Powerlord

2

不要在全局范围内使用extract($_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>'
));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接