谨记以下引言
如果 eval() 是答案,那么你几乎肯定会问
错误的问题。——PHP BDFL Rasmus Lerdorf
(PHP 4、PHP 5、PHP 7、PHP 8)
eval — 将字符串当作 PHP 代码进行求值
将给定 code
作为 PHP 来求值。
正在求值的代码继承呼叫 eval() 的行中 变量范围。该行中可用的任何变量都可以在求值代码中读取和修改。但是,定义的所有函数和类都将在全局命名空间中定义。换句话说,编译器将被评估代码视为一个独立 包含 的文件。
eval() 函数结构非常危险,因为它允许执行任意 PHP 代码。因此,不鼓励使用。如果您已仔细验证没有其他选择来使用此结构,请特别注意不要向其中传递任何由用户提供的数据,而不要事先对其进行验证。
code
要评估有效的 PHP 代码。
该代码不能包裹在 PHP 标记的开始和结束位置,即必须传递 'echo "Hi!";'
而不是 '<?php echo "Hi!"; ?>'
。通过使用适当的 PHP 标记仍然可以离开和重新进入 PHP 模式,例如:'echo "In PHP mode!"; ?>In HTML mode!<?php echo "Back in PHP mode!";'
。
除此之外,传递的代码必须是有效的 PHP 代码。其中包括必须使用分号正确终止所有语句。例如,'echo "Hi!"'
将导致解析错误,而 'echo "Hi!";'
将起作用。
return
语句将立即终止代码的求值。
代码将在调用 eval() 的代码范围内执行。因此,在 eval() 调用中定义或更改的任何变量在终止后仍将可见。
eval() 返回 null
,除非评估代码中的 return
被调用,在这种情况下,传递给 return
的值将被返回。从 PHP 7 开始,如果评估代码中存在语法错误,eval() 将引发 ParseError 异常。在 PHP 7 之前,这种情况下 eval() 返回 false
,并且继续正常执行后续代码。无法使用 set_error_handler() 捕捉 eval() 中的语法错误。
示例 1 eval() 示例 - 简单文本合并
<?php
$string = 'cup';
$name = 'coffee';
$str = 'This is a $string with my $name in it.';
echo $str. "\n";
eval("\$str = \"$str\";");
echo $str. "\n";
?>
上面示例将输出
This is a $string with my $name in it. This is a cup with my coffee in it.
注释:
如果评估代码出现致命错误,则整个脚本将退出。
eval() 中的盗梦空间
<pre>
盗梦空间开始
<?php
eval("echo '盗梦空间 1 级...\n'; eval('echo \"盗梦空间 2 级...\n\"; eval(\"echo \'盗梦空间 3 级...\n\'; eval(\'echo \\\"边缘!\\\";\');\");');");
?>
至少在 PHP 7.1+ 中,如果被评估的代码产生致命错误,eval() 会终止脚本。例如
<?php
@eval('$content = (100 - );');
?>
(即使它在手册中,我不确定它在 5.6 中是否这样,但无论如何)
要捕获它,我必须执行以下操作
<?php
try {
eval('$content = (100 - );');
} catch (Throwable $t) {
$content = null;
}
?>
这是我发现捕获错误并隐藏错误事实的唯一方法。
如果你想要允许数学输入,并确保输入是正确的数学而不是某些黑客代码,你可以尝试以下操作
<?php
$test = '2+3*pi';
// 去除空白符号
$test = preg_replace('/\s+/', '', $test);
$number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // 数字格式
$functions = '(?:sinh?|cosh?|tanh?|abs|acosh?|asinh?|atanh?|exp|log10|deg2rad|rad2deg|sqrt|ceil|floor|round)'; // 允许的 PHP 函数
$operators = '[+\/*\^%-]'; // 允许的数学运算符
$regexp = '/^(('.$number.'|'.$functions.'\s*\((?1)+\)|\((?1)+\))(?:'.$operators.'(?2))?)+$/'; // 最终 regexp,主要用于递归模式
if (preg_match($regexp, $q))
{
$test = preg_replace('!pi|π!', 'pi()', $test); // 将 pi 替换为 pi 函数
eval('$result = '.$test.';');
}
else
{
$result = false;
}
?>
我不能完全保证能够阻挡所有恶意代码,也不能阻挡畸形代码,但比下面允许畸形代码如 '2+2+' 导致报错的 matheval 函数要好。
依我之见,这是更好的 eval 替代品
<?php
函数 betterEval($code) {
$tmp = tmpfile ();
$tmpf = stream_get_meta_data ( $tmp );
$tmpf = $tmpf ['uri'];
fwrite ( $tmp, $code );
$ret = include ($tmpf);
fclose ( $tmp );
return $ret;
}
?>
- 为什么?betterEval 遵循正常的 php 开头和结尾标签约定,不必从源代码中剥离 `<?php?>`。如果存在解析错误,它总是抛出 ParseError,而不是返回 false(注意:此问题已在 php 7.0 中针对正常的 eval() 修复)。- 还有一些关于异常回溯的内容
以下代码
<?php
eval( '?> foo <?php' );
?>
不抛出任何错误,但打印开头标签。
在开头标签后面添加空格可以修复此问题
<?php
eval( '?> foo <?php ' );
?>
请注意
<?php
echo eval( '$var = (20 - 5);' ); // 没有任何显示
echo ' someString ' . eval( 'echo $var = 15;' );
// 输出:15 someString
// 或
echo ' someString ' . eval( 'echo $var = 15;' ) . ' otherString ';
// 输出:15 someString otherString
// 或
echo ' someString ' . eval( 'echo $var = 15;' ) . ' otherString ' . '...' .eval( 'echo " __ " . $var = 10;' );
// 输出:15 __ 10 someString otherString ...
?>