Validazione

La validazione è un compito molto comune nella applicazioni web. I dati inseriti nei form hanno bisogno di essere validati. I dati hanno bisogno di essere validati anche prima di essere inseriti in una base dati o passati a un servizio web.

Symfony2 ha un componente Validator , che rende questo compito facile e trasparente. Questo componente è bastato sulle specifiche di validazione JSR303 Bean.

Le basi della validazione

Il modo migliore per capire la validazione è quello di vederla in azione. Per iniziare, supponiamo di aver creato un classico oggetto PHP, da usare in qualche parte della propria applicazione:

// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;

class Author
{
    public $name;
}

Finora, questa è solo una normale classe, che ha una qualche utilità all’interno della propria applicazione. Lo scopo della validazione è dire se i dati di un oggetto siano validi o meno. Per poterlo fare, occorre configurare una lisa di regole (chiamate vincoli) che l’oggetto deve seguire per poter essere valido. Queste regole possono essere specificate tramite diversi formati (YAML, XML, annotazioni o PHP).

Per esempio, per garantire che la proprietà $name non sia vuota, aggiungere il seguente:

Tip

Anche le proprietà private e protette possono essere validate, così come i metodi “getter” (vedere Obiettivi dei vincoli).

Usare il servizio validator

Successivamente, per validare veramente un oggetto Author, usare il metodo validate sul servizio validator (classe Symfony\Component\Validator\Validator). Il compito di validator è semplice: leggere i vincoli (cioè le regole) di una classe e verificare se i dati dell’oggetto soddisfino o no tali vincoli. Se la validazione fallisce, viene restituita una lista di errori (classe Symfony\Component\Validator\ConstraintViolationList). Prendiamo questo semplice esempio dall’interno di un controllore:

// ...
use Symfony\Component\HttpFoundation\Response;
use Acme\BlogBundle\Entity\Author;

public function indexAction()
{
    $autore = new Author();
    // ... fare qualcosa con l'oggetto $autore

    $validator = $this->get('validator');
    $errori = $validator->validate($autore);

    if (count($errori) > 0) {
        /*
         * Usa un metodo a __toString sulla variabile $errors, che è un oggetto
         * ConstraintViolationList. Questo fornisce una stringa adatta
         * al debug
         */
        $errorsString = (string) $errori;

        return new Response($errorsString);
    }

    return new Response('L\'autore è valido! Sì!');
}

Se la proprietà $name è vuota, si vedrà il seguente messaggio di errore:

Acme\BlogBundle\Author.name:
    This value should not be blank

Se si inserisce un valore per la proprietà $name, apparirà il messaggio di successo.

Tip

La maggior parte delle volte, non si interagirà direttamente con il servizio validator, né ci si dovrà occupare di stampare gli errori. La maggior parte delle volte, si userà indirettamente la validazione, durante la gestione di dati inviati tramite form. Per maggiori informazioni, vedere Validazione e form.

Si può anche passare un insieme di errori in un template.

if (count($errors) > 0) {
    return $this->render('AcmeBlogBundle:Author:validate.html.twig', array(
        'errors' => $errors,
    ));
}

Dentro al template, si può stampare la lista di errori, come necessario:

Note

Ogni errore di validazione (chiamato “violazione di vincolo”) è rappresentato da un oggetto Symfony\Component\Validator\ConstraintViolation.

Validazione e form

Il servizio validator può essere usato per validare qualsiasi oggetto. In realtà, tuttavia, solitamente si lavorerà con validator indirettamente, lavorando con i form. La libreria dei form di Symfony usa internamente il servizio validator, per validare l’oggetto sottostante dopo che i valori sono stati inviati e collegati. Le violazioni dei vincoli sull’oggetto sono convertite in oggetti FieldError, che possono essere facilmente mostrati con il proprio form. Il tipico flusso dell’invio di un form assomiglia al seguente, all’interno di un controllore:

// ...
use Acme\BlogBundle\Entity\Author;
use Acme\BlogBundle\Form\AuthorType;
use Symfony\Component\HttpFoundation\Request;

public function updateAction(Request $request)
{
    $author = new Author();
    $form = $this->createForm(new AuthorType(), $author);

    $form->handleRequest($request);

    if ($form->isValid()) {
        // validazione passata, fare qualcosa con l'oggetto $author

        return $this->redirect($this->generateUrl(...));
    }

    return $this->render('BlogBundle:Author:form.html.twig', array(
        'form' => $form->createView(),
    ));
}

Note

Questo esempio usa una classe AuthorType, non mostrata qui.

Per maggiori informazioni, vedere il capitolo sui Form.

Configurazione

La validazione in Symfony2 è abilitata per configurazione predefinita, ma si devono abilitare esplicitamente le annotazioni, se le si usano per specificare i vincoli:

Vincoli

Il servizio validator è progettato per validare oggetti in base a vincoli (cioè regole). Per poter validare un oggetto, basta mappare uno o più vincoli alle rispettive classi e quindi passarli al servizio validator.

Dietro le quinte, un vincolo è semplicemente un oggetto PHP che esegue un’istruzione assertiva. Nella vita reale, un vincolo potrebbe essere “la torta non deve essere bruciata”. In Symfony2, i vincoli sono simili: sono asserzioni sulla verità di una condizione. Dato un valore, un vincolo dirà se tale valore sia aderente o meno alle regole del vincolo.

Vincoli supportati

Symfony2 dispone di un gran numero dei vincoli più comunemente necessari:

Si possono anche creare i propri vincoli personalizzati. L’argomento è discusso nell’articolo “/cookbook/validation/custom_constraint” del ricettario.

Configurazione dei vincoli

Alcuni vincoli, come NotBlank, sono semplici, mentre altri, come Choice, hanno diverse opzioni di configurazione disponibili. Supponiamo che la classe Author abbia un’altra proprietà, gender, che possa valere solo “M” oppure “F”:

Le opzioni di un vincolo possono sempre essere passate come array. Alcuni vincoli, tuttavia, consentono anche di passare il valore di una sola opzione, predefinita, al posto dell’array. Nel caso del vincolo Choice, l’opzione choices può essere specificata in tal modo.

Questo ha il solo scopo di rendere la configurazione delle opzioni più comuni di un vincolo più breve e rapida.

Se non si è sicuri di come specificare un’opzione, verificare la documentazione delle API per il vincolo relativo, oppure andare sul sicuro passando sempre un array di opzioni (il primo metodo mostrato sopra).

Traduzione dei messaggi dei vincoli

Per informazioni sulla traduzione dei messaggi dei vincoli, vedere Tradurre i messaggi dei vincoli.

Obiettivi dei vincoli

I vincoli possono essere applicati alle proprietà di una classe (p.e. $name) oppure a un metodo getter pubblico (p.e. getFullName). Il primo è il modo più comune e facile, ma il secondo consente di specificare regole di validazione più complesse.

Proprietà

La validazione delle proprietà di una classe è la tecnica di base. Symfony2 consente di validare proprietà private, protette o pubbliche. L’elenco seguente mostra come configurare la proprietà $firstName di una classe Author, per avere almeno 3 caratteri.

Getter

I vincoli si possono anche applicare ai valori restituiti da un metodo. Symfony2 consente di aggiungere un vincolo a qualsiasi metodo il cui nome inizi per “get”, “is” o “has”. In questa guida, si fa riferimento a questi tipi di metodi come “getter”.

New in version 2.5: Il supporto per metodi che iniziano per has è stato introdotto in Symfony 2.5.

Il vantaggio di questa tecnica è che consente di validare gli oggetti dinamicamente. Per esempio, supponiamo che ci si voglia assicurare che un campo password non corrisponda al nome dell’utente (per motivi di sicurezza). Lo si può fare creando un metodo isPasswordLegal e asserendo che tale metodo debba restituire true:

Creare ora il metodo isPasswordLegal() e includervi la logica necessaria:

public function isPasswordLegal()
{
    return $this->firstName != $this->password;
}

Note

I lettori più attenti avranno notato che il prefisso del getter (“get” o “is”) viene omesso nella mappatura. Questo consente di spostare il vincolo su una proprietà con lo stesso nome, in un secondo momento (o viceversa), senza dover cambiare la logica di validazione.

Classi

Alcuni vincoli si applicano all’intera classe da validare. Per esempio, il vincolo Callback è un vincolo generico, che si applica alla classe stessa. Quano tale classe viene validata, i metodi specifici di questo vincolo vengono semplicemente eseguiti, in modo che ognuno possa fornire una validazione personalizzata.

Gruppi di validazione

Finora, è stato possibile aggiungere vincoli a una classe e chiedere se tale classe passasse o meno tutti i vincoli definiti. In alcuni casi, tuttavia, occorre validare un oggetto solo per alcuni vincoli della sua classe. Per poterlo fare, si può organizzare ogni vincolo in uno o più “gruppi di validazione” e quindi applicare la validazione solo su un gruppo di vincoli.

Per esempio, si supponga di avere una classe User, usata sia quando un utente si registra che quando aggiorna successivamente le sue informazioni:

Con questa configurazione, ci sono tre gruppi di validazione:

  • Default - contiene i vincoli, nella classe corrente e in tutte le classi referenziate, che non appartengono ad altri gruppi;
  • User - equivalente a tutti i i vincoli dell’oggetto User nel gruppo Default;
  • registration - contiene solo i vincoli sui campi email e password.

Per dire al validatore di usare uno specifico gruppo, passare uno o più nomi di gruppo come secondo parametro del metodo validate():

// Se si usa la nuova API di validazione 2.5 (è probabile)
$errors = $validator->validate($author, null, array('registration'));

// Se si usa la vecchia API di validazione 2.4
// $errors = $validator->validate($author, array('registration'));

Se non si specifica alcun gruppo, saranno applicati tutti i vincoli che appartengono al gruppo Default.

Ovviamente, di solito si lavorerà con la validazione in modo indiretto, tramite la libreria dei form. Per informazioni su come usare i gruppi di validazione dentro ai form, vedere Gruppi di validatori.

Sequenza di gruppi

A volte si vogliono validare i gruppi in passi separati. Lo si può fare, usando GroupSequence. In questo caso, un oggetto definisce una sequenza di gruppi e i gruppi in tale sequenza sono validati in ordine.

Per esempio, si supponga di avere una classe User e di voler validare che nome utente e password siano diversi, solo se le altre validazioni passano (per evitare messaggi di errore multipli).

In questo esempio, prima saranno validati i vincoli del gruppo User (che corrispondono a quelli del gruppo Default). Solo se tutti i vincoli in tale gruppo sono validi, sarà validato il secondo gruppo, Strict.

Caution

Come già visto nella precedente sezione, il gruppo Default e il gruppo contenente il nome della classe (p.e. User) erano identici. Tuttavia, quando si usando le sequenza di gruppo, non lo sono più. Il gruppo Default farà ora riferimento alla sequenza digruppo, al posto di tutti i vincoli che non appartengono ad alcun gruppo.

Questo vuol dire che si deve usare il gruppo {NomeClasse} (p.e. User) quando si specifica una sequenza di gruppo. Quando si usa Default, si avrà una ricorsione infinita (poiché il gruppo Default si riferisce alla sequenza di gruppo, che contiene il gruppo Default, che si riferisce alla stessa sequenza di gruppo, ecc...).

Fornitori di sequenza di gruppo

Si immagini un’entità User, che potrebbe essere un utente normale oppure premium. Se è premium, necessita di alcuni vincoli aggiuntivi (p.e. dettagli sulla carta di credito). Per determinare in modo dinamico quali gruppi attivare, si può creare un Group Sequence Provider. Creare prima l’entità e aggiungere un nuovo gruppo di vincoli, chiamato Premium:

Cambiare ora la classe User per implementare Symfony\Component\Validator\GroupSequenceProviderInterface e aggiungere :method:`Symfony\\Component\\Validator\\GroupSequenceProviderInterface::getGroupSequence`, che deve restituire un array di gruppi da usare:

// src/Acme/DemoBundle/Entity/User.php
namespace Acme\DemoBundle\Entity;

// ...
use Symfony\Component\Validator\GroupSequenceProviderInterface;

class User implements GroupSequenceProviderInterface
{
    // ...

    public function getGroupSequence()
    {
        $groups = array('User');

        if ($this->isPremium()) {
            $groups[] = 'Premium';
        }

        return $groups;
    }
}

Infine, occorre notificare al componente Validator che la classe User fornisce una sequenza di gruppi da validare:

Validare valori e array

Finora abbiamo visto come si possono validare oggetti interi. Ma a volte si vuole validare solo un semplice valore, come verificare che una stringa sia un indirizzo email valido. Lo si può fare molto facilmente. Da dentro a un controllore, assomiglia a questo:

use Symfony\Component\Validator\Constraints\Email;
// ...

public function addEmailAction($email)
{
    $emailConstraint = new Email();
    // tutte le opzioni sui vincoli possono essere impostate in questo modo
    $emailConstraint->message = 'Invalid email address';

    // usa il validatore per validare il valore
    // Se si usa la nuova API di validazione 2.5 (è probabile)
    $errorList = $this->get('validator')->validate(
        $email,
        $emailConstraint
    );

    // Se si usa la vecchia API di validazione 2.4
    /*
    $errorList = $this->get('validator')->validateValue(
        $email,
        $emailConstraint
    );
    */

    if (count($errorList) == 0) {
        // è un indirizzo email valido, fare qualcosa
    } else {
        // *non* è un indirizzo email valido
        $errorMessage = $errorList[0]->getMessage();

        // fare qualcosa con l'errore
    }

    // ...
}

Richiamando validateValue sul validatore, si può passare un valore grezzo e l’oggetto vincolo su cui si vuole validare tale valore. Una lista completa di vincoli disponibili, così come i nomi completi delle classi per ciascun vincolo, è disponibile nella sezione riferimento sui vincoli.

Il metodo validateValue restituisce un oggetto Symfony\Component\Validator\ConstraintViolationList, che si comporta come un array di errori. Ciascun errore della lista è un oggetto Symfony\Component\Validator\ConstraintViolation, che contiene il messaggio di errore nel suo metodo getMessage.

Considerazioni finali

validator di Symfony2 è uno strumento potente, che può essere sfruttato per garantire che i dati di qualsiasi oggetto siano validi. La potenza dietro alla validazione risiede nei “vincoli”, che sono regole da applicare alle proprietà o ai metodi getter del proprio oggetto. Sebbene la maggior parte delle volte si userà il framework della validazione indirettamente, usando i form, si ricordi che può essere usato ovunque, per validare qualsiasi oggetto.

Imparare di più con le ricette

  • /cookbook/validation/custom_constraint