Evaluación de conjunto de datos con una fórmula de cadena en php

9

Me encargaron actualizar algunas condiciones en una aplicación. Tengo un conjunto de datos para evaluar, y ha sido codificado en la aplicación de la siguiente manera:

$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;

y así...

Es una pesadilla para mantener en muchos lugares.

Lo que esencialmente estoy buscando es hacer alguna forma de pasar la cadena de consulta para evaluar los datos. Para comenzar, una fórmula simple podría definirse como una matriz

$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]);
    }
}

Esto podría extenderse y llamarse recursivamente

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

pero lo que estoy buscando esencialmente es almacenar fórmulas a una cadena, como:

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

donde [] identificaría las claves de la matriz

o tal vez algo como en Excel

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

¿Hay alguna solución limpia para hacer esto? Tengo una idea de cómo construir una biblioteca completa para ello, tal vez en el futuro.

No quiero agregar nuevas condiciones al código cada vez. Ya están en toda la aplicación. Sería mejor almacenarlo en la configuración y extenderlo o modificarlo en un solo lugar.

Cualquier ayuda muy apreciada.

Pawel Jankowski
fuente
1
Escribir un evaluador es una tarea compleja, pero puede echarle un vistazo a la extensión de la respuesta de ircmaxell a esta pregunta para manejar también las cadenas; o mira el motor de cálculo en algo como PHPExcel
Mark Baker
1
Supongo que una solución "limpia" sería configurar una clase con diferentes funciones e incluirla en varios archivos. Para almacenar el código como una cadena y evaluarlo más tarde, PHP ofrece eval().
1
Gracias @MarkBaker, podría echarles un vistazo. No es exactamente lo que estoy buscando. Realmente no quiero usar eval (), esto podría ser demasiado peligroso, ya que los usuarios usarán esas fórmulas. Esto debería ser más infalible.
Pawel Jankowski el
3
Cuidado con la creación de un "efecto de plataforma interna". Es difícil imaginar lo que nos ha mostrado hasta ahora que terminaría guardando muchas líneas de código. Es posible que no prefiera toda la sintaxis de PHP, pero es un estándar que cualquier desarrollador de PHP (o desarrollador de C ++ o Java) puede entender. Entonces, aunque esto parezca algo divertido de probar, podría ser mejor experimentar primero con un proyecto paralelo a menor escala. Si funciona allí, entonces considere incluirlo en el gran proyecto.
John Doe
1
Un evaluador RPN sería una tarea más simple de escribir y mantener. Es bastante poderoso y hay menos cosas en las que te puedes equivocar. Es menos fácil de usar como un lenguaje pensado.
Scara95

Respuestas:

1

Así que esta es solo una solución rápida, pero funciona para mí en este momento.

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

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

Si hay [a] en la fórmula, se tratará como clave de matriz.

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);
    }
}

En esta solución funcionará con fórmulas como:

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

No es exactamente lo que quería, pero hace el trabajo, y no es tan terrible (espero). Necesita un poco de verificación de entrada y manejo de errores, pero esto es solo un ejemplo.

Pawel Jankowski
fuente