Validazione dei Form

Elementi Obbligatori

Gli elementi obbligatori vengono contrassegnati con il metodo setRequired(), il cui argomento è il testo del Messaggi di errore che verrà visualizzato se l'utente non compila l'elemento. Se l'argomento non viene fornito, verrà utilizzato il messaggio di errore predefinito.

$form->addText('name', 'Nome:')
	->setRequired('Inserisci un nome');

Regole

Aggiungiamo regole di validazione agli elementi usando il metodo addRule(). Il primo parametro è la regola, il secondo è il testo del Messaggi di errore e il terzo è l'argomento della regola di validazione.

$form->addPassword('password', 'Password:')
	->addRule($form::MinLength, 'La password deve contenere almeno %d caratteri', 8);

Le regole di validazione vengono verificate solo se l'utente ha compilato l'elemento.

Nette include una serie di regole predefinite, i cui nomi sono costanti della classe Nette\Forms\Form. Possiamo usare queste regole per tutti gli elementi:

costante descrizione tipo argomento
Required elemento obbligatorio, alias per setRequired()
Filled elemento obbligatorio, alias per setRequired()
Blank l'elemento non deve essere compilato
Equal il valore è uguale al parametro mixed
NotEqual il valore non è uguale al parametro mixed
IsIn il valore è uguale a uno degli elementi nell'array array
IsNotIn il valore non è uguale a nessuno degli elementi nell'array array
Valid l'elemento è compilato correttamente? (per Condizioni)

Input di Testo

Per gli elementi addText(), addPassword(), addTextArea(), addEmail(), addInteger(), addFloat() è possibile utilizzare anche alcune delle seguenti regole:

MinLength lunghezza minima del testo int
MaxLength lunghezza massima del testo int
Length lunghezza nell'intervallo o lunghezza esatta coppia [int, int] o int
Email indirizzo e-mail valido
URL URL assoluto
Pattern corrisponde all'espressione regolare string
PatternInsensitive come Pattern, ma case-insensitive string
Integer valore intero
Numeric alias per Integer
Float numero
Min valore minimo dell'elemento numerico int|float
Max valore massimo dell'elemento numerico int|float
Range valore nell'intervallo coppia [int|float, int|float]

Le regole di validazione Integer, Numeric e Float convertono direttamente il valore in integer o float rispettivamente. Inoltre, la regola URL accetta anche un indirizzo senza schema (es. nette.org) e aggiunge lo schema (https://nette.org). L'espressione in Pattern e PatternIcase deve corrispondere all'intero valore, cioè come se fosse racchiusa tra i caratteri ^ e $.

Numero di Elementi

Per gli elementi addMultiUpload(), addCheckboxList(), addMultiSelect() è possibile utilizzare anche le seguenti regole per limitare il numero di elementi selezionati o file caricati:

MinLength numero minimo int
MaxLength numero massimo int
Length numero nell'intervallo o numero esatto coppia [int, int] o int

Upload di File

Per gli elementi addUpload(), addMultiUpload() è possibile utilizzare anche le seguenti regole:

MaxFileSize dimensione massima del file in byte int
MimeType tipo MIME, consentiti caratteri jolly ('video/*') string|string[]
Image immagine JPEG, PNG, GIF, WebP, AVIF
Pattern il nome del file corrisponde all'espressione regolare string
PatternInsensitive come Pattern, ma case-insensitive string

MimeType e Image richiedono l'estensione PHP fileinfo. Rilevano se un file o un'immagine è del tipo richiesto in base alla sua firma e non verificano l'integrità dell'intero file. È possibile verificare se un'immagine è danneggiata, ad esempio, provando a caricarla.

Messaggi di Errore

Tutte le regole predefinite, ad eccezione di Pattern e PatternInsensitive, hanno un messaggio di errore predefinito, quindi può essere omesso. Tuttavia, specificando e formulando tutti i messaggi su misura, renderete il form più user-friendly.

Potete modificare i messaggi predefiniti nella configurazione, modificando i testi nell'array Nette\Forms\Validator::$messages o utilizzando un traduttore.

Nel testo dei messaggi di errore è possibile utilizzare le seguenti stringhe segnaposto:

%d sostituisce progressivamente con gli argomenti della regola
%n$d sostituisce con l'n-esimo argomento della regola
%label sostituisce con l'etichetta dell'elemento (senza i due punti)
%name sostituisce con il nome dell'elemento (es. name)
%value sostituisce con il valore inserito dall'utente
$form->addText('name', 'Nome:')
	->setRequired('Compila %label');

$form->addInteger('id', 'ID:')
	->addRule($form::Range, 'almeno %d e al massimo %d', [5, 10]);

$form->addInteger('id', 'ID:')
	->addRule($form::Range, 'al massimo %2$d e almeno %1$d', [5, 10]);

Condizioni

Oltre alle regole, è possibile aggiungere anche condizioni. Queste si scrivono in modo simile alle regole, ma invece di addRule() usiamo il metodo addCondition() e ovviamente non specifichiamo alcun messaggio di errore (la condizione si limita a chiedere):

$form->addPassword('password', 'Password:')
	// se la password non è più lunga di 8 caratteri
	->addCondition($form::MaxLength, 8)
		// allora deve contenere un numero
		->addRule($form::Pattern, 'Deve contenere un numero', '.*[0-9].*');

La condizione può essere legata anche a un elemento diverso da quello corrente usando addConditionOn(). Come primo parametro, specifichiamo un riferimento all'elemento. In questo esempio, l'e-mail sarà obbligatoria solo se la checkbox è selezionata (il suo valore sarà true):

$form->addCheckbox('newsletters', 'inviami le newsletter');

$form->addEmail('email', 'E-mail:')
	// se la checkbox è selezionata
	->addConditionOn($form['newsletters'], $form::Equal, true)
		// allora richiedi l'e-mail
		->setRequired('Inserisci l\'indirizzo e-mail');

È possibile creare strutture complesse di condizioni usando elseCondition() e endCondition():

$form->addText(/* ... */)
	->addCondition(/* ... */) // se la prima condizione è soddisfatta
		->addConditionOn(/* ... */) // e la seconda condizione su un altro elemento
			->addRule(/* ... */) // richiedi questa regola
		->elseCondition() // se la seconda condizione non è soddisfatta
			->addRule(/* ... */) // richiedi queste regole
			->addRule(/* ... */)
		->endCondition() // torniamo alla prima condizione
		->addRule(/* ... */);

In Nette è molto facile reagire al soddisfacimento o meno di una condizione anche lato JavaScript usando il metodo toggle(), vedi JavaScript dinamico.

Riferimento a un Altro Elemento

Come argomento di una regola o condizione, è possibile passare anche un altro elemento del form. La regola utilizzerà quindi il valore inserito successivamente dall'utente nel browser. In questo modo è possibile, ad esempio, validare dinamicamente che l'elemento password contenga la stessa stringa dell'elemento password_confirm:

$form->addPassword('password', 'Password');
$form->addPassword('password_confirm', 'Conferma password')
    ->addRule($form::Equal, 'Le password inserite non corrispondono', $form['password']);

Regole e Condizioni Personalizzate

A volte ci troviamo in una situazione in cui le regole di validazione integrate in Nette non sono sufficienti e abbiamo bisogno di validare i dati dell'utente a modo nostro. In Nette è molto semplice!

Ai metodi addRule() o addCondition() è possibile passare qualsiasi callback come primo parametro. Questo riceve l'elemento stesso come primo parametro e restituisce un valore booleano che indica se la validazione è andata a buon fine. Quando si aggiunge una regola usando addRule(), è possibile specificare anche altri argomenti, che vengono poi passati come secondo parametro.

Possiamo quindi creare il nostro set di validatori come una classe con metodi statici:

class MyValidators
{
	// verifica se il valore è divisibile per l'argomento
	public static function validateDivisibility(BaseControl $input, $arg): bool
	{
		return $input->getValue() % $arg === 0;
	}

	public static function validateEmailDomain(BaseControl $input, $domain)
	{
		// altri validatori
	}
}

L'uso è quindi molto semplice:

$form->addInteger('num')
	->addRule(
		[MyValidators::class, 'validateDivisibility'],
		'Il valore deve essere un multiplo di %d',
		8,
	);

Le regole di validazione personalizzate possono essere aggiunte anche a JavaScript. La condizione è che la regola sia un metodo statico. Il suo nome per il validatore JavaScript viene creato unendo il nome della classe senza backslash \, un underscore _ e il nome del metodo. Ad esempio, App\MyValidators::validateDivisibility lo scriviamo come AppMyValidators_validateDivisibility e lo aggiungiamo all'oggetto Nette.validators:

Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
	return val % args === 0;
};

Evento onValidate

Dopo l'invio del form, viene eseguita la validazione, durante la quale vengono controllate le singole regole aggiunte tramite addRule() e successivamente viene attivato l'evento onValidate. Il suo handler può essere utilizzato per una validazione aggiuntiva, tipicamente per verificare la corretta combinazione di valori in più elementi del form.

Se viene rilevato un errore, lo passiamo al form usando il metodo addError(). Questo può essere chiamato su un elemento specifico o direttamente sul form.

protected function createComponentSignInForm(): Form
{
	$form = new Form;
	// ...
	$form->onValidate[] = [$this, 'validateSignInForm'];
	return $form;
}

public function validateSignInForm(Form $form, \stdClass $data): void
{
	if ($data->foo > 1 && $data->bar > 5) {
		$form->addError('Questa combinazione non è possibile.');
	}
}

Errori durante l'Elaborazione

In molti casi, veniamo a conoscenza di un errore solo nel momento in cui elaboriamo un form valido, ad esempio quando inseriamo un nuovo elemento nel database e incontriamo una duplicazione di chiavi. In tal caso, passiamo nuovamente l'errore al form usando il metodo addError(). Questo può essere chiamato su un elemento specifico o direttamente sul form:

try {
	$data = $form->getValues();
	$this->user->login($data->username, $data->password);
	$this->redirect('Home:');

} catch (Nette\Security\AuthenticationException $e) {
	if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) {
		$form->addError('Password non valida.');
	}
}

Se possibile, si consiglia di allegare l'errore direttamente all'elemento del form, poiché verrà visualizzato accanto ad esso quando si utilizza il renderer predefinito.

$form['date']->addError('Siamo spiacenti, ma questa data è già occupata.');

Potete chiamare addError() ripetutamente per passare più messaggi di errore al form o all'elemento. Li ottenete usando getErrors().

Attenzione, $form->getErrors() restituisce un riepilogo di tutti i messaggi di errore, anche quelli passati direttamente ai singoli elementi, non solo direttamente al form. I messaggi di errore passati solo al form si ottengono tramite $form->getOwnErrors().

Modifica dell'Input

Usando il metodo addFilter() possiamo modificare il valore inserito dall'utente. In questo esempio, tollereremo e rimuoveremo gli spazi nel CAP (Codice di Avviamento Postale):

$form->addText('zip', 'CAP:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // rimuoviamo gli spazi dal CAP
	})
	->addRule($form::Pattern, 'Il CAP non è nel formato di cinque cifre', '\d{5}');

Il filtro viene inserito tra le regole di validazione e le condizioni, quindi l'ordine dei metodi è importante, cioè il filtro e la regola vengono chiamati nello stesso ordine in cui sono presenti i metodi addFilter() e addRule().

Validazione JavaScript

Il linguaggio per formulare condizioni e regole è molto potente. Tutte le costruzioni funzionano sia lato server che lato JavaScript. Vengono trasmesse negli attributi HTML data-nette-rules come JSON. La validazione stessa viene quindi eseguita da uno script che intercetta l'evento submit del form, scorre i singoli elementi ed esegue la validazione appropriata.

Questo script è netteForms.js ed è disponibile da diverse fonti possibili:

Potete inserire lo script direttamente nella pagina HTML da una CDN:

<script src="https://unpkg.com/nette-forms@3"></script>

Oppure copiarlo localmente nella cartella pubblica del progetto (ad es. da vendor/nette/forms/src/assets/netteForms.min.js):

<script src="/path/to/netteForms.min.js"></script>

Oppure installarlo tramite npm:

npm install nette-forms

E successivamente caricarlo ed eseguirlo:

import netteForms from 'nette-forms';
netteForms.initOnLoad();

In alternativa, potete caricarlo direttamente dalla cartella vendor:

import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();

JavaScript Dinamico

Volete visualizzare i campi per l'inserimento dell'indirizzo solo se l'utente sceglie di spedire la merce per posta? Nessun problema. La chiave è la coppia di metodi addCondition() & toggle():

$form->addCheckbox('send_it')
	->addCondition($form::Equal, true)
		->toggle('#address-container');

Questo codice dice che quando la condizione è soddisfatta, cioè quando la checkbox è selezionata, l'elemento HTML #address-container sarà visibile. E viceversa. Quindi, posizioniamo gli elementi del form con l'indirizzo del destinatario in un container con questo ID e, facendo clic sulla checkbox, verranno nascosti o visualizzati. Questo è gestito dallo script netteForms.js.

Come argomento del metodo toggle() è possibile passare qualsiasi selettore. Per motivi storici, una stringa alfanumerica senza altri caratteri speciali viene intesa come ID dell'elemento, cioè come se fosse preceduta dal carattere #. Il secondo parametro opzionale consente di invertire il comportamento, cioè se usassimo toggle('#address-container', false), l'elemento verrebbe visualizzato solo se la checkbox non fosse selezionata.

L'implementazione predefinita in JavaScript modifica la proprietà hidden degli elementi. Tuttavia, possiamo facilmente modificare il comportamento, ad esempio aggiungendo un'animazione. Basta sovrascrivere il metodo Nette.toggle in JavaScript con la propria soluzione:

Nette.toggle = (selector, visible, srcElement, event) => {
	document.querySelectorAll(selector).forEach((el) => {
		// nascondiamo o mostriamo 'el' in base al valore 'visible'
	});
};

Disabilitazione della Validazione

A volte può essere utile disabilitare la validazione. Se la pressione del pulsante di invio non deve eseguire la validazione (adatto per i pulsanti AnnullaAnteprima), la disabilitiamo con il metodo $submit->setValidationScope([]). Se deve eseguire solo una validazione parziale, possiamo specificare quali campi o container del form devono essere validati.

$form->addText('name')
	->setRequired();

$details = $form->addContainer('details');
$details->addInteger('age')
	->setRequired('età');
$details->addInteger('age2')
	->setRequired('età2');

$form->addSubmit('send1'); // Valida l'intero form
$form->addSubmit('send2')
	->setValidationScope([]); // Non valida affatto
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // Valida solo l'elemento name
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Valida solo l'elemento age
$form->addSubmit('send5')
	->setValidationScope([$form['details']]); // Valida il container details

setValidationScope non influisce sull'evento onValidate del form, che verrà chiamato sempre. L'evento onValidate del container verrà attivato solo se questo container è contrassegnato per la validazione parziale.

versione: 4.0