Formulare utilizate Standalone
Nette Forms facilitează în mod dramatic crearea și procesarea formularelor web. Le puteți utiliza în aplicațiile dumneavoastră complet singure, fără restul cadrului, lucru pe care îl vom demonstra în acest capitol.
Cu toate acestea, dacă folosiți Nette Application și prezentări, există un ghid pentru dumneavoastră: Formulare în prezentări.
Primul formular
Vom încerca să scriem un formular de înregistrare simplu. Codul acestuia va arăta astfel (full code):
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');
Și haideți să îl redăm:
$form->render();
iar rezultatul ar trebui să arate așa:
Formularul este un obiect din clasa Nette\Forms\Form
(clasa Nette\Application\UI\Form
este utilizată
în prezentatori). I-am adăugat controalele nume, parolă și butonul de trimitere.
Acum vom reactiva formularul. Întrebând $form->isSuccess()
, vom afla dacă formularul a fost trimis și dacă
a fost completat în mod valid. Dacă da, vom descărca datele. După definirea formularului vom adăuga:
if ($form->isSuccess()) {
echo 'The form has been filled in and submitted correctly';
$data = $form->getValues();
// $data->name conține numele
// $data->password conține parola
var_dump($data);
}
Metoda getValues()
returnează datele trimise sub forma unui obiect ArrayHash. Vom arăta cum să modificăm acest lucru mai târziu. Variabila $data
conține cheile name
și
password
cu datele introduse de utilizator.
De obicei, trimitem datele direct pentru o prelucrare ulterioară, care poate fi, de exemplu, inserarea în baza de date. Cu
toate acestea, poate apărea o eroare în timpul procesării, de exemplu, numele de utilizator este deja ocupat. În acest caz,
transmitem eroarea înapoi la formular folosind addError()
și îl lăsăm să se redeseneze, cu un mesaj de
eroare:
$form->addError('Sorry, username is already in use.');
După procesarea formularului, vom redirecționa către pagina următoare. Acest lucru împiedică retrimiterea neintenționată a formularului prin apăsarea butonului refresh, back sau prin mutarea istoricului browserului.
În mod implicit, formularul este trimis folosind metoda POST către aceeași pagină. Ambele pot fi modificate:
$form->setAction('/submit.php');
$form->setMethod('GET');
Și asta e tot :-) Avem un formular funcțional și perfect securizat.
Încercați să adăugați mai multe controale de formular.
Accesul la controale
Formularul și controalele sale individuale se numesc componente. Acestea creează un arbore de componente, în care rădăcina este formularul. Puteți accesa controalele individuale după cum urmează:
$input = $form->getComponent('name');
// sintaxa alternativă: $input = $form['name'];
$button = $form->getComponent('send');
// sintaxă alternativă: $button = $form['send'];
Controalele sunt eliminate folosind unset:
unset($form['name']);
Reguli de validare
Aici a fost folosit cuvântul valid, dar formularul nu are încă reguli de validare. Să reparăm acest lucru.
Numele va fi obligatoriu, așa că îl vom marca cu metoda setRequired()
, al cărei argument este textul mesajului
de eroare care va fi afișat în cazul în care utilizatorul nu îl completează. Dacă nu se dă niciun argument, se va folosi
mesajul de eroare implicit.
$form->addText('name', 'Name:')
->setRequired('Please enter a name.');
Încercați să trimiteți formularul fără ca numele să fie completat și veți vedea că se afișează un mesaj de eroare, iar browserul sau serverul îl va respinge până când îl veți completa.
În același timp, nu veți putea păcăli sistemul introducând doar spații în câmp, de exemplu. În niciun caz. Nette taie automat spațiile albe din stânga și din dreapta. Încercați. Este un lucru pe care ar trebui să-l faceți întotdeauna la fiecare intrare pe o singură linie, dar este adesea uitat. Nette o face automat. (Puteți încerca să păcăliți formularele și să trimiteți un șir de caractere multiliniar ca nume. Chiar și în acest caz, Nette nu se va lăsa păcălit și întreruperile de linie se vor schimba în spații).
Formularul este întotdeauna validat pe partea serverului, dar este generată și validarea JavaScript, care este rapidă, iar
utilizatorul știe imediat de eroare, fără a fi nevoie să trimită formularul la server. Acest lucru este gestionat de scriptul
netteForms.js
. Adăugați-l în pagină:
<script src="https://unpkg.com/nette-forms@3"></script>
Dacă vă uitați în codul sursă al paginii cu formular, puteți observa că Nette inserează câmpurile obligatorii în
elemente cu o clasă CSS required
. Încercați să adăugați următorul stil în șablon, iar eticheta “Name”
va fi roșie. În mod elegant, marcăm câmpurile obligatorii pentru utilizatori:
<style>
.required label { color: maroon }
</style>
Reguli de validare suplimentare vor fi adăugate prin metoda addRule()
. Primul parametru este regula, al doilea
este din nou textul mesajului de eroare, iar argumentul opțional al regulii de validare poate urma. Ce înseamnă
acest lucru?
Formularul va primi o altă intrare opțională age cu condiția ca aceasta să fie un număr
(addInteger()
) și să se încadreze în anumite limite ($form::Range
). Și aici vom folosi al treilea
argument din addRule()
, intervalul propriu-zis:
$form->addInteger('age', 'Age:')
->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]);
În cazul în care utilizatorul nu completează câmpul, regulile de validare nu vor fi verificate, deoarece câmpul este opțional.
Evident, există loc pentru o mică refactorizare. În mesajul de eroare și în cel de-al treilea parametru, numerele sunt
listate în dublu exemplar, ceea ce nu este ideal. Dacă am crea un formular multilingv, iar mesajul care conține numere ar
trebui tradus în mai multe limbi, ar îngreuna modificarea valorilor. Din acest motiv, se pot utiliza caracterele de substituție
%d
:
->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]);
Să ne întoarcem la câmpul parolă, să îl facem obligatoriu și să verificăm lungimea minimă a parolei
($form::MinLength
), folosind din nou caracterele de substituție din mesaj:
$form->addPassword('password', 'Password:')
->setRequired('Pick a password')
->addRule($form::MinLength, 'Your password has to be at least %d long', 8);
Vom adăuga un câmp passwordVerify
la formular, în care utilizatorul introduce din nou parola, pentru
verificare. Folosind reguli de validare, vom verifica dacă ambele parole sunt identice ($form::Equal
). Iar ca
argument vom da o referință la prima parolă folosind paranteze pătrate:
$form->addPassword('passwordVerify', 'Password again:')
->setRequired('Fill your password again to check for typo')
->addRule($form::Equal, 'Password mismatch', $form['password'])
->setOmitted();
Folosind setOmitted()
, marcăm un element a cărui valoare nu ne interesează cu adevărat și care există doar
pentru validare. Valoarea sa nu este transmisă la $data
.
Avem un formular complet funcțional cu validare în PHP și JavaScript. Capacitățile de validare ale Nette sunt mult mai largi, puteți crea condiții, afișa și ascunde părți ale unei pagini în funcție de acestea etc. Puteți afla totul în capitolul dedicat validării formularelor.
Valori implicite
Adesea se stabilesc valori implicite pentru controalele de formular:
$form->addEmail('email', 'Email')
->setDefaultValue($lastUsedEmail);
Adesea este util să setați valorile implicite pentru toate controalele deodată. De exemplu, atunci când formularul este utilizat pentru a edita înregistrări. Citim înregistrarea din baza de date și o setăm ca valoare implicită:
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
Apelați setDefaults()
după definirea controalelor.
Redarea formularului
În mod implicit, formularul este redat sub forma unui tabel. Controalele individuale respectă orientările de bază privind
accesibilitatea web. Toate etichetele sunt generate ca <label>
elemente și sunt asociate cu intrările lor,
făcând clic pe etichetă se mută cursorul pe intrare.
Putem seta orice atribute HTML pentru fiecare element. De exemplu, adăugați un marcaj de loc:
$form->addInteger('age', 'Age:')
->setHtmlAttribute('placeholder', 'Please fill in the age');
Există într-adevăr o mulțime de moduri de a reda un formular, așa că este un capitol dedicat redării.
Maparea în clase
Să ne întoarcem la procesarea datelor din formulare. Metoda getValues()
a returnat datele trimise sub forma unui
obiect ArrayHash
. Deoarece aceasta este o clasă generică, ceva de genul stdClass
, ne vor lipsi unele
facilități atunci când lucrăm cu ea, cum ar fi completarea codului pentru proprietăți în editori sau analiza statică a
codului. Acest lucru ar putea fi rezolvat prin existența unei clase specifice pentru fiecare formular, ale cărei proprietăți
să reprezinte controalele individuale. De ex:
class RegistrationFormData
{
public string $name;
public int $age;
public string $password;
}
Alternativ, puteți utiliza constructorul:
class RegistrationFormData
{
public function __construct(
public string $name,
public int $age,
public string $password,
) {
}
}
Proprietățile clasei de date pot fi, de asemenea, enumuri, iar acestea vor fi mapate automat.
Cum să îi spunem lui Nette să ne returneze datele ca obiecte din această clasă? Mai ușor decât credeți. Tot ce trebuie să faceți este să specificați ca parametru numele clasei sau obiectul de hidratat:
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
Un 'array'
poate fi, de asemenea, specificat ca parametru, iar apoi datele se întorc sub forma unui array.
În cazul în care formularele constau într-o structură pe mai multe niveluri compusă din containere, creați o clasă separată pentru fiecare dintre acestea:
$form = new Form;
$person = $form->addContainer('person');
$person->addText('firstName');
/* ... */
class PersonFormData
{
public string $firstName;
public string $lastName;
}
class RegistrationFormData
{
public PersonFormData $person;
public int $age;
public string $password;
}
În acest caz, cartografierea știe din tipul de proprietate $person
că trebuie să mapeze containerul la clasa
PersonFormData
. În cazul în care proprietatea ar trebui să conțină o matrice de containere, furnizați tipul
array
și treceți clasa care urmează să fie mapată direct la container:
$person->setMappedType(PersonFormData::class);
Puteți genera o propunere pentru clasa de date a unui formular utilizând metoda
Nette\Forms\Blueprint::dataClass($form)
, care o va imprima în pagina browserului. Puteți apoi să dați un simplu
clic pentru a selecta și a copia codul în proiectul dumneavoastră.
Butoane de trimitere multiple
În cazul în care formularul are mai multe butoane, de obicei trebuie să distingem care dintre ele a fost apăsat. Metoda
isSubmittedBy()
a butonului ne returnează această informație:
$form->addSubmit('save', 'Save');
$form->addSubmit('delete', 'Delete');
if ($form->isSuccess()) {
if ($form['save']->isSubmittedBy()) {
// ...
}
if ($form['delete']->isSubmittedBy()) {
// ...
}
}
Nu omiteți $form->isSuccess()
pentru a verifica validitatea datelor.
Atunci când un formular este trimis cu tasta Enter, acesta este tratat ca și cum ar fi fost trimis cu primul buton.
Protecția împotriva vulnerabilităților
Nette Framework depune un mare efort pentru a fi sigur și, deoarece formularele sunt cele mai frecvente intrări ale utilizatorului, formularele Nette sunt ca și impenetrabile.
Pe lângă protejarea formularelor împotriva atacurilor vulnerabilităților bine cunoscute, cum ar fi Cross-Site Scripting (XSS ) și Cross-Site Request Forgery (CSRF), face o mulțime de mici sarcini de securitate la care nu mai trebuie să vă gândiți.
De exemplu, filtrează toate caracterele de control din intrări și verifică validitatea codificării UTF-8, astfel încât datele din formular vor fi întotdeauna curate. Pentru căsuțele de selectare și listele radio, verifică dacă elementele selectate sunt într-adevăr dintre cele oferite și dacă nu a existat nicio falsificare. Am menționat deja că, pentru introducerea textului pe o singură linie, elimină caracterele de sfârșit de linie pe care un atacator le-ar putea trimite acolo. În cazul intrărilor de text pe mai multe linii, normalizează caracterele de sfârșit de linie. Și așa mai departe.
Nette rezolvă pentru dumneavoastră vulnerabilitățile de securitate despre care majoritatea programatorilor habar nu au că există.
Atacul CSRF menționat constă în faptul că un atacator atrage victima să viziteze o pagină care execută în tăcere o cerere în browserul victimei către serverul unde victima este conectată în acel moment, iar serverul crede că cererea a fost făcută de către victimă în mod voit. Prin urmare, Nette împiedică trimiterea formularului prin POST de pe un alt domeniu. Dacă dintr-un motiv oarecare doriți să dezactivați protecția și să permiteți ca formularul să fie trimis de pe un alt domeniu, utilizați:
$form->allowCrossOrigin(); // ATENȚIE! Dezactivează protecția!
Această protecție utilizează un cookie SameSite numit _nss
. Prin urmare, creați un formular înainte de a
trimite prima ieșire, astfel încât cookie-ul să poată fi trimis.
Este posibil ca protecția cookie-urilor SameSite să nu fie 100% fiabilă, așa că este o idee bună să activați protecția cu token-uri:
$form->addProtection();
Se recomandă cu tărie să aplicați această protecție formularelor dintr-o parte administrativă a aplicației dvs. care
modifică date sensibile. Cadrul protejează împotriva unui atac CSRF prin generarea și validarea token-ului de autentificare
care este stocat într-o sesiune (argumentul este mesajul de eroare afișat în cazul în care token-ul a expirat). De aceea,
este necesar să aveți o sesiune pornită înainte de a afișa formularul. În partea de administrare a site-ului web, de
obicei, sesiunea este deja începută, datorită autentificării utilizatorului. În caz contrar, începeți sesiunea cu metoda
Nette\Http\Session::start()
.
Așadar, avem o scurtă introducere în formulare în Nette. Încercați să căutați în directorul de exemple din distribuție pentru mai multă inspirație.