2012-05-19 11 views
8

Ho cercato modi migliori per gestire la convalida fino a quando ho sviluppato applicazioni Web. Spesso è necessario catturare più errori di convalida, quindi volevo sapere se c'era un modo migliore per farlo rispetto al seguente.Qual è il modo più pulito per gestire gli errori di convalida con PHP?

In questo momento ho un metodo assert in un framework che ho sviluppato io stesso. Un esempio del metodo è questo:

assert(($foo == 1), 'Foo is not equal to 1');

Se la condizione nel primo argomento è falso, viene aggiunto il messaggio di errore nel secondo argomento di un $errors matrice (che è avvolto in una classe (riferimento da $eh in basso) che fornisce funzioni utili come hasErrors()).

Questo metodo funziona ma è in pratica disordinato. Considera questo codice:

public function submit($foo, $bar, $baz) 
{ 
    assert(($foo == 1), 'Foo is not equal to 1'); 
    assert(($bar == 2), 'Bar is not equal to 2'); 

    if (!$eh->hasErrors()) 
    { 
     assert(($baz == 3), 'Baz is not equal to 3'); 

     if (!$eh->hasErrors()) 
     { 
      finallyDoSomething(); 
      return; 
     } 
    } 

    outputErrors(); 
} 

Questa è una cosa abbastanza comune. Voglio controllare due condizioni prima di andare avanti, e poi se quelle passano, controlla una terza condizione prima di fare finalmente quello che voglio fare. Come puoi vedere, la maggior parte delle righe in questo codice sono correlate alla convalida. In un'applicazione reale, ci saranno più convalide e probabilmente più dichiarazioni nidificate.

Qualcuno ha una struttura migliore per la gestione di convalida di questo? Se esistono quadri che gestiscono questo in modo più elegante, quali sono e in che modo lo realizzano? Le affermazioni multiple se nidificate sembrano una soluzione del tipo "forza bruta".

Solo una nota, ho capito che sarebbe probabilmente una buona idea di avvolgere alcune funzioni comuni di validazione in una classe in modo che posso controllare la lunghezza, formato della stringa, ecc, chiamando queste funzioni. Quello che sto chiedendo è un approccio più pulito alla struttura del codice, non come sto effettivamente controllando gli errori.

Grazie!

risposta

6

Controllare Respect\Validation. È una biblioteca costruita per quel purpouse. È in grado di gestire più regole molto facilmente e utilizza eccezioni per errori. Ecco un esempio rapido:

<?php 

use Respect\Validation\Validator as v; 

$usernameValidator = v::alnum()->noWhitespace()->length(1,15); 

$valid = $usernameValidator->validate("alganet"); //$valid now == true 
$valid = $usernameValidator->validate("ácido acético"); //$valid now == false 

Ora utilizzando eccezioni:

try { 
    $usernameValidator->assert("foo # bar"); 
} catch (Exception $e) { 
    $errors = $e->findMessages('alnum', 'noWhitespace', 'length'); 
} 

Nell'esempio di cui sopra, la variabile $errors sarebbe qualcosa di simile:

array(
    "alnum" => '"foo # bar" must contain only letters and digits', 
    "noWhitespace" => '"foo # bar" must not contain whitespace', 
    "length" => null 
) 

ho rotto due precedentemente dichiarato regole usando "foo # bar": ha spazi bianchi e ha un carattere non alnum. Per ogni regola che non passa, verrà restituito un messaggio. Poiché "length" è OK, il messaggio di errore è nullo.

Il documentation include numerosi altri esempi, tra cui regole gerarchiche nidificate e una migliore gestione delle eccezioni. Ha anche una vasta lista di campioni per tutti i validatori 30+ incorporati.

Spero che questo aiuti!

+0

Mi piace molto questa libreria, penso che esaminerò il modo in cui funziona in modo più dettagliato e cerco di replicare le sue funzionalità nel mio framework. Grazie! –

+0

* La validazione * è fantastica, la migliore che abbia mai visto. Complimenti a te! –

+2

+1. Ho finalmente provato 'Respect \ Validation' su un progetto veloce ieri. Libreria molto utile! –

4

Che ne dici di lanciare eccezioni? puoi prendere eccezioni con i blocchi try/catch e/o prenderli usando set_exception_handler()

ci sono un numero di tipi di eccezioni utili definiti in PHP, che puoi usare a tuo vantaggio se hai bisogno di granularità nella gestione delle eccezioni. inoltre è possibile definire eccezioni personalizzate.

http://php.net/manual/en/function.set-exception-handler.php http://www.php.net/manual/en/spl.exceptions.php

EDIT

Per rispondere alla tua domanda su come alcuni altri quadri di affrontare questo problema - giudizioso l'uso di eccezioni sembra abbastanza comune. La cosa utile del loro utilizzo è, diciamo che hai un metodo particolare che fa un numero di diverse convalide che potrebbero essere errate - puoi lanciare un'eccezione appropriata in ciascun caso, ma non devi gestire le diverse eccezioni possibili in quel metodo. invece, a seconda di come si struttura il codice, è possibile consentire all'eccezione di creare bolle in una posizione più centralizzata del codice in cui è possibile accedervi e gestirla in modo appropriato.

EDIT 2

di approfondire il mio ultimo commento circa filter_input_array()

Sulla base di un esempio molto semplice con i dati utente pubblicati. In primo luogo creare una definizione:

$userFormDefinition = array(
    'email' => FILTER_VALIDATE_EMAIL, 
    'age' => FILTER_VALIDATE_INT, 
    'name' => array(
     'filter' => FILTER_VALIDATE_REGEXP, 
     'options' => array('regexp' => '/^\w+$/') 
    ), 
); 

Quindi, utilizzando una classe di convalida generica (definizione di classe di seguito):

$formValidator = new FormValidator(); 
$formValidator->validatePost($userFormDefinition); 

if ($formValidator->isValid()) { 

    // if valid, retrieve the array 
    // and use the values how you wish 

    $values = $formValidator->getValues(); 

    // for example, extract and populate 
    // a User object, or whatever :) 

    extract($values); 

    $user = new User(); 
    $user->setName($name); 
    $user->setEmail($email); 
    $user->setAge($age); 

    // etc. 
} 

Un'implementazione molto semplice (e non testato) di un FormValidator.

Il caso di utilizzo di base è chiamare il metodo appropriato per il metodo di richiesta da filtrare. Questo a sua volta controlla i valori restituiti e decide se l'input è valido.

Questo potrebbe utilizzare un sacco di amore - soprattutto il metodo filterInput, perché potrebbe essere necessario fare qualche prova per essere sicuri di gestire i valori 'truthy' o 'falsy' in modo appropriato. Sto pensando ai valori del tipo di checkbox. Un controllo diretto in_array per false potrebbe non essere tagliato come implementato qui. Ma ci sono un sacco di bandiere che puoi trasmettere con la definizione.

Immagino che si possa anche verificare la presenza di input mancanti mediante il conteggio del numero risultante dell'array $values e della definizione, per assicurarsi che corrispondano. Gli input addizionali che non sono nella definizione sono filtrati (potresti volerlo controllare ma sono ragionevolmente sicuro di questo in cima alla mia testa).

<?php 

class FormValidator 
{ 
    private $isValid = false; 

    private $isBound = false; 

    private $values = array(); 

    public function validatePost(array $definition) 
    { 
     // additional REQUEST_METHOD checking here? 
     $this->filter(INPUT_POST, $definition); 
    } 

    public function validateGet(array $definition) 
    { 
     // additional REQUEST_METHOD checking here? 
     $this->filterInput(INPUT_GET, $definition); 
    } 

    protected function filterInput($type, $definition) 
    { 
     $this->isBound = true; 

     $this->values = filter_input_array($type, $definition); 

     // might have to do some reading on nulls vs false, 
     // and validating checkbox type values here... you can 
     // set all sorts of flags so a better implementation 
     // would probably be required here... :s 

     if (is_array($this->values) && !in_array(false, $this->values))) { 
      $this->isValid = true; 
     } 
    } 

    public function isValid() 
    { 
     if (!$this->isBound) { 
      throw new Exception("you didn't validate yet!"); 
     } 

     return $this->isValid; 
    } 

    public function getValues() 
    { 
     if (!$this->isBound) { 
      throw new Exception("You didn't validate yet!"); 
     } 

     return $this->values; 
    } 
} 

Comunque, direi refactoring e testare le bejayzis fuori di quella classe, (o anche del tutto cambia), ma si spera che delinea l'idea di base: per ogni tipo di ingresso, creare una definizione e quindi utilizzare un classe di convalida generica per filtrare e garantire la validità.

Spero che questo aiuti. filter_input rock

+1

L'ho esaminato, ma non ho avuto una grande risposta quando ho chiesto. Entrambe le risposte che ho ricevuto non erano a favore di Eccezioni. Vedi la mia domanda qui: http://stackoverflow.com/questions/10196227/how-to-make-good-use-of-php-error-handling-throwing-exceptions –

+0

interessante. Suppongo che l'opinione vari qui - hanno sicuramente degli usi. è possibile esagerare con loro (come qualsiasi cosa), ma penso che l'uso appropriato delle eccezioni sia decisamente utile. A parer mio. mi sembra una cosa culturale - altre lingue sembrano abbracciare maggiormente il loro uso. –

+0

si. leggendo un po 'di più.l'opinione sembra variare su questo argomento. alcune eccezioni di una mano sono pubblicizzate da alcuni per circostanze * eccezionali * - quando la situazione è fuori dal tuo controllo. un buon esempio che mi viene in mente è la creazione di un oggetto PDO: puoi racchiuderlo in un try/block per gestire la miriade di possibili errori durante la connessione a un db. Detto questo, se si guarda al componente Validator di Symfony2, molte delle sue classi generano UnexpectedValueExceptionException in base alla valutazione di un argomento. Ho intenzione di alzare la mano e dire che è una questione di gusti. e possibilmente tatto :) –

0

Di seguito ho scritto un esempio che mostra come utilizzare le eccezioni in generale (non specifiche per la situazione) e più in basso qualcosa che è più specifico per voi (utilizzando ancora eccezioni). Questi primi due esempi gestiranno 1 errore alla volta. Il terzo esempio che ho fornito fornisce un esempio di come gestire più errori ed eccezioni.

La maggior parte della spiegazione è nei commenti del codice, in modo da essere sicuri di guardare attraverso di esso a fondo :)

Exception Handling Generale

<?php 
//Define some variables to work with 
$var = false; 
$var2 = false; 

try { //Outer try 
    echo 'Do something here!<br />'; 

    try { //Inner try 
     if($var !== true) { //Fail 
      throw new Exception('$var is not true',123); //Exception is thrown (caught 2 lines down) 
     } 
    } catch (Exception $e) { //Exception caught here 
     echo 'InnerError# '.$e->getCode().': '.$e->getMessage().'<br />'; //Exception handled (in this case printed to screen) 
    } 

    //Code is continuing here even after the exception was thrown 
    echo 'Do something else here!<br />'; 

    if($var2 !== true) { //Fail 
     throw new Exception('$var2 is not true', 456); //Exception is thrown (caught 6 lines down) 
    } 

    //Code fails to run as the exception above has been thrown and jumps straight into the below catch 
    echo 'Do the third thing here!<br />'; 

} catch (Exception $e) { //Exception caught here 
    echo 'Error # '.$e->getCode().': '.$e->getMessage().' on line '.$e->getLine().' in '.$e->getFile().'<br />'; //Exception handled (in this case printed to screen) 
} 

//Code is continuting here even after both of the exceptions 
echo 'Do even more stuff here!<br />'; 
?> 

standard classe Exception costruttore:

public __construct ([ string $message = "" [, int $code = 0 [, Exception $previous = NULL ]]]) 

eccezioni personalizzate

Ora, in relazione questo per il tuo esempio, si potrebbe fare qualcosa in queste righe:

<?php 
class customException extends Exception { //Create a custom exception handler that allows you to pass more arguments in the constructor    
    public function __construct($errorString, $errorNumber, $errorFile, $errorLine) { 
     $this->message = $errorString; //Using the Exception class to store our information 
     $this->code = $errorNumber; 
     $this->file = $errorFile; 
     $this->line = $errorLine; 
    } 
} 

function err2Exception($errNo, $errStr, $errFile, $errLine) { //This function converts the error into an exception 
    throw new customException($errStr, $errNo, $errFile, $errLine); //Throw the customException 
} 

set_error_handler('err2Exception'); //Set the error handler to the above function 

try { 
    assert(1==2); //This fails, calls the function err2Exception with the correct arguments, throws the error and is caught below 
} catch (Exception $e) { //Error caught as an Exception here 
    //Echo out the details (or log them, or whatever you want to do with them) 
    echo 'Error String: '.$e->getMessage().'<br />'; 
    echo 'Error Number: '.$e->getCode().'<br />'; 
    echo 'File containing error: '.$e->getFile().'<br />'; 
    echo 'Line with error: '.$e->getLine().'<br />'; 

} 
?> 

http://php.net/manual/en/function.set-error-handler.php

uscita del codice di cui sopra:

Error String: assert(): Assertion failed

Error Number: 2

File containing error: 18

Line with error: /var/www/test2.php

è possibile applicare i concetti di nidificazione try/catch dichiarazioni nel primo esempio di codice con questo secondo esempio di gestione degli errori personalizzati.

gestire più errori/eccezioni

<?php 
class errorLogger { //create an errorLogger class 
    private $errors; //Stores all errors 

    public function addError($errCode, $errMsg, $errFile = null, $errLine = null) { //Manually add an error 
     $this->errors[] = array(//Add to the error list 
      'code' => $errCode, 
      'message' => $errMsg, 
      'file' => $errFile, 
      'line' => $errLine 
     ); 
    } 

    public function addException($exception) { //Add an exception to the error list 
     $this->errors[] = array(//Add to the error list 
      'code' => $exception->getCode(), 
      'message' => $exception->getMessage(), 
      'file' => $exception->getFile(), 
      'line' => $exception->getLine() 
     ); 
    } 

    public function getErrors() { //Return all of the errors 
     return $this->errors; 
    } 

    public function numErrors() { //Return the number of errors 
     return count($this->errors); 
    } 
} 

$el = new errorLogger(); //New errorLogger 
set_error_handler(array($el, 'addError')); //Set the default error handler as our errorLoggers addError method 
set_exception_handler(array($el, 'addException')); //Set the default exception handler as our errorLoggers addException method 

if(!is_numeric('a')) //Will fail 
    $el->addError('Invalid number', 1); //Adds a new error 

if(($name = 'Dave') !== 'Fred') //Will fail 
    $el->addError('Invalid name ('.$name.')', 2, 'test.php', 40); //Adds another error 

assert(1==2); //Something random that fails (non fatal) also adds to the errorLogger 

try { 
    if('Cats' !== 'Dogs') //Will fail 
     throw new Exception('Cats are not Dogs', 14); //Throws an exception 
} catch (Exception $ex) { //Exception caught 
    $el->addException($ex); //Adds exception to the errorLogger 
} 

trigger_error('Big bad wolf blew the house down!'); //Manually trigger an error 

//throw new Exception('Random exception', 123); //Throw an exception that isn't caught by any try/catch statement 
               //(this is also added to the errorLogger, but any code under this is not run if it is uncommented as it isn't in a try/catch block) 

//Prints out some 
echo '<pre>'.PHP_EOL; 
echo 'There are '.$el->numErrors().' errors:'.PHP_EOL; //Get the number of errors 

print_r($el->getErrors()); 

echo '</pre>'.PHP_EOL; 
?> 

Ovviamente è possibile modificare e adattare la classe errorLogger per soddisfare specificamente le vostre esigenze.

uscita del codice di cui sopra:

There are 5 errors:

Array (

[0] => Array 
    (
     [code] => Invalid number 
     [message] => 1 
     [file] => 
     [line] => 
    ) 

[1] => Array 
    (
     [code] => Invalid name (Dave) 
     [message] => 2 
     [file] => test.php 
     [line] => 10 
    ) 

[2] => Array 
    (
     [code] => 2 
     [message] => assert(): Assertion failed 
     [file] => /var/www/test.php 
     [line] => 42 
    ) 

[3] => Array 
    (
     [code] => 14 
     [message] => Cats are not Dogs 
     [file] => /var/www/test.php 
     [line] => 46 
    ) 

[4] => Array 
    (
     [code] => 1024 
     [message] => Big bad wolf blew the house down! 
     [file] => /var/www/test.php 
     [line] => 51 
    ) 

)

Il codice di cui sopra consente di:

  • eccezioni tiro e aggiungerli alla errorLogger
  • gestire eventuali situazioni non gestite dalle funzioni casuali che farebbe normalmente causare errori da visualizzare
  • Aggiungi i tuoi errori manualmente
  • errori trigger (http://uk3.php.net/trigger_error)

È quindi possibile visualizzare/log/qualunque cosa tutti gli errori in un momento successivo.

NB: Tutto il codice di cui sopra può essere copiato e incollato direttamente darti qualcosa di sperimentare

+0

Le eccezioni non fermano l'esecuzione del codice? Se non dovessi fare più validazioni e restituire tutti gli errori, lanciare un'eccezione sul primo errore non colmerebbe il resto dei potenziali errori. È corretto? –

+0

* Cough * Sì, lol. Ho trascurato il bit "multiplo" nel tuo post. Ho aggiunto alcuni alla mia risposta ora comunque. – vimist

2

Quando si dice "validazione" - Io parto dal presupposto che si sta convalidando l'input dell'utente prima di fare un azione.Lo uso spesso quando invio dati con AJAX da jQuery o quando rispondo da un servizio web.

Se è così, si potrebbe voler guardare il mio very simple validation class.

<?php 

$validator = new Validator(); 

// Each validation rule is a property of the validator object. 
$validator->username = function($value, $key, $self) 
{ 
    if(preg_match('~\W~', $value)) 
    { 
     return 'Your username must only contain letters and numbers'; 
    } 
}; 

$validator->password = function($value, $key, $self) 
{ 
    if(strlen($value) < 8) 
    { 
     return 'Your password must be at least 8 characters long'; 
    } 
}; 

if(! $validator($_POST)) 
{ 
    die(json_encode($validator->errors())); 
} 

// ... register user now 

È possibile utilizzarlo per convalidare qualsiasi dato, purché sia ​​in forma di array. Non solo $ _POST/$ _ GET array.

1

Abbiamo creato e utilizzato il numero di diversi quadri. La gestione della forma è in genere una parte essenziale della creazione di applicazioni Web. Quindi, per rispondere alla tua domanda sulla gestione degli errori, suggerirei di guardare la domanda più ampiamente.

Chiaramente, per qualsiasi cosa per essere convalidato, è necessario disporre di dati di input una sorta di e la definizione dei dati di input. Quindi, hai un modulo o pianifichi di avere una convalida centralizzata per più di un modulo. Se, quindi, la creazione di un oggetto di convalida comune ha senso.

class validator {} 

Ok, quindi, affinché il validatore funzioni correttamente, deve sapere cosa convalidare e come. Quindi, qui torniamo alla domanda su come create le forme - sono quelle dinamiche, basate su Modelli, o sono semplici html. Se il modulo è basato sul modello, di solito ha tutti i campi definiti e in genere la maggior parte delle regole di convalida sono già presenti a livello di modello. In tal caso, ha senso insegnare al tuo validatore ad apprendere i campi dal modello, ad es.

function setModel($model){} 
function getFields(){ -- iterates through $model fields} 

in alternativa, se non si utilizza modelli e la forma è semplice HTML, il semplice array di campi e validatori ha più senso:

$fields = array(
    "name" => array("notNull"), 
    "age" => array("notNull", array("size", 13, 99)) 
); 

sopra approccio consente di definito validatori (uno o più), e ogni validatore può contenere parametri aggiuntivi. in questo caso, il validatore sarà simile:

function validate($data, $fields){ 
    $this->valid = true; 
    $this->data = $data; 
    foreach ($fields as $field_name => $validators){ 
     foreach ($validators as $v){ 
      $params = array($field_name, isset($data[$field_name])?$data[$field_name]:null); 
      if (is_array($v)){ 
       $m = "_" . $v[0]; 
       unset($v[0]); 
       $params = array_merge($params, $v); 
      } else { 
       $m = "_" . $v; 
      } 
      if (method_exists($this, $m)){ 
       call_user_func_array(array($this, $m), $params); 
      } 
     } 
    } 
    if (!empty($this->errors)){ 
     $this->valid = false; 
    } 
    return $this->valid; 
} 

cosa interessante è che è possibile aggiungere le seguenti validatori come nuovi metodi alla classe di convalida nel seguente modo:

function _notNull($field_name, $value){ 
    if (!$value){ 
     $this->errors[$field_name][] = "Must be filled"; 
    } 
} 

function _size($field_name, $value, $min = null, $max = null){ 
    if ($value < $min && $min){ 
     $this->errors[$field_name][] = "Must be at least $min"; 
    } else if ($value > $max && $max){ 
     $this->errors[$field_name][] = "Must be at most $max"; 
    } 
} 

è così, allora, utilizzando questo approccio avresti una classe di validatori che può essere facilmente estesa, puoi avere più parametri per i validatori, e i validatori possono usare regex/filter o qualsiasi altro metodo di validazione dei campi. infine, $this->errors array conterrà array associativo con campi e gli errori. Inoltre, un solo errore per campo, per non confondere l'utente. e ovviamente puoi usare solo array o modelli basati sull'ambiente in cui avverrà la validazione.

0

Per aiutarti a rilassarti, qualunque cosa tu faccia, finirai con lo stesso ciclo procedurale di base che descrivi. Puoi rimuovere leggermente il nidificazione (vedi sotto) ma non di molto.

Per la convalida, è necessario un flusso procedurale, che è quello che si ha. Potrebbero esserci variazioni minime (come ad esempio i validatori combinatori, anche se alcuni altri campi sono errati) ma questo è il flusso procedurale.

1. Loop through all fields, and validate them; store errors if any 
2. If (no errors) { 
3.  loop though all multiple combinations and validate them, store errors if any 
4. } 
5. If (no errors) { 
6.  do action, store errors if any 
7. } 
8. If (no errors) { 
9.  Report back success 
10. } else { 
11. Report back problems 
12. } 

Per renderlo più efficiente dal punto di vista di codifica è possibile seguire quasi tutte le risposte lì - aggiungere “sul campo” le classi, e un ciclo tra quelli, o una serie di condizioni di validazione e ciclo se quelli. Puoi aggiungere "classi di validatori" (ma avrai bisogno di due tipi - un tipo collegato al campo, un tipo allegato al modulo) e puoi usare le eccezioni per tornare al ciclo precedente - ma quel loop procedurale di base sei preoccupato per non cambierà mai.


Ma per rispondere in modo più appropriato il mio modo di lavorare (su progetti di grandi dimensioni) è quello di avere un:

  1. “Campo Validator” di classe che convalida il tipo di campo, la lunghezza, ecc obbligatoria
  2. Classe "Form Validator" che convalida combinazioni specifiche di dati, ecc.
  3. Classe "Modulo" che controlla le azioni del modulo. Questo è anche utile che diversi tipi di classe di form possono collegarsi a un database (simile a VB/C# .Net) e inserire la convalida del campo dai tipi di campo e ha funzionalità standard "modifica", "aggiungi" e "elimina".
  4. Classe "Field" che controlla le azioni del campo. Il campo può anche essere collegato a un DB, collegato ad altri campi ecc.

Il modulo verrà convalidato con la stessa struttura procedurale, ad eccezione del ciclo che scorre tra gli oggetti campo (non campi non elaborati) e restituisce le eccezioni a il ciclo che memorizza gli errori rispetto al modulo (come suggerito da Darragh). Con le classi è solo più strutturato e più facile da aggiungere/modificare, ecc.

Per progetti rapidi (moduli di una pagina) senza il sovraccarico del framework, vorrei solo utilizzare il codice con convalide specifiche. (Questa è una scelta personale - gli altri diranno che dovresti sempre usare il framework anche per piccoli progetti, entrambe le opzioni sono valide e non una discussione qui. A volte utilizzerei solo un'opzione di medio livello.) Qualunque sia il progetto.)

Ma non importa cosa - il ciclo procedurale di base è lo stesso. Niente di ciò che fai perché è ciò che è richiesto.

Problemi correlati