PHP 验证约束列表

PHP validate a list of constraints

我有一个数组 <> 对我从用户那里得到的变量名称的约束:

$constraints = array('1<x','x<5','y>4');

其中 $x$y 在相关范围内定义。

我想验证 所有 约束是否成立(return truefalse

如何不使用 eval 来执行此操作?

很多人确实知道 php 中的 $ 符号实际上是一个计算变量的运算符。

$total_expressions = true;
foreach($constraints as $c) {
  #parse the expression in to the number, and the variable
  $parts = explode(">",str_replace("<",">",$c));
  $i = is_numeric($parts[0]) ? 0 : 1 ;
  $n = $parts[$i];
  $v = $parts[1-$i];
  # At this stage, $v is the variable name, and $n is the number
  # This line is kinda hard coded to only ">" or "<", but you get the idea
  $expression = strpos(">",$c) && $i ? $$v > $n : $$v < $n;
  $total_expressions = $total_expressions && $expression;
  if (!$total_expressions)
      break;
}

$total_expressions 仅当所有约束都成立时才为真。

如果您只想知道所有 约束是否有效,您可以将它们传递给执行检查的函数。它可以使用 foreach 循环逐一检查每个约束。如果当前约束无效,那么它将 return false 并停止检查。否则,如果它到达循环的末尾,它将 return true。变量的值作为两个数组传递到函数中,如 str_replace().

中所用
function validate($constraints, $search, $replace) {

    foreach ($constraints as $constraint) {

        // replace variables in string with submitted values
        $constraint = str_replace($search, $replace, $constraint);

        if (strpos($constraint, '<') !== false) {
            // extract parts from less than constraint
            list($a, $b) = explode('<', $constraint, 2);
            if ($a >= $b) {
                // $a is greater than or equal to $b i.e. not less than
                return false;
            }
        } else if (strpos($constraint, '>') !== false) {
            // extract parts from greater than constraint
            list($a, $b) = explode('>', $constraint, 2);
            if ($a <= $b) {
                // $a is less than or equal to $b i.e. not greater than
                return false;
            }
        }
    }
    // no invalid constraints were found...
    return true;
}

然后您可以使用它来检查您的 $constraints 数组,

// variables to search for
$search = ['x', 'y'];

// variable replacements
$replace = [5, 2];

// constraints array
$constraints = array('4<x','x<6','y>1');

// run the function
var_dump(validate($constraints, $search, $replace));

该函数假定数据完全按照您的描述传递给它。如果数据格式可能不同,您可能需要添加一些检查。

我在这里炮制了部分答案。它不循环,但支持五种不同的比较运算符。

function lt($p1, $p2) {
    return ($p1 < $p2);
}
function le($p1, $p2) {
    return ($p1 <= $p2);
}

function gt($p1, $p2) {
    return ($p1 > $p2);
}
function ge($p1, $p2) {
    return ($p1 >= $p2);
}
function eq($p1, $pw) {
    return ($p1 == $p2);
}

function apply_rule($rule, $x, $y) {
    $matches = NULL;
    if (!preg_match('/^([a-zA-Z0-9]+)(<|>|=|<=|>=)([a-zA-Z0-9]+)$/', $rule, $matches)) {
        throw new Exception("Invalid rule: " . $rule);
    }
    //var_dump($matches);
    $p1 = $matches[1];
    $operator = $matches[2];
    $p2 = $matches[3];

    // check if first param is a variable
    if (preg_match('/([a-zA-Z]+)/', $p1)) {
        $p1 = $$p1;
    }
    // check if second param is a variable
    if (preg_match('/([a-zA-Z]+)/', $p2)) {
        $p2 = $$p2;
    }

    switch($operator) {
        case "<":
            return lt($p1, $p2);
        case "<=":
            return le($p1, $p2);
        case ">":
            return gt($p1, $p2);
        case ">=":
            return ge($p1, $p2);
        case "=":
            return eq($p1, $p2);
    }
}

var_dump(apply_rule("x>=10", 10, 20));

如果您只需要计算简单的表达式,并且您事先知道变量的数量和名称,那么您可以编写一个简单的解析器:

/**
 * Parse and evaluate a simple comparison.
 *
 * @param string  $condition e.g. 'x<4'
 * @param integer $x         the value of 'x'
 * @param integer $y         the value of 'y'
 */
function compare($condition, $x, $y)
{
    // Verify that the condition uses the format accepted by this function
    // Also extract the pieces in $m
    $m = array();
    if (! preg_match('/^(x|y|\d+)([<>])(x|y|\d+)$/', $condition, $m)) {
        throw new RuntimeException("Cannot parse the condition");
    }

    // $m[0] is the entire string that matched the expression
    // $m[1] and $m[3] are the operands (the first and the third groups)
    // $m[2] is the operator (the second group in the regex)

    // Replace the variables with their values in $m[1] and $m[3]
    foreach (array(1, 3) as $i) {
        switch ($m[$i]) {
        case 'x':
            $m[$i] = $x;
            break;
        case 'y':
            $m[$i] = $y;
            break;
        default:
            $m[$i] = (int)$m[$i];
            break;
        }
    }

    // Compare the values, return a boolean
    return ($m[2] == '<') ? ($m[1] < $m[3]) : ($m[1] > $m[3]);
}

// A simple test
$x = 7;
$y = 3;
echo('$x='.$x."\n");
echo('$y='.$y."\n");

echo('1<x: '.(compare('1<x', $x, $y) ? 'TRUE' : 'FALSE')."\n");
echo('x<5: '.(compare('x<5', $x, $y) ? 'TRUE' : 'FALSE')."\n");
echo('y>4: '.(compare('y>4', $x, $y) ? 'TRUE' : 'FALSE')."\n");

该代码适用于整数值。要使其与浮点值一起使用,只需在 switch 语句的 default 分支上将 (int) 替换为 (double)

正则表达式:

^               # match the beginning of the string
(               # start capturing group #1
  x               # match the 'x' character 
  |y              # ... OR (|) the 'y' character
  |\d+            # ... OR (|) a sequence of 1 or more (+) digits (\d)
)               # end capturing group #1     <-- find the captured value in $m[1]
(               # start capturing group #2
  [               # match any character from the range
    <>              # match '<' or '>'
  ]               # end range
)               # end capturing group #2     <-- find the captured value in $m[2]
(x|y|\d+)       # the capturing group #3, identical to group #1
$               # match the end of the string

通过简单的更改,上面的代码可以调整为还允许 <=>==(更改正则表达式)或事先不知道的变量列表(传递由名称索引的数组中的变量,使用 $m[$i] 查找数组中的值)。