在PHP中评估以字符串形式给出的逻辑表达式

6

我有一个对象,它有一个状态属性,例如 state = 'state4'state = 'state2'

现在我还有一个包含可获取的所有状态的数组,state1state8(注意:这些状态没有命名为 stateN。它们有八个不同的名称,如 payment 或 canceled。我只是用 stateN 描述问题)。

除此之外,我有一个逻辑表达式,例如 $expression = !state1||state4&&(!state2||state5)。这是上述描述的代码:

$state = 'state4';
$expression = '!state1||state4&&(!state2||state5)';

现在我想检查逻辑表达式是真还是假。在上面的情况下,它是真的。在以下情况下,它将是错误的:
$state = 'state1';
$expression = state4&&!state2||(!state1||state7);

这个问题怎么才能优雅地解决?

我几年前在我的应用程序中使用eval()解决了这个问题。但为了使代码更具可移植性,我正在将其重建为分层数据库表,以便数据库可以代替PHP为我评估逻辑布尔表达式。但这超出了您设定的“在PHP中”的要求。 - Code4R7
3个回答

2
//Initialize
$state = 'state4';
$expression = '!state1||state4&&(!state2||state5)';

//Adapt to your needs
$pattern='/state\d/';

//Replace
$e=str_replace($state,'true',$expression);
while (preg_match_all($pattern,$e,$matches)
   $e=str_replace($matches[0],'false',$e);

//Eval
eval("\$result=$e;");
echo $result;

编辑:

您对OQ的更新需要进行一些小的工作:

//Initialize
$state = 'payed';
$expression = '!payed||cancelled&&(!whatever||shipped)';

//Adapt to your needs
$possiblestates=array(
   'payed',
   'cancelled',
   'shipped',
   'whatever'
);

//Replace
$e=str_replace($state,'true',$expression);
$e=str_replace($possiblestates,'false',$e);

//Eval
eval("\$result=$e;");
echo $result;

编辑2

有人对eval和评论中的PHP注入表示担忧:表达式和替换完全由应用程序控制,没有用户输入参与。只要这种情况持续存在,eval就是安全的。


2
使用 eval 非常危险,会让您容易受到 PHP 注入攻击的威胁。 - Markus Hedlund
5
没有PHP注入的机会:被 eval 的表达式完全由网站所有者控制: $expression = '!state1||state4&&(!state2||state5)'; - 没有用户输入! - Eugen Rieck

1
我认为如果您将每个表达式建模为一个有根有向无环图(DAG),那么您就有一个可以解决的案例。
我假设这个图是无环的,因为您的最终目标是找到布尔代数运算的结果(如果图中存在循环,那么它将毫无意义)。
但是,即使您的图结构有意义地是循环的,那么您的目标搜索术语仍然应该是循环图,并且它仍然应该有解决方案。
$expression = '!state1||state4&&(!state2||state5)';

在你的例子中,你有一个根节点和两个子DAG。
EXPRESSION as a Rooted DAG:

          EXPRESSION
              |
             AND
        ___/     \___
      OR             OR
     /  \           /  \
! S_1    S_4     ! S_2  S5

您的邻接表是:
expression_adj_list = [
    expression => [ subExp_1, subExp_2 ] ,
    subExp_1 => [ ! S_1, S_4 ],
    subExp_2 => [ ! S_2, S5 ]
]

现在,您可以通过广度优先搜索算法深度优先搜索算法或您自定义的调整算法来遍历此图。
当然,如果这样更适合您并且更容易,您可以多次访问具有键和值的邻接列表。
您将需要一个查找表来教授您的算法。例如,
  • S2 && S5 = 1,
  • S1 or S4 = 0,
  • S3 && S7 = -1(可能会抛出异常)
最终,下面的算法可以解决您的表达式结果。
$adj_list = convert_expression_to_adj_list();
// can also be assigned by a function.
// root is the only node which has no incoming-edge in $adj_list.
$root = 'EXPRESSION';
q[] = $root; //queue to have expression & subexpressions
$results = [];
while ( ! empty(q)) {
    $current = array_shift($q);
    if ( ! in_array($current, $results)) {
        if (isset($adj_list[$current])) { // if has children (sub/expression)
            $children = $adj_list[$current];
            // true if all children are states. false if any child is subexpression.
            $bool = is_calculateable($children);
            if ($bool) {
                $results[$current] = calc($children);
            }
            else {
                array_unshift($q, $current);
            }
            foreach ($children as $child) {
                if (is_subexpresssion($child) && ! in_array($child, $results)) {
                    array_unshift($q, $child);
                }
            }
        }
    }
}
return $results[$root];

这种方法还有一个很大的优势:如果您将表达式的结果保存在数据库中,那么如果一个表达式是根表达式的子表达式,那么您就不需要重新计算它,只需使用数据库中子表达式的结果。通过这种方式,您始终具有两级深度DAG(根和其子级)。

1
我正在使用ExpressionLanguage,但有几种不同的解决方案。
  1. ExpressionLanguage Symfony组件 - https://symfony.com/doc/current/components/expression_language.html

    • 缺点 - 奇怪的数组语法 - array['key']。array.key仅适用于对象
    • 缺点 - 当键未定义时,array['key']会生成通知
    • 优点 - 稳定且维护良好
  2. https://github.com/mossadal/math-parser

  3. https://github.com/optimistex/math-expression

请记住,eval绝对不是一个选项。我们生活在一个不断发展和演变的世界中。曾经被视为安全输入的内容可能变得完全不安全和无法控制。

如果“绝不使用eval”是真的,那么PHP开发人员早就删除了eval - 风险在于在eval中使用用户输入。这里不会发生,因为eval中的所有内容都完全受站点所有者的控制。而且,假设一个常量字符串在将来某个时候会神奇地变得“完全不安全和不受控制”,这也是一种非常冒险的想法。 - Eugen Rieck
需要注意的是,@YourCommonSense 使用他的声望点数将原始答案中的“如果您可以更改语法-您可以使用'php样式语法'和eval”更改为“请记住,在任何情况下都不要使用eval”,已经向未被接受的答案提供了500赏金,并且现在再次这样做。 - Eugen Rieck

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