2013-07-26 6 views
5

Sto creando un validatore che sarà in grado di gestire condizioni complesse consentendo allo sviluppatore di utilizzare un'istruzione condizionale nelle regole delle condizioni.Provare a non utilizzare eval() in php per valutare una stringa con un'istruzione di condizione

Prendete questo esempio con una serie di regole:

... 
"element_name":{ 
    "required": "conditional", 
    "conditions" : { 
     "requirements" : "(4 < 5)", 
     "requirements" : "('something' == 'something_else')" 
    } 
} 
... 

ciò che il PHP poi fare è un ciclo tra quelli requirements e valutarli come codice per restituire un valore booleano che determinerà se è necessario o meno l'elemento .

Il problema con l'utilizzo della funzione eval() è abbastanza ovvio. Quindi mi chiedo, dato che le dichiarazioni condizione sono quello che sarà consentito solo, c'è un modo più sicuro di fare questo rispetto:

$result = eval(element_name->requirements[0]); 

Grazie ragazzi.

---- ----- AGGIORNAMENTO

Grazie Mike e tutti per le idee, vorrei poter contrassegnare tutti come la risposta perché per essere onesti, ho finito per usare un po ' dell'idea di tutti. Più di Mike, quindi lo capisce.

Quindi, questo è probabilmente qualcosa che verrà esaminato in futuro perché è un metodo piuttosto interessante di convalidare in modo condizionale un campo. Il mio obiettivo è creare un modo intuitivo per affrontare questo. Mi è piaciuta l'idea di dare semplicemente uno schiaffo in una dichiarazione condizionale nel file di configurazione di json. Ovviamente, questo comporterebbe qualche serio rischio per la sicurezza o un motore di parsing super complesso, quindi ho finito col richiedere allo sviluppatore di imparare il nostro metodo di linguaggio condizionale, ma come vedrete, l'ho mantenuto abbastanza simile a quello originale. Penso che sia importante avere API semplici altrimenti scoraggerebbe lo sviluppo sulla tua piattaforma. Check it out:

"element_name":{ 
    "required": "conditional", 
    "conditions" : { 
     "<" : ['4', '5'], 
     "==" : [':element_id', ':other_element'], // will grab the values of those elements and compare 
     "exp" : [['something', '==', 'something_else'], 'OR', [':other_element', '!=', '0']] 
    } 
} 
+1

In che modo esattamente lo sviluppatore fornisce l'elenco dei requisiti? – Mike

+0

Mike Devo scusarmi, ho corretto il mio codice. Ma una volta che lo sviluppatore ha contrassegnato il 'required' come' condizionale', ci si aspetta che lo sviluppatore fornisca una 'condizione' obj/array contenente una lista di condizioni come visto sopra. –

+0

@FelipeTadeo http://www.php.net/manual/en/yaml.installation.php Sto avendo un problema simile e lo userei se potessi per i condizionali, dato che verrà eseguito molto più velocemente nella libreria libYAML C . Sfortunatamente ho bisogno che funzioni sui sistemi di altre persone senza installare un modulo. Tuttavia, se è possibile utilizzare YAML - json è in realtà un sottoinsieme e YAML ha grandi funzionalità di abilitazione logica. –

risposta

3

Per espandere la risposta di dethtron5000, un modo in cui ho pensato di fare questo per evitare una regex ridicolmente complessa è quello di fare in modo che il dev divida i suoi condizionali in qualcosa di più di una cosa condizionale multidimensionale e ricolleghi usando una funzione ricorsiva. Ad ogni livello avresti un "operatore", che potrebbe essere "AND" o "OR" (almeno spero che questo sia chiamato "operatore", se non ti senti libero di cambiarlo).

Nel tuo esempio hai:. (32 < 40 AND 10 > 5 OR 20 == 10)

(sembra che si sta json_encoding i condizionali, così ho iniziato con il seguente array PHP e lavorato a ritroso da lì che ti sto assumendo posso solo ciò che il vostro json_decode dev ti fornisce un array PHP valido). L'esempio precedente è rappresentato come il seguente array PHP:

$arr = array(
    'required' => 'conditional', 
    'conditions' => array(
     'requirements' => array(
      'operator' => 'OR', // this makes it so that if any conditions at this level are true, it returns true 
      0 => array(
       'operator' => 'AND', // this makes it so that all the conditions at this sub-level need to be satisfied to return true 
       array(
        'conditional1' => 32, 
        'conditional2' => 40, 
        'operation' => 'lessthan', 
       ), 
       array(
        'conditional1' => 10, 
        'conditional2' => 5, 
        'operation' => 'greaterthan', 
       ), 
      ), 
      1 => array(
       // Since there is only one condition here, it is not necessary to specify "AND" or "OR" 
       array(
        'conditional1' => 20, 
        'conditional2' => 10, 
        'operation' => 'equals', 
       ), 
      ), 
     ), 
    ), 
); 

È possibile quindi scorrere le condizionali con una funzione ricorsiva simili:

function check_req(Array $reqs) { 
    $operator = (isset($reqs['operator'])) ? $reqs['operator'] : 'AND'; 
    unset($reqs['operator']); 
    foreach ($reqs as $req) { 
     if (isset($req['operation'])) { 
      switch ($req['operation']) { 
       case 'lessthan': 
        $valids[] = $req['conditional1'] < $req['conditional2']; 
        break; 
       case 'greaterthan': 
        $valids[] = $req['conditional1'] > $req['conditional2']; 
        break; 
       case 'equals': 
        $valids[] = $req['conditional1'] == $req['conditional2']; 
        break; 
      } 
     } 
     else { 
      $valids[] = check_req($req); 
     } 
    } 
    if ($operator == 'OR') { 
     foreach ($valids as $valid) { 
      if ($valid == true) { 
       return true; 
      } 
     } 
     return false; 
    } 
    else { 
     foreach ($valids as $valid) { 
      if ($valid == false) { 
       return false; 
      } 
     } 
     return true; 
    } 
} 

var_dump(check_req($arr['conditions']['requirements'])); // true in this case 

Quando json_encode, ottengo:

{ 
    "required":"conditional", 
    "conditions":{ 
     "requirements":{ 
      "operator":"OR", 
      "0":{ 
       "operator":"AND", 
       "0":{ 
        "conditional1":32, 
        "conditional2":40, 
        "operation":"lessthan" 
       }, 
       "1":{ 
        "conditional1":10, 
        "conditional2":5, 
        "operation":"greaterthan" 
       } 
      }, 
      "1":[{ 
       "conditional1":20, 
       "conditional2":10, 
       "operation":"equals" 
      }] 
     } 
    } 
} 

Suppongo che questo sia ciò che lo sviluppatore dovrebbe fornirti.

+1

Grazie Mike! Sì, si chiama operatore logico, prendo coscienza del gergo, quindi ecco un breve elenco di definizioni per riferimento futuro: Operatori logici = 'AND OR' Operatori di confronto' < >! = == === ext..'. GRAZIE ANCORA! –

+0

Per semplificare lo sviluppo, suppongo che potremmo prendere quest'idea e analizzare la stringa e crearla in una matrice come quella che hai fornito, quindi eseguirla come hai fatto qui. Sarà un lavoro infernale, ma farà sicuramente risparmiare tempo nella documentazione e nella lunghezza del json. –

+1

Se il tuo dev potrebbe fornirti una stringa json_encoded come quella che ho qui alla fine, hai solo [json_decode] (http://php.net/json_decode) nel tuo codice. Non è necessario analizzarlo manualmente. È già molto vicino a come lo hai ora. – Mike

1

Sì - è possibile utilizzare un'istruzione switch vis:

switch ($ operatore)

case "==": 
return $a==$b; 
break; 
case "+": 
return $a+$b; 
break; 

etc... 

default: 
return false; 
break; 

La chiave è come inviare le parti allo switch - ci sono molti modi dopo tutto.

Sarebbe pazzo esporre eval() al codice utente non attendibile. Posso vederne l'uso in situazioni come Drupal, dove gli utenti uber possono creare pagine PHP che vengono poi valutate come tali, ma ovviamente solo gli utenti fidati possono farlo. Molto meglio per limitare il codice che può essere eseguito in questo modo.

EDIT:

Al fine di gestire più operatori, è ancora eseguire questo come l'unità aritmetica, ma è necessario valutare ogni operatore separatamente. Ciò potrebbe significare che devi valutare una stringa di istruzioni un carattere alla volta per raccogliere parentesi quadre. Potrebbe essere meglio fare i calcoli in javascript sul lato client. Questo è sicuro fino a quando la risposta non viene inviata al tuo server (eval in JS può portare ad attacchi DOM injection)

Se potessi fare un suggerimento impertinente, hai davvero bisogno di farlo in questo modo? C'è un modo migliore, forse uno che tratterà l'input in blocchi più piccoli? Se stavo usando questo sistema, probabilmente prenderei un calcolatore da tavolo per fare il lavoro piuttosto che usare un sito web! Cerca di fare più del lavoro degli utenti per loro.

+0

Grazie per il tuo suggerimento! Questo è abbastanza doozy. Conosco i vantaggi della limitazione del codice, ma considero la seguente condizione se superata: '(32 < 40 AND 10 > 5 OR 20 == 10)' –

4

Una soluzione potrebbe essere quella di strutturare il JSON in modo da limitare il numero totale di operazioni che è possibile eseguire. Per esempio

"element_name":{ 
"required": "conditional", 
"requirements" : [ 
     { 
      "condition1": 4, 
      "condition1": 5, 
      "operation": "greaterthan" 
     } 
} 

e poi in PHP (psedo-Codey, ma è possibile ottenere l'idea):

foreach($requirements as $key => $test){ 
    switch($test->operation) { 
     case 'greaterthan': 
      return ($test->condition1 > $test->condition2); 
     /// put other comparison types here 

    } 
} 

Questo significherebbe la codifica di più la logica di business, ma finirebbe per essere più sicuro e da impedire iniezioni come eval.

+0

Questa è un'ottima alternativa. Speravo di evitare di fare qualcosa di simile, perché poi lo sviluppatore avrebbe scavato nella mia documentazione per capire come usarlo. Peccato che non possa essere facile come il mio esempio sopra e sicuro. Forse se esagero una regex pazza, ma non sono molto bravo con quelli. –

2

Per scomporre il testo iniziale, è possibile utilizzare json_decode()? Questo non eseguirà nulla, ma coprirà la tua grande stringa in una struttura di array.

Per le singole espressioni effettive, si ha accesso a parsekit_compile_string?

Ciò consentirebbe la conversione del testo non elaborato in operazioni bytecode php. Non dovrebbe essere difficile interpretare le operazioni tramite un'istruzione switch. A causa della natura limitata di ciò che hai detto che ti aspetti nei requisiti, questo non sarebbe un gran numero di codice.

Si potrebbe tentare di regex senza parsekit_compile_string, ma questa sarebbe una soluzione più fragile.

Problemi correlati