eval() альтернатива для получения вывода строки типа «(0 И 1 ИЛИ 1)»

#php #string #eval

#php #строка #eval

Вопрос:

У меня есть данные логического выражения из базы данных в строковом формате —

 "(id1 OR id2) AND (id3 OR id3)"
 

Это может быть любой тип комбинации, поскольку это динамическая строка, основанная на вводе пользователем.
После получения этого из базы данных я заменяю все значения идентификаторов на 0/1 на основе некоторой логики, и это жало становится —

 "(1 OR 1) AND (0 OR 1)"
 

Теперь я хочу запустить эту строку как PHP-код и ожидать ее вывода в виде логического значения. Но проблема в том, что я использовал eval() функцию PHP, которая не подходит по соображениям безопасности.

 $op = "(1 OR 1) AND (0 OR 1)";
$op = eval('return '.$op.';');
 

И единственная причина, по которой мне пришлось использовать eval, — это строковый тип. Если я запускаю это echo (1 OR 1) AND (0 OR 1) , я получаю ожидаемый результат.

Итак, кто-нибудь может мне помочь с этим? Что может быть лучшим способом для этого?

Комментарии:

1. Нет ничего встроенного, что делало бы это, кроме eval() . Вам нужно написать код, который анализирует его и оценивает.

2. Компонент языка выражений Symfony должен быть в состоянии справиться с этим без каких-либо дальнейших манипуляций, если вы можете использовать сторонний пакет.

Ответ №1:

Формат в единственном числе

Если это всегда в одном и том же формате, вы можете использовать регулярное выражение и отформатировать свое собственное выражение…

 if(preg_match("/((d) OR (d)) AND ((d) OR (d))/", $string, $matches))
    var_dump( ($matches[1] || $matches[2]) amp;amp; ($matches[3] || $matches[4]));
 

Множественный формат

Предупреждение, приведенное ниже, не улавливает каждое выражение, если оно неправильно отформатировано. Например, выражение:

 AND 1
 

Приведет к сбою для обоих методов… Вы могли бы обойти это в регулярных выражениях, определив дополнительные правила сопоставления, например:

 Pattern         Replacement

/^s*ANDs*1/   0
/^s*ANDs*0/   0
/^s*ORs*0/    0

/^s*ORs*1/    1
 

Но охватить все означало бы длинный список правил.

Примеры выражений

 $expressions  = [
    "1 AND 0",
    "1 AND 1",
    "0 OR 0",
    "0 OR 1",
    "1 OR 1",
    "0 AND (1 OR 1) AND (0 OR 0) OR 0",
    "(1 OR 1) AND (0 OR 1)",
    "(1 AND 0 OR 1 ) OR (0 OR 1 OR 1) AND 1",
    "(1 OR 0) DELETE EVERYTHING!"
];
 

Способ 1: eval

Хотя люди, как правило, ненавидят использование eval (потому что это потенциально допускает выполнение вредоносного кода), нет причин не использовать его, пока вы очищаете свои входные данные.

Итак, давайте создадим простую функцию:

 function cleanBooleanExpression($string){                
    $string = preg_replace("/OR/i", "O", $string);       // Replace instances of OR with the letter O; i flag makes it case insensitive
    $string = preg_replace("/AND/i", "A", $string);      // Replace instances of AND with the letter A; i flag makes it case insensitive
    $string = preg_replace("/[^OA01() ]/", "", $string);  // Strip all non-legal characters
    $string = preg_replace("/O/", "OR", $string);         // Re-instate OR
    $string = preg_replace("/A/", "AND", $string);        // Re-instate AND
    return $string;                                       // Return the updated string
}
 

Затем вы можете использовать очищенный вывод в eval :

 foreach($expressions as $input){
    $input = cleanBooleanExpression($input);
    eval("var_dump({$input});");
}

/* Output:

bool(false)
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(true)

*/
 

Метод 2: пользовательская функция

В качестве альтернативы, если вы действительно не хотите использовать eval , вы можете использовать регулярные выражения для оценки входных данных:

 function evalBooleanString($string){
    $string = preg_replace('/
        (
        (1)|
        [01]*1[01] |
        [01] 1[01]*|
        (*s*1s*ORs*[01]s*)*|
        (*s*[01]s*ORs*1s*)*|
        (*s*1s*ANDs*1s*)*
        )
        /x', 1, $string);
    $string = preg_replace('/
        (
        ()|
        (0)|
        00 |
        (*s*0s*ORs*0s*)*|
        (*s*[01]s*ANDs*0s*)*|
        (*s*0s*ANDs*[01]s*)*
        )
        /x', 0, $string);

    $string = trim($string);

    if($string === "1" || $string === "0"){
        return (bool)$string;
    }
    return evalBooleanString($string);
}
 

И используйте его как:

 foreach($expressions as $input){
    $input = cleanBooleanExpression($input);
    var_dump(evalBooleanString($input));
}

/* Output:

bool(false)
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(true)

*/
 

Комментарии:

1. Спасибо, но это не всегда в одном и том же формате, это может быть любая комбинация, например «(1 И 0 ИЛИ 1) ИЛИ (0 ИЛИ 1 ИЛИ 1) И 1»

Ответ №2:

Может быть, поможет

 function tmp_function($body)
{
    $tmp_file = tempnam(sys_get_temp_dir(), "tmp_func");
    file_put_contents($tmp_file, "<?php return function() { return $body };");
    $function = include($tmp_file);
    unlink($tmp_file);

    return $function;
}

$op = "(1 OR 1) AND (0 OR 1);";
$call_me = tmp_function($op);
var_dump($call_me());