Validation Framework | Episodio 1 cover image

Validation Framework | Episodio 1

alberto • May 4, 2022

validation

La validazione degli input utente in Laravel è una componente particolarmente importante e utile del framework ed esaurirla con un semplice articolo era impossibile. Ho preferito quindi, sulla scia di Netflix e Prime Video, creare una serie di articoli di approfondimento.

Non so ancora quanti articoli usciranno, i contenuti sono tanti ed è difficile definirlo in anticipo.

Questo primo articolo si concentra sulle regole required, ovvero quelle che si occupano di controllare se gli input dell'utente sono correttamente popolati in termini di presenza. Nei prossimi episodi introdurremo altre caratteristiche del Validation Framework, legate piú al contenuto specifico.

Si validano array, non richieste utente

Iniziamo con una premessa: il Validation Framework di Laravel permette di validare strutture dati complesse, siano esse recuperate dalla request e quindi da un input utente o costruite programmaticamente all'interno del codice. Non dobbiamo quindi affrontare l'argomento pensando esclusivamente ad una form HTML, ma dobbiamo astrarci verso il concetto di struttura dati.

In particolare all'oggetto Validator, viene passato un array associativo, eventualmente multilivello, ed è su questo che dobbiamo spostare il nostro focus:

$validator = Validator::make([
    'title' => 'Laravello',
    'body' => 'il miglior blog su Laravel'
], [
    'title' => 'required|unique:posts|max:255',
    'body' => 'required',
]);

Per questa ragione, possono esistere diversi scenari:

Alcuni di questi scenari non sono possibili nel caso di validazione di richiesta HTTP, principalmente grazie ad alcuni middleware che puliscono per noi la request (per esempio ConvertEmptyStringsToNull che converte le stringhe vuote in null).

required

La regola principe legata a questo ambito è sicuramente required.

Questa regola verifica che la property sia presente e che nessuna delle seguenti condizioni siano vere:

public function test_required_missing()
{
    $validator = Validator::make([

    ], [
        'name' => 'required'
    ]);

    $this->assertTrue($validator->fails());
}

public function test_required_empty_string()
{
    $validator = Validator::make([
        'name' => ''
    ], [
        'name' => 'required'
    ]);

    $this->assertTrue($validator->fails());
}

public function test_required_null()
{
    $validator = Validator::make([
        'name' => null
    ], [
        'name' => 'required'
    ]);

    $this->assertTrue($validator->fails());
}

Le declinazioni di required

Considerata l'importanza di questa regola di validazione, il Validation Framework presenta una serie di altre regole che si basano sulla regola appena vista.

require_if e required_unless

Queste due regole sono complementari e permettono di rendere un campo obbligatorio sulla base del valore di un altro campo:

public function test_required_if()
{
    $validator = Validator::make([
        'age' => 10
    ], [
        'name' => 'required_if:age,10'
    ]);

    //diventa obbligatorio perchè age vale 10
    $this->assertTrue($validator->fails());

    $validator = Validator::make([
        'age' => 9
    ], [
        'name' => 'required_if:age,10'
    ]);

    //rimane opzionale perchè age vale 9
    $this->assertFalse($validator->fails());
}

public function test_required_unless()
{
    $validator = Validator::make([
        'age' => 10
    ], [
        'name' => 'required_unless:age,10'
    ]);

    //rimane opzionale perchè age vale 10
    $this->assertFalse($validator->fails());

    $validator = Validator::make([
        'age' => 9
    ], [
        'name' => 'required_unless:age,10'
    ]);

    //diventa obbligatorio perchè age non vale 10
    $this->assertTrue($validator->fails());
}

required_with, required_with_all, required_without, required_without_all

Questo secondo gruppo di regole permette di rendere un campo obbligatorio qualora altri campi siano valorizzati:

public function test_required_with()
{
    $validator = Validator::make([
        'age' => 10
    ], [
        'name' => 'required_with:age,city'
    ]);

    //diventa obbligatorio perchè age è valorizzato
    $this->assertTrue($validator->fails());

    $validator = Validator::make([

    ], [
        'name' => 'required_with:age,city'
    ]);

    //rimane opzionale perchè mancano age e city
    $this->assertFalse($validator->fails());
}

public function test_required_with_all()
{
    $validator = Validator::make([
        'age' => 10,
        'city' => 'Varese'
    ], [
        'name' => 'required_with_all:age,city'
    ]);

    //diventa obbligatorio perchè sia age che city sono valorizzati
    $this->assertTrue($validator->fails());

    $validator = Validator::make([
        'age' => 10
    ], [
        'name' => 'required_with_all:age,city'
    ]);

    //rimane opzionale perchè manca city
    $this->assertFalse($validator->fails());
}

/*
    ometto per leggibilità il test without e without_all 
    perchè opposti rispetto a with e with_all
*/

required_array_keys

Questa ultima declinazione permette di validare la presenza di chiavi all'interno di array annidiati.

public function test_required_array_keys()
{
    $validator = Validator::make([
        'author' => [
            'age' => 20
        ]
    ], [
        'author' => 'required_array_keys:name'
    ]);
    $this->assertTrue($validator->fails());

}

Prohibited e le sue declinazioni

prohibited rappresenta l'esatto opposto di required. Un campo con questa regola deve essere non valorizzato o completamente assente.

La regola presenta alcune declinazioni simili a quelle viste in precedenza:

public function test_prohibited()
{
    $validator = Validator::make([
        'name' => 'Alberto'
    ], [
        'name' => 'prohibited'
    ]);
    $this->assertTrue($validator->fails());

    $validator = Validator::make([
        'name' => ''
    ], [
        'name' => 'prohibited'
    ]);
    $this->assertFalse($validator->fails());

}

public function test_prohibited_if()
{
    $validator = Validator::make([
        'name' => 'Alberto',
        'age' => 10
    ], [
        'name' => 'prohibited_if:age,10'
    ]);
    $this->assertTrue($validator->fails());

    $validator = Validator::make([
        'name' => 'Alberto',
        'age' => 9
    ], [
        'name' => 'prohibited_if:age,10'
    ]);
    $this->assertFalse($validator->fails());

}

public function test_prohibited_unless()
{
    $validator = Validator::make([
        'name' => 'Alberto',
        'age' => 10
    ], [
        'name' => 'prohibited_unless:age,9'
    ]);
    $this->assertTrue($validator->fails());

    $validator = Validator::make([
        'name' => 'Alberto',
        'age' => 9
    ], [
        'name' => 'prohibited_unless:age,9'
    ]);
    $this->assertFalse($validator->fails());

}

public function test_prohibits()
{
    $validator = Validator::make([
        'name' => 'Alberto',
        'age' => 10
    ], [
        'name' => 'prohibits:age'
    ]);
    $this->assertTrue($validator->fails());

    $validator = Validator::make([
        'name' => 'Alberto'
    ], [
        'name' => 'prohibits:age'
    ]);
    $this->assertFalse($validator->fails());

}

Altre regole

L'argomento non è peró esaurito qua. Esistono altre regole, non strettamente correlate a required ma che insistono sulla presenza o meno di proprietà all'interno dell'array da validare.

filled

Con questa regola validiamo il fatto che il campo sia valorizzato solamente se presente. Qualora mancasse questa regola sarebbe comunque considerata valida.

public function test_filled()
{
    $validator = Validator::make([
        'name' => ''
    ], [
        'name' => 'filled'
    ]);
    //name non è valorizzato
    $this->assertTrue($validator->fails());

    $validator = Validator::make([
        'name' => 'Alberto'
    ], [
        'name' => 'filled'
    ]);
    //name è valorizzato
    $this->assertFalse($validator->fails());

    $validator = Validator::make([

    ], [
        'name' => 'filled'
    ]);
    //name non esiste
    $this->assertFalse($validator->fails());

}

present

Con questa regola validiamo il fatto che il campo sia presente, non ci interessa se valorizzato o meno.

public function test_present()
{
    $validator = Validator::make([

    ], [
        'name' => 'present'
    ]);
    //name non è presente
    $this->assertTrue($validator->fails());

    $validator = Validator::make([
        'name' => 'Alberto'
    ], [
        'name' => 'present'
    ]);
    //name è presente e valorizzato
    $this->assertFalse($validator->fails());

    $validator = Validator::make([
        'name' => ''
    ], [
        'name' => 'present'
    ]);
    //name è presente anche se non valorizzato
    $this->assertFalse($validator->fails());

}

nullable

La property che presenta questa regola puó essere valorizzata a null. Questa non è una vera regola, ma una specie di placeholder per indicare che il campo assume questa caratteristica e che eventuali altre regole addizionali non devono essere invocate qualora il campo non sia valorizzato.

public function test_nullable()
{

    $validator = Validator::make([
        'name' => null
    ], [
        'name' => 'nullable|string'
    ]);
    //essendo nullabile, evito di controllare che sia una stringa
    $this->assertFalse($validator->fails());

    $validator = Validator::make([
        'name' => null
    ], [
        'name' => 'string'
    ]);
    //null non è una stringa valida
    $this->assertTrue($validator->fails());


}

sometimes

Sulla falsa riga di nullable, anche sometimes non è una vera regola di validazione ma permette di indicare una property come non sempre disponibile e di applicare eventuali altre regole solamente se presente nell'array dei dati.

public function test_sometimes()
{

    $validator = Validator::make([
        'name' => null
    ], [
        'name' => 'sometimes|string'
    ]);
    //il campo è valorizzato, quindi mi aspetto una stringa
    $this->assertTrue($validator->fails());

    $validator = Validator::make([

    ], [
        'name' => 'sometimes|string'
    ]);
    //non valido name
    $this->assertFalse($validator->fails());

}

Tiriamo le somme

Per questo episodio ci fermiamo qui: non analizzeremo piú regole.

Se giunti a questo punto avete un po' di confusione in testa è normale. Gli scenari sono di piú di quelli che uno poteva immaginarsi 10 minuti fa.

Le regole sono appunto molteplici e varie perchè ci sono differenti possibilità. La maggior parte delle volte avrete a che fare solamente con le regole che si basano su required, ma è interessante che le altre sfaccettature siano già implementate nativamente.

Nel prossimo episodio introdurremo tutti i concetti per creare regole condizionali avanzate.

PS tutti i file dei test sono disponibili in questo Github Gist