請記住以下引言
如果 eval() 是答案,那麼您幾乎肯定是在問
錯誤的問題。-- Rasmus Lerdorf,PHP 的 BDFL
(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() 呼叫中定義或變更的任何變數在終止後仍可見。
除非在已評估的程式碼中呼叫 return
,否則 eval() 會傳回 null
,在這種情況下,會傳回傳遞給 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 'Inception lvl 1...\n'; eval('echo \"Inception lvl 2...\n\"; eval(\"echo \'Inception lvl 3...\n\'; eval(\'echo \\\"Limbo!\\\";\');\");');");
?>
至少在 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))?)+$/'; // 最終的正規表達式,大量使用遞迴模式
if (preg_match($regexp, $q))
{
$test = preg_replace('!pi|π!', 'pi()', $test); // 將 pi 取代為 pi 函數
eval('$result = '.$test.';');
}
else
{
$result = false;
}
?>
我無法絕對保證這能阻擋所有可能的惡意程式碼,也無法保證能阻擋格式錯誤的程式碼,但這比下面 matheval 函數好,matheval 允許像 '2+2+' 這種格式錯誤的程式碼,會拋出錯誤。
我認為,這是一個更好的 eval 替代方案
<?php
function 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 ' );
?>