在 PHP 中,何时使用 eval 函数是不好的?

90

在我多年的PHP开发经验中,我一直听说使用eval()是邪恶的。

考虑下面的代码,使用第二种(更优雅)的选项不是很合理吗?如果不是,请说明原因。

// $type is the result of an SQL statement, e.g.
// SHOW COLUMNS FROM a_table LIKE 'a_column';
// hence you can be pretty sure about the consistency
// of your string.

$type = "enum('a','b','c')";

// option one
$type_1 = preg_replace('#^enum\s*\(\s*\'|\'\s*\)\s*$#', '', $type);
$result = preg_split('#\'\s*,\s*\'#', $type_1);

// option two
eval('$result = '.preg_replace('#^enum#','array', $type).';');

3
"eval永远是有害的,在编写代码时总有更好的方法,特别是自从PHP引入匿名函数以来。在这种情况下,我会使用 $result = array(); preg_replace_callback('#^enum\s*\(\s*\'|\'\s*\)\s*$#', function($m) use($result) { $result[] = $m[1]; }, $type);" - Geoffrey
说实话,我认为 PHP 的主要问题不在于语言本身,而在于使用它的人。对于这个问题,三个正确的答案(thomasrutter、braincracking 和我的)都被投了反对票,却没有人提出任何反对意见。另一方面,有一个回答声称“有时 eval() 是唯一/正确的解决方案”,却没有举例或解释,却因此得到了最高票数... - Francois Bourgeois
21个回答

3

eval函数会将一个字符串当作代码执行,问题在于如果这个字符串存在任何“污染”,那么就会暴露严重的安全威胁。通常这个问题出现在用户输入的代码需要在字符串中运行的情况下,例如PHP或SSI,这些代码将与您的PHP脚本具有相同的权限,并可能被用于获取信息/访问您的服务器。确保用户的输入在提交给eval之前已经得到正确清理是非常棘手的。还有其他问题...其中一些是有争议的。


3

PHP建议你编写代码时,要以一种可以通过call_user_func执行而不是进行显式评估的方式来编写。


3

eval() 的恶劣表现并不是函数本身的问题,而是糟糕的编程习惯。在多个网站上进行动态编程时,有时我无法避免使用它。如果我在一个网站上解析PHP代码,那么我将无法获得我想要的内容,只会得到一个结果!我很高兴 eval() 这样的函数存在,因为它让我的生活变得更加轻松。用户输入?只有糟糕的程序员才会被黑客攻击。我不担心这个问题。


2

这是一个不使用eval来运行从数据库中提取的PHP代码的解决方案。可以让所有范围内的函数和异常生效:

$rowId=1;  //database row id
$code="echo 'hello'; echo '\nThis is a test\n'; echo date(\"Y-m-d\");"; //php code pulled from database

$func="func{$rowId}";

file_put_contents('/tmp/tempFunction.php',"<?php\nfunction $func() {\n global \$rowId;\n$code\n}\n".chr(63).">");

include '/tmp/tempFunction.php';
call_user_func($func);
unlink ('/tmp/tempFunction.php');

基本上,它会在文本文件中创建一个包含代码的唯一函数,将文件包含进来,调用该函数,然后在完成时删除该文件。我使用它来执行每日数据库摄取/同步,其中每个步骤都需要独特的代码来处理过程。这解决了我遇到的所有问题。


这似乎是对答案的回复,请在可以时将其发布为答案。 - rfornal

2

eval之所以被认为是有害的另一个原因是,它无法被PHP字节码缓存(如eAccelerator或ACP)缓存。


2
eval()的本质并不邪恶,它只是由于糟糕的编程才会造成问题。在多个站点上进行动态编程时,有时候无法绕过eval()使用。在一个站点上解析PHP可能会导致无法得到想要的结果,只能得到一个结果!eval()函数的存在使我的生活变得更加轻松愉快,我很高兴。用户输入?只有糟糕的程序员才会受到黑客的攻击。我不担心这个。

我预测你很快就会遇到严重的问题......

老实说,在PHP这样的解释型语言中,eval等过度复杂的函数绝对没有好处。我从来没有见过eval执行的程序功能不能通过其他安全方式执行的情况......

评价eval为“万恶之源”,我完全同意那些认为测试用户输入可以帮助的人。请三思而后行,用户输入可以采用许多不同的形式,而且在我们说话的同时,黑客正在利用你不够关注的功能。在我看来,应该完全避免eval。

我看到了精心制作的滥用eval函数的例子,甚至超过了我的创造力。从安全角度来看,应该尽一切可能避免使用它,甚至我会要求在PHP配置中将其作为“选项”,而非“默认”。


"无论你如何小心谨慎地保护X特性的代码,聪明的黑客总是领先一步……"这种推理可以适用于任何其他技术,而不仅仅是X == 'eval'。因此,对于任何特性X:X就是所有eval的根源......嗯......邪恶之源,所以最好放弃编程(从而在最终还是能够智胜这些愚蠢的黑客的情况下,彻底摆脱他们的困扰)。” - Sz.
"像 eval 这样的过度函数绝对没有任何好处。能用诸如“绝对”之类的词汇的人,要么是新手,要么是糟糕的程序员。" - unity100

1

我曾经经常使用eval(),但是我发现在大多数情况下,你不必使用eval来完成技巧。在PHP中,你可以使用call_user_func()和call_user_func_array()。这足以静态和动态地调用任何方法。

要执行静态调用,请将回调构造为array('class_name', 'method_name'),甚至可以像'class_name::method_name'这样简单的字符串。要执行动态调用,请使用array($object, 'method')风格的回调。

eval()唯一明智的用途是编写自定义编译器。我做了一个,但是eval仍然很邪恶,因为它非常难以调试。最糟糕的是,在evaled代码中发生致命错误会导致调用它的代码崩溃。我使用Parsekit PECL扩展来检查语法,但仍然没有成功-尝试引用未知类并且整个应用程序崩溃。


0
另一种选择
参考: ClosureStream
class ClosureStream
{
    const STREAM_PROTO = 'closure';

    protected static $isRegistered = false;

    protected $content;

    protected $length;

    protected $pointer = 0;

    function stream_open($path, $mode, $options, &$opened_path)
    {
        $this->content = "<?php\nreturn " . substr($path, strlen(static::STREAM_PROTO . '://')) . ";";
        $this->length = strlen($this->content);
        return true;
    }

    public function stream_read($count)
    {
        $value = substr($this->content, $this->pointer, $count);
        $this->pointer += $count;
        return $value;
    }

    public function stream_eof()
    {
        return $this->pointer >= $this->length;
    }

    public function stream_set_option($option, $arg1, $arg2)
    {
        return false;
    }

    public function stream_stat()
    {
        $stat = stat(__FILE__);
        $stat[7] = $stat['size'] = $this->length;
        return $stat;
    }

    public function url_stat($path, $flags)
    {
        $stat = stat(__FILE__);
        $stat[7] = $stat['size'] = $this->length;
        return $stat;
    }

    public function stream_seek($offset, $whence = SEEK_SET)
    {
        $crt = $this->pointer;

        switch ($whence) {
            case SEEK_SET:
                $this->pointer = $offset;
                break;
            case SEEK_CUR:
                $this->pointer += $offset;
                break;
            case SEEK_END:
                $this->pointer = $this->length + $offset;
                break;
        }

        if ($this->pointer < 0 || $this->pointer >= $this->length) {
            $this->pointer = $crt;
            return false;
        }

        return true;
    }

    public function stream_tell()
    {
        return $this->pointer;
    }

    public static function register()
    {
        if (!static::$isRegistered) {
            static::$isRegistered = stream_wrapper_register(static::STREAM_PROTO, __CLASS__);
        }
    }

}
ClosureStream::register();
// Your code here!
$closure=include('closure://function(){echo "hola mundo";}');
$closure();

0

除了安全问题,eval() 无法编译、优化或操作码缓存,因此它始终比普通的 PHP 代码慢得多 -- 慢得多。因此,使用 eval 是非高性能的,尽管这并不意味着它是邪恶的。(goto 是邪恶的,eval 只是不良实践/有异味的代码/丑陋的)


“eval” 的邪恶评级比 “goto” 低?难道今天是反着来的吗? - JLRishe
只是我对 PHP 开发人员实际在 5.3 中实现了 goto 感到不满。 - Xyz
1
嘿,对于“goto”-fobians:有工具,也有使用它们(或不使用)的工匠。当犯错误时,从来不是工具让专业人士看起来像白痴,而是无法正确使用适当的工具。但总是怪罪于工具... - Sz.

0

大多数人会指出,当你处理用户输入时可能会很危险(但是可以处理)。

对我来说,最糟糕的部分是它降低了代码的可维护性

  • 难以调试
  • 难以更新
  • 限制了工具和助手的使用(如IDE)

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