Validarea formularelor
Elemente obligatorii
Elementele obligatorii le marcăm cu metoda setRequired()
, al cărei argument este textul mesajului de eroare, care se afișează dacă utilizatorul nu completează elementul. Dacă nu
specificăm argumentul, se va utiliza mesajul de eroare implicit.
$form->addText('name', 'Nume:')
->setRequired('Vă rugăm să introduceți numele');
Reguli
Regulile de validare le adăugăm elementelor cu metoda addRule()
. Primul parametru este regula, al doilea este
textul mesajului de eroare și al treilea este argumentul regulii de validare.
$form->addPassword('password', 'Parolă:')
->addRule($form::MinLength, 'Parola trebuie să aibă cel puțin %d caractere', 8);
Regulile de validare se verifică doar în cazul în care utilizatorul a completat elementul.
Nette vine cu o serie întreagă de reguli predefinite, ale căror nume sunt constante ale clasei
Nette\Forms\Form
. Pentru toate elementele putem folosi aceste reguli:
constantă | descriere | tip argument |
---|---|---|
Required |
element obligatoriu, alias pentru setRequired() |
– |
Filled |
element obligatoriu, alias pentru setRequired() |
– |
Blank |
elementul nu trebuie completat | – |
Equal |
valoarea este egală cu parametrul | mixed |
NotEqual |
valoarea nu este egală cu parametrul | mixed |
IsIn |
valoarea este egală cu unul dintre elementele din array | array |
IsNotIn |
valoarea nu este egală cu niciun element din array | array |
Valid |
elementul este completat corect? (pentru condiții) | – |
Intrări de text
Pentru elementele addText()
, addPassword()
, addTextArea()
, addEmail()
,
addInteger()
, addFloat()
se pot utiliza și unele dintre următoarele reguli:
MinLength |
lungimea minimă a textului | int |
MaxLength |
lungimea maximă a textului | int |
Length |
lungimea în interval sau lungimea exactă | pereche [int, int] sau int |
Email |
adresă de e-mail validă | – |
URL |
URL absolut | – |
Pattern |
se potrivește expresiei regulate | string |
PatternInsensitive |
ca Pattern , dar independent de majuscule/minuscule |
string |
Integer |
valoare întreagă | – |
Numeric |
alias pentru Integer |
– |
Float |
număr | – |
Min |
valoarea minimă a elementului numeric | int|float |
Max |
valoarea maximă a elementului numeric | int|float |
Range |
valoarea în interval | pereche [int|float, int|float] |
Regulile de validare Integer
, Numeric
și Float
convertesc direct valoarea la integer,
respectiv float. Mai mult, regula URL
acceptă și o adresă fără schemă (de ex. nette.org
) și
completează schema (https://nette.org
). Expresia din Pattern
și PatternIcase
trebuie să
fie valabilă pentru întreaga valoare, adică ca și cum ar fi încadrată de caracterele ^
și $
.
Numărul de elemente
Pentru elementele addMultiUpload()
, addCheckboxList()
, addMultiSelect()
se pot utiliza
și următoarele reguli pentru a limita numărul de elemente selectate, respectiv fișiere încărcate:
MinLength |
număr minim | int |
MaxLength |
număr maxim | int |
Length |
numărul în interval sau numărul exact | pereche [int, int] sau int |
Încărcarea fișierelor
Pentru elementele addUpload()
, addMultiUpload()
se pot utiliza și următoarele reguli:
MaxFileSize |
dimensiunea maximă a fișierului în octeți | int |
MimeType |
tip MIME, permise caractere wildcard ('video/*' ) |
string|string[] |
Image |
imagine JPEG, PNG, GIF, WebP, AVIF | – |
Pattern |
numele fișierului se potrivește expresiei regulate | string |
PatternInsensitive |
ca Pattern , dar independent de majuscule/minuscule |
string |
MimeType
și Image
necesită extensia PHP fileinfo
. Faptul că fișierul sau imaginea
este de tipul dorit este detectat pe baza semnăturii sale și nu verifică integritatea întregului fișier. Dacă
imaginea nu este deteriorată, se poate afla, de exemplu, încercând să o încărcați.
Mesaje de eroare
Toate regulile predefinite, cu excepția Pattern
și PatternInsensitive
, au un mesaj de eroare
implicit, deci acesta poate fi omis. Cu toate acestea, specificarea și formularea tuturor mesajelor personalizate va face
formularul mai prietenos pentru utilizator.
Puteți modifica mesajele implicite în configurație, editând
textele din array-ul Nette\Forms\Validator::$messages
sau folosind un translator.
În textul mesajelor de eroare se pot utiliza următorii substituenți:
%d |
înlocuiește succesiv cu argumentele regulii |
%n$d |
înlocuiește cu al n-lea argument al regulii |
%label |
înlocuiește cu eticheta elementului (fără două puncte) |
%name |
înlocuiește cu numele elementului (de ex. name ) |
%value |
înlocuiește cu valoarea introdusă de utilizator |
$form->addText('name', 'Nume:')
->setRequired('Vă rugăm să completați %label');
$form->addInteger('id', 'ID:')
->addRule($form::Range, 'cel puțin %d și cel mult %d', [5, 10]);
$form->addInteger('id', 'ID:')
->addRule($form::Range, 'cel mult %2$d și cel puțin %1$d', [5, 10]);
Condiții
Pe lângă reguli, se pot adăuga și condiții. Acestea se scriu similar cu regulile, doar că în loc de
addRule()
folosim metoda addCondition()
și, desigur, nu specificăm niciun mesaj de eroare (condiția
doar întreabă):
$form->addPassword('password', 'Parolă:')
// dacă parola nu este mai lungă de 8 caractere
->addCondition($form::MaxLength, 8)
// atunci trebuie să conțină o cifră
->addRule($form::Pattern, 'Trebuie să conțină o cifră', '.*[0-9].*');
Condiția poate fi legată și de alt element decât cel curent folosind addConditionOn()
. Ca prim parametru,
specificăm referința la element. În acest exemplu, e-mailul va fi obligatoriu doar dacă se bifează checkbox-ul (valoarea sa
va fi true):
$form->addCheckbox('newsletters', 'trimiteți-mi newslettere');
$form->addEmail('email', 'E-mail:')
// dacă checkbox-ul este bifat
->addConditionOn($form['newsletters'], $form::Equal, true)
// atunci solicită e-mail
->setRequired('Introduceți adresa de e-mail');
Din condiții se pot crea structuri complexe folosind elseCondition()
și endCondition()
:
$form->addText(/* ... */)
->addCondition(/* ... */) // dacă prima condiție este îndeplinită
->addConditionOn(/* ... */) // și a doua condiție pe un alt element
->addRule(/* ... */) // solicită această regulă
->elseCondition() // dacă a doua condiție nu este îndeplinită
->addRule(/* ... */) // solicită aceste reguli
->addRule(/* ... */)
->endCondition() // ne întoarcem la prima condiție
->addRule(/* ... */);
În Nette se poate reacționa foarte ușor la îndeplinirea sau neîndeplinirea condiției și pe partea de JavaScript folosind
metoda toggle()
, vezi javascript-dinamic.
Referință la alt element
Ca argument al regulii sau condiției se poate transmite și alt element al formularului. Regula va folosi atunci valoarea
introdusă ulterior de utilizator în browser. Astfel se poate valida dinamic, de exemplu, că elementul password
conține același șir ca elementul password_confirm
:
$form->addPassword('password', 'Parolă');
$form->addPassword('password_confirm', 'Confirmați parola')
->addRule($form::Equal, 'Parolele introduse nu se potrivesc', $form['password']);
Reguli și condiții personalizate
Uneori ajungem în situația în care regulile de validare încorporate în Nette nu sunt suficiente și trebuie să validăm datele de la utilizator în felul nostru. În Nette este foarte simplu!
Metodelor addRule()
sau addCondition()
li se poate transmite ca prim parametru orice callback. Acesta
primește ca prim parametru elementul însuși și returnează o valoare booleană care indică dacă validarea a avut loc cu
succes. La adăugarea unei reguli folosind addRule()
, se pot specifica și alte argumente, acestea fiind apoi
transmise ca al doilea parametru.
Putem crea astfel propriul set de validatori ca o clasă cu metode statice:
class MyValidators
{
// testează dacă valoarea este divizibilă cu argumentul
public static function validateDivisibility(BaseControl $input, $arg): bool
{
return $input->getValue() % $arg === 0;
}
public static function validateEmailDomain(BaseControl $input, $domain)
{
// alți validatori
}
}
Utilizarea este apoi foarte simplă:
$form->addInteger('num')
->addRule(
[MyValidators::class, 'validateDivisibility'],
'Valoarea trebuie să fie un multiplu al numărului %d',
8,
);
Regulile de validare personalizate pot fi adăugate și în JavaScript. Condiția este ca regula să fie o metodă statică.
Numele său pentru validatorul JavaScript se formează prin concatenarea numelui clasei fără backslash-uri \
, a
unui underscore _
și a numelui metodei. De ex. App\MyValidators::validateDivisibility
se scrie ca
AppMyValidators_validateDivisibility
și se adaugă la obiectul Nette.validators
:
Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
return val % args === 0;
};
Evenimentul onValidate
După trimiterea formularului, se efectuează validarea, în timpul căreia se verifică regulile individuale adăugate
folosind addRule()
și apoi se declanșează evenimentul
onValidate
. Handler-ul său poate fi utilizat pentru validare suplimentară, de obicei verificarea combinației
corecte de valori în mai multe elemente ale formularului.
Dacă se descoperă o eroare, o transmitem formularului prin metoda addError()
. Aceasta poate fi apelată fie pe
un element specific, fie direct pe formular.
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('Această combinație nu este posibilă.');
}
}
Erori în timpul procesării
În multe cazuri, aflăm despre eroare abia în momentul în care procesăm formularul valid, de exemplu, scriem un nou element
în baza de date și întâlnim o duplicitate a cheilor. În acest caz, transmitem din nou eroarea formularului prin metoda
addError()
. Aceasta poate fi apelată fie pe un element specific, fie direct pe formular:
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('Parolă invalidă.');
}
}
Dacă este posibil, recomandăm atașarea erorii direct la elementul formularului, deoarece aceasta va fi afișată lângă el la utilizarea renderer-ului implicit.
$form['date']->addError('Ne pare rău, dar această dată este deja ocupată.');
Puteți apela addError()
în mod repetat pentru a transmite formularului sau elementului mai multe mesaje de
eroare. Le puteți obține folosind getErrors()
.
Atenție, $form->getErrors()
returnează un sumar al tuturor mesajelor de eroare, inclusiv cele transmise
direct elementelor individuale, nu doar direct formularului. Mesajele de eroare transmise doar formularului le puteți obține
prin $form->getOwnErrors()
.
Modificarea intrării
Folosind metoda addFilter()
, putem modifica valoarea introdusă de utilizator. În acest exemplu, vom tolera și
elimina spațiile din codul poștal:
$form->addText('zip', 'Cod poștal:')
->addFilter(function ($value) {
return str_replace(' ', '', $value); // eliminăm spațiile din codul poștal
})
->addRule($form::Pattern, 'Codul poștal nu este în format de cinci cifre', '\d{5}');
Filtrul se integrează între regulile și condițiile de validare, deci ordinea metodelor contează, adică filtrul și regula
se apelează în ordinea în care sunt metodele addFilter()
și addRule()
.
Validare JavaScript
Limbajul pentru formularea condițiilor și regulilor este foarte puternic. Toate construcțiile funcționează atât pe partea
de server, cât și pe partea de JavaScript. Acestea sunt transmise în atributele HTML data-nette-rules
ca JSON.
Validarea propriu-zisă este apoi efectuată de un script care interceptează evenimentul submit
al formularului,
parcurge elementele individuale și efectuează validarea corespunzătoare.
Acest script este netteForms.js
și este disponibil din mai multe surse posibile:
Puteți include scriptul direct în pagina HTML de pe CDN:
<script src="https://unpkg.com/nette-forms@3"></script>
Sau copiați-l local în folderul public al proiectului (de ex. din
vendor/nette/forms/src/assets/netteForms.min.js
):
<script src="/path/to/netteForms.min.js"></script>
Sau instalați-l prin npm:
npm install nette-forms
Și apoi încărcați-l și rulați-l:
import netteForms from 'nette-forms';
netteForms.initOnLoad();
Alternativ, îl puteți încărca direct din folderul vendor
:
import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();
JavaScript dinamic
Doriți să afișați câmpurile pentru introducerea adresei doar dacă utilizatorul alege să primească produsul prin
poștă? Nicio problemă. Cheia este perechea de metode addCondition()
& toggle()
:
$form->addCheckbox('send_it')
->addCondition($form::Equal, true)
->toggle('#address-container');
Acest cod spune că atunci când condiția este îndeplinită, adică atunci când checkbox-ul este bifat, elementul HTML
#address-container
va fi vizibil. Și invers. Elementele formularului cu adresa destinatarului le vom plasa astfel
într-un container cu acest ID, iar la clic pe checkbox, acestea se vor ascunde sau afișa. Acest lucru este asigurat de scriptul
netteForms.js
.
Ca argument al metodei toggle()
se poate transmite orice selector. Din motive istorice, un șir alfanumeric fără
alte caractere speciale este înțeles ca ID-ul elementului, adică la fel ca și cum ar fi precedat de caracterul #
.
Al doilea parametru opțional permite inversarea comportamentului, adică dacă am folosi
toggle('#address-container', false)
, elementul s-ar afișa doar dacă checkbox-ul nu ar fi bifat.
Implementarea implicită în JavaScript modifică proprietatea hidden
a elementelor. Putem însă schimba ușor
comportamentul, de exemplu, adăugând o animație. Este suficient să suprascriem în JavaScript metoda
Nette.toggle
cu propria soluție:
Nette.toggle = (selector, visible, srcElement, event) => {
document.querySelectorAll(selector).forEach((el) => {
// ascundem sau afișăm 'el' în funcție de valoarea 'visible'
});
};
Dezactivarea validării
Uneori poate fi util să dezactivăm validarea. Dacă apăsarea butonului de trimitere nu trebuie să efectueze validarea
(potrivit pentru butoanele Cancel sau Preview), o dezactivăm cu metoda
$submit->setValidationScope([])
. Dacă trebuie să efectueze doar o validare parțială, putem specifica ce
câmpuri sau containere de formular trebuie validate.
$form->addText('name')
->setRequired();
$details = $form->addContainer('details');
$details->addInteger('age')
->setRequired('age');
$details->addInteger('age2')
->setRequired('age2');
$form->addSubmit('send1'); // Validează întregul formular
$form->addSubmit('send2')
->setValidationScope([]); // Nu validează deloc
$form->addSubmit('send3')
->setValidationScope([$form['name']]); // Validează doar elementul name
$form->addSubmit('send4')
->setValidationScope([$form['details']['age']]); // Validează doar elementul age
$form->addSubmit('send5')
->setValidationScope([$form['details']]); // Validează containerul details
setValidationScope
nu afectează evenimentul onValidate al formularului,
care va fi apelat întotdeauna. Evenimentul onValidate
al containerului va fi declanșat doar dacă acest container
este marcat pentru validare parțială.