9

I was tasked to update some conditions in an application. I have a data set to be evaluated, and it has been hard-coded in the application following way:

$arr = array(
'a' => 'apple',
'b' => 'orange',
'c' => 1,
'd' => 2,
'e' => 5,
'f' => 'green',
'g' => 'red',
'h' => 'yellow',
)

$res1 = ($arr['a'] == 'apple') ? TRUE : FALSE;
$res2 = (($arr['b'] == $arr['f']) && ($arr['c'] < $arr['d']) ? TRUE : FALSE;
$res3 = (($arr['e'] == '5') && $res2) ?TRUE : FALSE;

and so on...

It is a nightmare to maintain in many places.

What I am essentially looking for is to make some way to pass in query string to evaluate data. For start a simple formula could be defined as an array

$formula = ['a', '=', 'apple'];

function query($formula, $arr) {
    switch ($formula[1]) {
        case '=':
            return ($arr[$formula[0]] == $formula[2]);
        case '!=':
            return ($arr[$formula[0]]!= $formula[2]);
        case '>':
            return ($arr[$formula[0]] > $formula[2]);
        case '<':
            return ($arr[$formula[0]] == $formula[2]);
    }
}

This then could be extended and called recursively

$formula = [['a','=','apple'], 'AND', ['e','<','10']]

but what I am essentially looking for is to store formulas a a string, like:

"((([a]="orange") OR ([c]<"4")) AND ([g]="red"))"

where [ ] would identify array keys

or maybe something like in Excel

"AND(OR(IF('a'='orange'),IF('c'<4)),IF('g'='red'))"

Is there any clean solution to do this? I have an idea how to build whole library for it, maybe in the future.

I don't want to add new conditions to the code every time. They are already all over the application. It would be better to store it in configuration and extend or modify in one place.

Any help much appreciated.

  • 1
    Writing an evaluator is a complex task, but you might take a look at extending ircmaxell's answer to this question to handle and/or and strings as well; or look at the Calculation engine in something like PHPExcel – Mark Baker Nov 23 '15 at 12:14
  • 1
    I guess a "clean" solution would be to set up a class with different functions and include it in several files. To store code as a string and evaluate it later, PHP offers eval(). –  Nov 23 '15 at 12:15
  • 1
    Thank you @MarkBaker, I might have a look at those. Not exactly what I am looking for thou. I don't really want to use eval(), this might be too dangerous, as those formulas will be used by users. This should be more foolproof. – Pawel Jankowski Nov 23 '15 at 17:32
  • 3
    Watch out for creating an "inner-platform effect". It's hard to imagine from what you've shown us so far that you'd end up saving many lines of code. You may not prefer all of PHP's syntax, but it's a standard that any PHP developer (or C++ or Java developer) can understand. So while this seems like a fun thing to try, it might be better to experiment with it first on a smaller-scale side project. If it works there, then consider rolling it into the big project. – John Doe Nov 24 '15 at 00:11
  • 1
    A RPN evaluator would be a simpler task to write and maintain. It is pretty powerful and there are less things you can get wrong. It is less user friendly as a language thought. – Scara95 Nov 28 '15 at 21:22
  • 1
    You are rapidly approaching Greenspun's tenth rule. Your excelesque example is almost Lisp. –  Nov 28 '15 at 22:13
  • Are these rules regularly changing? I think better use of names would be a lot more useful than writing your own programming language. Something like $data_set[$FRUIT] == $desired_fruit. – Andrew Feb 02 '16 at 18:29
  • One rule actually changed quite recently, and since I had no easy way for users to define it, I had to manually edit code to accommodate it. – Pawel Jankowski Feb 03 '16 at 19:20
  • The actual code I needed to implement is checking for certain data and sends an email if conditions are met. In my case: there are certain fields database. If Field1 not equal to "A" or "B" or "C" and Field2 datetime is bigger than [some date] - send an email. These conditions are sometimes quite complex and may change frequently. Users have no coding expertise, but can build logical statements. (like in my answer below) – Pawel Jankowski Feb 03 '16 at 19:31

1 Answers1

1

So this is only quick solution, but works for me right now.

$arr = array('a' => 'red','b' => 'blue');

$formula = ['[a]', '=', 'red'];

If there is [a] in formula it will be treated as array key.

function query($formula, $arr) {

    $query_operator=$formula[1];

    if (is_array($formula[0])) {
        //recursive call
        $query_left = query($formula[0], $arr);
    } else {
        //extracting string between brackets
        preg_match("/\[([^\]]*)\]/", $formula[0], $match);
        $query_left = $match ? $arr[($match[1])] : $formula[0];
    }

    if (is_array($formula[2])) {
        //recursive call
        $query_right = query($formula[2], $arr);
    } else {
        //extracting string between brackets
        preg_match("/\[([^\]]*)\]/", $formula[2], $match);
        $query_right = $match ? $arr[($match[1])] : $formula[2];
    }


    switch ($query_operator) {
        case '=':
            return ($query_left == $query_right);
        case '!=':
            return ($query_left != $query_right);
        case '>':
            return ($query_left > $query_right);
        case '<':
            return ($query_left == $query_right);
        case 'AND':
            return ($query_left && $query_right);
        case 'OR':
            return ($query_left || $query_right);
    }
}

In this solution it will work with formula like:

$formula = [['[a]', '=', 'red'], 'AND', ['[b]', '=', 'blue']];

It is not exactly what I wanted, but does the job, and is not so terrible (I hope). It needs some input checking and error handling, but this is just an example.

gnat
  • 21,213
  • 29
  • 113
  • 291