我意识到这是一个老问题,但它在今天很重要,我不太喜欢这里的答案。
解决此问题的正确方法是自己实际评估表达式 - 即通过解析表达式,然后逐步评估它,而不是将其转换为 PHP。这可以使用https://en.wikipedia.org/wiki/Shunting-yard_algorithm来完成。
我编写了以下实现,但我还没有测试过。它基于上面的维基百科文章。不支持右关联运算符,因此稍微简化了一些。
// You may need to do a better parsing than this to tokenize your expression.
// In PHP, you could for example use token_get_all()
$formula = explode(' ', 'foo + bar * ( baz / ( foz - bak ) )');;
$queue = array();
$operators = array();
$precedence = array('-' => 2, '+' => 2, '/' => 3, '*' => 3, '^' => 4);
$rightAssoc = array('^');
$variables = array('foo' => $foo, 'bar' => $bar, 'baz' => $baz, 'foz' => $foz, 'bak' => $bak);
foreach($formula as $token) {
if(isset($variables[$token])) {
$queue[] = $variables[$token];
} else if(isset($precedence[$token])) {
// This is an operator
while(
sizeof($operators) > 0 &&
$operators[sizeof($operators)-1] != '(' && (
$precedence[$operators[sizeof($operators)-1]] > $precedence[$token] ||
(
$precedence[$operators[sizeof($operators)-1]] == $precedence[$token] &&
!in_array($operators[sizeof($operators)-1], $rightAssoc)
)
)
) $queue[] = array_pop($operators);
$operators[] = $token;
} else if($token == '(') {
$operators[] = '(';
} else if($token == ')') {
while($operators[sizeof($operators)-1] != '(') {
$queue[] = array_pop($operators);
}
array_pop($operators);
} else if($token == ')') {
while($operators[sizeof($operators)-1] != ')') {
$queue[] = array_pop($operators);
}
if(null === array_pop($operators))
throw new \Exception("Mismatched parentheses");
}
$queue = array_merge($queue, array_reverse($operators));
$stack = array();
foreach($queue as $token) {
if(is_numeric($token)) $stack[] = $token;
else switch($token) {
case '+' :
$stack[] = array_pop($stack) + array_pop($stack);
break;
case '-' :
// Popped variables come in reverse, so...
$stack[] = -array_pop($stack) + array_pop($stack);
break;
case '*' :
$stack[] = array_pop($stack) * array_pop($stack);
break;
case '/' :
$b = array_pop($stack);
$a = array_pop($stack);
if($b == 0)
throw new \Exception("Division by zero");
$stack[] = $a / $b;
break;
}
}
echo "The result from the calculation is ".array_pop($stack)."\n";
在您的特定情况下
即使我更喜欢 Shutting Yard 解决方案 - 如果我仍然决定使用 eval() 版本,我会创建一个 custom_division($leftHandSide, $rightHandSide) 方法,该方法会引发异常。这段代码:
eval("$foo + $bar * ( $baz / ( $foz - $bak ) )");
变成
function custom_division($a, $b) { if($b == 0) throw Exception("Div by 0"); }
eval("$foo + $bar * ( custom_division( $baz, ( $foz - $bak ) )");