Validazione dei form
Controlli obbligatori
I controlli si contrassegnano come obbligatori con il metodo setRequired(). Il suo argomento è il testo del messaggio di errore che verrà mostrato se l'utente non compila il controllo. Se non viene
indicato alcun argomento, viene usato il messaggio di errore predefinito.
$form->addText('name', 'Nome:')
->setRequired('Inserite il vostro nome.');
Regole
Aggiungiamo regole di validazione ai controlli con il metodo addRule(). Il primo parametro è la regola, il
secondo è il messaggio di errore e il terzo è l'argomento della regola di validazione.
$form->addPassword('password', 'Password:')
->addRule($form::MinLength, 'La password deve essere lunga almeno %d caratteri', 8);
Le regole di validazione vengono controllate solo se l'utente ha compilato il controllo.
Nette porta con sé diverse regole predefinite, i cui nomi sono costanti della classe Nette\Forms\Form. Possiamo
applicare queste regole a tutti i controlli:
| costante | descrizione | tipo dell'argomento |
|---|---|---|
Required |
controllo obbligatorio, alias di setRequired() |
– |
Filled |
controllo obbligatorio, alias di setRequired() |
– |
Blank |
il controllo non deve essere compilato | – |
Equal |
il valore deve essere uguale al parametro | mixed |
NotEqual |
il valore non deve essere uguale al parametro | mixed |
IsIn |
il valore deve essere uno degli elementi dell'array | array |
IsNotIn |
il valore non deve essere nessuno degli elementi dell'array | array |
Valid |
il controllo è compilato correttamente? (solo in addConditionOn()) | – |
Campi di testo
Ai controlli addText(), addPassword(), addTextArea(), addEmail(),
addInteger(), addFloat() si possono applicare anche alcune delle regole seguenti:
MinLength |
lunghezza minima del testo | int |
MaxLength |
lunghezza massima del testo | int |
Length |
lunghezza in un intervallo o lunghezza esatta | coppia [int, int] oppure int |
Email |
indirizzo e-mail valido | – |
URL |
URL assoluto | – |
Pattern |
corrisponde a un'espressione regolare | string |
PatternInsensitive |
come Pattern, ma senza distinguere maiuscole e minuscole |
string |
Integer |
valore intero | – |
Numeric |
intero non negativo (solo cifre) | – |
Float |
numero | – |
Min |
valore minimo di un controllo numerico | int|float |
Max |
valore massimo di un controllo numerico | int|float |
Range |
valore in un intervallo | coppia [int|float, int|float] |
Le regole di validazione Integer e Float convertono automaticamente il valore rispettivamente in
intero o in float. La regola URL accetta inoltre anche un indirizzo senza schema (per esempio
nette.org) e ne completa lo schema (https://nette.org). L'espressione in Pattern e
PatternInsensitive deve valere per l'intero valore, cioè come se fosse racchiusa tra i caratteri ^ e
$.
Numero di elementi
Ai controlli addMultiUpload(), addCheckboxList(), addMultiSelect() potete applicare
anche le regole seguenti, per limitare il numero di elementi selezionati o di file caricati:
MinLength |
numero minimo | int |
MaxLength |
numero massimo | int |
Length |
numero in un intervallo o numero esatto | coppia [int, int] oppure int |
Upload di file
Ai controlli addUpload(), addMultiUpload() si possono applicare anche le regole seguenti:
MaxFileSize |
dimensione massima del file in byte | int |
MimeType |
tipo MIME, sono ammessi i caratteri jolly ('video/*') |
string|string[] |
Image |
immagine JPEG, PNG, GIF, WebP, AVIF | – |
Pattern |
il nome del file corrisponde a un'espressione regolare | string |
PatternInsensitive |
come Pattern, ma senza distinguere maiuscole e minuscole |
string |
MimeType e Image richiedono l'estensione PHP fileinfo. Se un file o un'immagine siano
del tipo richiesto viene rilevato in base alla loro firma, e l'integrità dell'intero file non viene controllata. Potete
stabilire se un'immagine è danneggiata, per esempio, provando a caricarla.
Messaggi di errore
Tutte le regole predefinite tranne Pattern e PatternInsensitive hanno un messaggio di errore
predefinito, quindi si può omettere. Indicando e formulando tutti i messaggi personalizzati secondo le vostre esigenze, però,
renderete il form più amichevole.
Potete cambiare i messaggi predefiniti nella configurazione,
modificando i testi nell'array Nette\Forms\Validator::$messages, oppure usando un traduttore.
Nel testo dei messaggi di errore si possono usare i segnaposto seguenti:
%d |
sostituito in sequenza dagli argomenti della regola |
%n$d |
sostituito dall'n-esimo argomento della regola |
%label |
sostituito dall'etichetta del controllo (senza i due punti) |
%name |
sostituito dal nome del controllo (per esempio name) |
%value |
sostituito dal valore inserito dall'utente |
$form->addText('name', 'Nome:')
->setRequired('Compilate %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 si possono aggiungere anche delle condizioni. Si scrivono come le regole, ma al posto di
addRule() usiamo il metodo addCondition() e naturalmente non indichiamo un messaggio di errore (la
condizione si limita a chiedere):
$form->addPassword('password', 'Password:')
// se la lunghezza della password non supera 8
->addCondition($form::MaxLength, 8)
// allora deve contenere una cifra
->addRule($form::Pattern, 'Deve contenere una cifra', '.*[0-9].*');
La condizione si può legare a un controllo diverso da quello corrente con addConditionOn(). Il primo parametro è
un riferimento al controllo. In questo esempio l'e-mail sarà obbligatoria solo se la checkbox è selezionata (cioè se il suo
valore è true):
$form->addCheckbox('newsletters', 'Inviatemi le newsletter');
$form->addEmail('email', 'Email:')
// se la checkbox è selezionata
->addConditionOn($form['newsletters'], $form::Equal, true)
// allora richiedi l'e-mail
->setRequired('Inserite il vostro indirizzo e-mail');
Le condizioni si possono comporre in strutture complesse con elseCondition() ed endCondition():
$form->addText(/* ... */)
->addCondition(/* ... */) // se la prima condizione è soddisfatta
->addConditionOn(/* ... */) // ed è soddisfatta anche la seconda condizione su un altro controllo
->addRule(/* ... */) // richiedi questa regola
->elseCondition() // se la seconda condizione non è soddisfatta
->addRule(/* ... */) // richiedi queste regole
->addRule(/* ... */)
->endCondition() // torniamo alla prima condizione
->addRule(/* ... */);
Il primo argomento di addCondition() può essere anche un valore booleano. È utile quando la decisione è già
nota mentre il form viene costruito, per esempio per applicare una regola solo in certe circostanze:
$form->addText('nickname')
->addCondition($isRequired) // un valore noto al momento della costruzione del form
->setRequired();
In Nette è molto semplice reagire dal lato JavaScript al fatto che una condizione sia soddisfatta o meno, con il metodo
toggle(), vedi JavaScript dinamico.
Riferimento a un altro controllo
Come argomento di una regola o di una condizione potete passare anche un altro controllo del form. La regola userà allora il
valore che l'utente inserirà in seguito nel browser. Lo si può usare, per esempio, per validare dinamicamente che il controllo
password contenga la stessa stringa del controllo password_confirm:
$form->addPassword('password', 'Password');
$form->addPassword('password_confirm', 'Conferma la password')
->addRule($form::Equal, 'Le password non coincidono', $form['password']);
Regole e condizioni personalizzate
A volte incontriamo situazioni in cui le regole di validazione integrate in Nette non bastano e dobbiamo validare i dati dell'utente a modo nostro. In Nette è semplicissimo!
Come primo parametro dei metodi addRule() o addCondition() potete passare qualsiasi callback. La
callback accetta come primo parametro il controllo stesso e restituisce un valore booleano che indica se la validazione è
riuscita. Aggiungendo una regola con addRule() si possono indicare argomenti aggiuntivi, che vengono poi passati come
secondo parametro.
Un insieme personalizzato di validatori si può quindi creare come 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 è poi molto immediato:
$form->addInteger('num')
->addRule(
[MyValidators::class, 'validateDivisibility'],
'Il valore deve essere un multiplo di %d',
8,
);
Le regole di validazione personalizzate si possono aggiungere anche a JavaScript. La condizione è che la regola sia un metodo
statico. Il suo nome per il validatore JavaScript si forma concatenando il nome della classe senza le barre rovesciate
\, un trattino basso _ e il nome del metodo. Per esempio
App\MyValidators::validateDivisibility si scrive come AppMyValidators_validateDivisibility e si aggiunge
all'oggetto Nette.validators:
Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
return val % args === 0;
};
Evento onValidate
Dopo l'invio del form avviene la validazione, che controlla le singole regole aggiunte con addRule(), e viene poi
scatenato l'evento onValidate. Il suo gestore si può
usare per una validazione aggiuntiva, di norma per verificare la corretta combinazione di valori in più controlli del form.
Se viene rilevato un errore, lo si passa al form con il metodo addError(). Lo si può chiamare su un controllo
specifico oppure direttamente sul form.
protected function createComponentSignInForm(): Form
{
$form = new Form;
// ...
$form->onValidate[] = $this->validateSignInForm(...);
return $form;
}
private function validateSignInForm(Form $form, \stdClass $data): void
{
if ($data->foo > 1 && $data->bar > 5) {
$form->addError('Questa combinazione non è possibile.');
}
}
Elaborare gli errori
In molti casi scopriamo un errore solo elaborando un form valido, per esempio scrivendo un nuovo record nel database e
incontrando una chiave duplicata. In tal caso passiamo di nuovo l'errore al form con il metodo addError(). Lo si può
chiamare su un controllo specifico oppure 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, consigliamo di aggiungere l'errore direttamente al controllo del form, perché con il renderer predefinito verrà mostrato accanto a esso.
$form['date']->addError('Spiacenti, questa data è già occupata.');
Potete chiamare addError() più volte per passare più messaggi di errore a un form o a un controllo. Potete
ottenerli con getErrors().
Attenzione: $form->getErrors() restituisce il riepilogo di tutti i messaggi di errore, compresi quelli passati
direttamente ai singoli controlli, non solo quelli passati direttamente al form. I messaggi di errore passati solo al form si
ottengono con $form->getOwnErrors().
Modificare i valori inseriti
Con il metodo addFilter() possiamo modificare il valore inserito dall'utente. In questo esempio tolleriamo e
rimuoviamo gli spazi nel codice postale:
$form->addText('zip', 'Codice postale:')
->addFilter(function ($value) {
return str_replace(' ', '', $value); // rimuove gli spazi dal codice postale
})
->addRule($form::Pattern, 'Il codice postale non è di cinque cifre', '\d{5}');
Il filtro si integra tra le regole di validazione e le condizioni, quindi l'ordine dei metodi conta: il filtro e la regola
vengono chiamati nello stesso ordine in cui sono elencati i metodi addFilter() e addRule().
Validazione JavaScript
Il linguaggio per formulare condizioni e regole è molto potente. Tutti i costrutti funzionano sia sul lato server sia sul
lato client, in JavaScript. Vengono trasferiti negli attributi HTML data-nette-rules come JSON. Della validazione
vera e propria si occupa uno script che intercetta l'evento submit del form, scorre i singoli controlli ed esegue la
validazione corrispondente.
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 vostro progetto (per esempio da
vendor/nette/forms/src/assets/netteForms.min.js):
<script src="/path/to/netteForms.min.js"></script>
Oppure installarlo con npm:
npm install nette-forms
E poi 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();
Potete disattivare del tutto la validazione lato client aggiungendo al form l'attributo novalidate. Lo script
netteForms.js salta allora la validazione all'invio, quindi la validazione avviene solo sul server:
$form->setHtmlAttribute('novalidate');
JavaScript dinamico
Volete mostrare i campi dell'indirizzo solo se l'utente sceglie di farsi spedire la merce per posta? Nessun problema. La
chiave è la coppia di metodi addCondition() e 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. Collochiamo quindi i controlli con l'indirizzo del destinatario in
un container con questo ID, e si nasconderanno o si mostreranno quando la checkbox viene cliccata. Se ne occupa lo script
netteForms.js.
Come argomento del metodo toggle() si può passare qualsiasi selettore. Per motivi storici, una stringa che inizia
con una lettera, una cifra o un trattino basso e contiene solo lettere, cifre, trattini bassi, trattini, punti e due punti viene
trattata come un ID di elemento, come se fosse preceduta dal carattere #. Il secondo parametro facoltativo permette
di invertire il comportamento; se per esempio usassimo toggle('#address-container', false), l'elemento verrebbe
mostrato solo se la checkbox non fosse selezionata.
L'implementazione JavaScript predefinita cambia la proprietà hidden degli elementi. Possiamo però cambiare
facilmente il comportamento, per esempio aggiungendo un'animazione. Basta sovrascrivere in JavaScript il metodo
Nette.toggle con una soluzione personalizzata:
Nette.toggle = (selector, visible, srcElement, event) => {
document.querySelectorAll(selector).forEach((el) => {
// nasconde o mostra 'el' in base al valore di 'visible'
});
};
Disattivare la validazione
A volte può essere utile disattivare la validazione. Se premere un pulsante di invio non deve eseguire la validazione (adatto
ai pulsanti Annulla o Anteprima), la disattiviamo con il metodo $submit->setValidationScope([]). Se
deve eseguire solo una validazione parziale, possiamo indicare quali campi o container del form vadano validati.
$form->addText('name')
->setRequired();
$details = $form->addContainer('details');
$details->addInteger('age')
->setRequired('age');
$details->addInteger('age2')
->setRequired('age2');
$form->addSubmit('send1'); // valida l'intero form
$form->addSubmit('send2')
->setValidationScope([]); // non valida nulla
$form->addSubmit('send3')
->setValidationScope([$form['name']]); // valida solo il controllo 'name'
$form->addSubmit('send4')
->setValidationScope([$form['details']['age']]); // valida solo il controllo 'age'
$form->addSubmit('send5')
->setValidationScope([$form['details']]); // valida il container 'details'
setValidationScope non influisce sull'evento onValidate del form, che verrà
sempre chiamato. L'evento onValidate di un container verrà scatenato solo se quel container è contrassegnato per la
validazione parziale.
La validazione parziale influisce anche sui valori restituiti da getValues(): il risultato contiene solo i valori
dei controlli che rientrano nell'ambito della validazione. I valori dei controlli fuori da questo ambito vengono omessi.