Obrazci, uporabljeni samostojno
Nette Forms bistveno olajšajo ustvarjanje in obdelavo spletnih obrazcev. Uporabljate jih lahko v svojih aplikacijah popolnoma samostojno brez preostalega ogrodja, kar bomo pokazali v tem poglavju.
Če pa uporabljate Nette Application in presenterje, je za vas namenjen vodnik za uporabo v presenterjih.
Prvi obrazec
Poskusimo napisati preprost obrazec za registracijo. Njegova koda bo naslednja (celotna koda):
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Ime:');
$form->addPassword('password', 'Geslo:');
$form->addSubmit('send', 'Registriraj');
Zelo enostavno ga izrišemo:
$form->render();
in v brskalniku se prikaže takole:

Obrazec je objekt razreda Nette\Forms\Form
(razred Nette\Application\UI\Form
se uporablja
v presenterjih). Dodali smo mu t.i. elemente ime, geslo in gumb za pošiljanje.
In zdaj obrazec oživimo. Z vprašanjem $form->isSuccess()
ugotovimo, ali je bil obrazec poslan in ali je bil
veljavno izpolnjen. Če je, podatke izpišemo. Za definicijo obrazca torej dodamo:
if ($form->isSuccess()) {
echo 'Obrazec je bil pravilno izpolnjen in poslan';
$data = $form->getValues();
// $data->name vsebuje ime
// $data->password vsebuje geslo
var_dump($data);
}
Metoda getValues()
vrača poslane podatke v obliki objekta ArrayHash. Kako to spremeniti, si bomo ogledali kasneje. Objekt $data
vsebuje ključa name
in password
s podatki, ki jih je izpolnil uporabnik.
Običajno podatke takoj pošljemo v nadaljnjo obdelavo, kar je lahko na primer vstavljanje v bazo podatkov. Med obdelavo pa
se lahko pojavi napaka, na primer uporabniško ime je že zasedeno. V takem primeru napako vrnemo nazaj v obrazec z
addError()
in ga pustimo ponovno izrisati, tudi s sporočilom o napaki.
$form->addError('Oprostite, to uporabniško ime že nekdo uporablja.');
Po obdelavi obrazca preusmerimo na naslednjo stran. S tem preprečimo neželeno ponovno pošiljanje obrazca z gumbom obnovi, nazaj ali premikanjem v zgodovini brskalnika.
Obrazec se standardno pošilja z metodo POST in to na isto stran. Oboje se da spremeniti:
$form->setAction('/submit.php');
$form->setMethod('GET');
In to je pravzaprav vse :-) Imamo delujoč in popolnoma zaščiten obrazec.
Poskusite dodati tudi druge elemente obrazca.
Dostop do elementov
Obrazec in njegove posamezne elemente imenujemo komponente. Tvorijo drevo komponent, kjer je koren prav obrazec. Do posameznih elementov obrazca dostopamo na ta način:
$input = $form->getComponent('name');
// alternativna sintaksa: $input = $form['name'];
$button = $form->getComponent('send');
// alternativna sintaksa: $button = $form['send'];
Elementi se odstranijo z unset:
unset($form['name']);
Validacijska pravila
Omenili smo besedo veljaven, vendar obrazec zaenkrat nima nobenih validacijskih pravil. Popravimo to.
Ime bo obvezno, zato ga označimo z metodo setRequired()
, katere argument je besedilo sporočila o napaki, ki se
prikaže, če uporabnik imena ne izpolni. Če argumenta ne navedemo, se uporabi privzeto sporočilo o napaki.
$form->addText('name', 'Ime:')
->setRequired('Prosimo, vnesite ime');
Poskusite poslati obrazec brez izpolnjenega imena in videli boste, da se prikaže sporočilo o napaki in brskalnik ali strežnik ga bo zavračal, dokler polja ne izpolnite.
Hkrati sistema ne boste prelisičili s tem, da v polje vpišete samo presledke. Kje pa. Nette samodejno odstranjuje leve in desne presledke. Preizkusite. To je stvar, ki bi jo morali vedno narediti z vsakim enovrstičnim vnosom, vendar se nanjo pogosto pozablja. Nette to naredi samodejno. (Lahko poskusite prelisičiti obrazec in kot ime poslati večvrstični niz. Tudi tu se Nette ne pusti zmesti in prelome vrstic spremeni v presledke.)
Obrazec se vedno validira na strani strežnika, vendar se generira tudi JavaScript validacija, ki poteka bliskovito in
uporabnik se o napaki takoj obvesti, brez potrebe po pošiljanju obrazca na strežnik. Za to skrbi skript
netteForms.js
. Vstavite ga na stran:
<script src="https://unpkg.com/nette-forms@3"></script>
Če pogledate izvorno kodo strani z obrazcem, lahko opazite, da Nette obvezne elemente vstavlja v elemente s CSS razredom
required
. Poskusite dodati v predlogo naslednji slog in oznaka “Ime” bo rdeča. Tako elegantno uporabnikom
označimo obvezne elemente:
<style>
.required label { color: maroon }
</style>
Druga validacijska pravila dodamo z metodo addRule()
. Prvi parameter je pravilo, drugi je spet besedilo
sporočila o napaki in lahko še sledi argument validacijskega pravila. Kaj to pomeni?
Obrazec razširimo z novim neobveznim poljem “starost”, ki mora biti celo število (addInteger()
) in poleg
tega v dovoljenem obsegu ($form::Range
). In tu prav izkoristimo tretji parameter metode addRule()
,
s katerim validatorju predamo zahtevani obseg kot par [od, do]
:
$form->addInteger('age', 'Starost:')
->addRule($form::Range, 'Starost mora biti od 18 do 120', [18, 120]);
Če uporabnik polja ne izpolni, se validacijska pravila ne bodo preverjala, saj je element neobvezen.
Tu nastane prostor za drobno preoblikovanje (refactoring). V sporočilu o napaki in v tretjem parametru so števila navedena
dvakrat, kar ni idealno. Če bi ustvarjali večjezične
obrazce in bi bilo sporočilo, ki vsebuje števila, prevedeno v več jezikov, bi se morebitna sprememba vrednosti otežila.
Zato je mogoče uporabiti nadomestne znake %d
in Nette vrednosti dopolni:
->addRule($form::Range, 'Starost mora biti od %d do %d let', [18, 120]);
Vrnimo se k elementu password
, ki ga prav tako naredimo obveznega in še preverimo minimalno dolžino gesla
($form::MinLength
), spet z uporabo nadomestnega znaka:
$form->addPassword('password', 'Geslo:')
->setRequired('Izberite si geslo')
->addRule($form::MinLength, 'Geslo mora imeti vsaj %d znakov', 8);
Dodamo v obrazec še polje passwordVerify
, kjer uporabnik vnese geslo še enkrat, za kontrolo. S pomočjo
validacijskih pravil preverimo, ali sta obe gesli enaki ($form::Equal
). In kot parameter damo sklic na prvo geslo
z uporabo oglatimi oklepaji:
$form->addPassword('passwordVerify', 'Geslo za kontrolo:')
->setRequired('Prosimo, vnesite geslo še enkrat za kontrolo')
->addRule($form::Equal, 'Gesli se ne ujemata', $form['password'])
->setOmitted();
Z setOmitted()
smo označili element, katerega vrednost nas pravzaprav ne zanima in ki obstaja le zaradi
validacije. Vrednost se ne preda v $data
.
S tem imamo končan popolnoma delujoč obrazec z validacijo v PHP in JavaScriptu. Validacijske zmožnosti Nette so veliko širše, dajo se ustvarjati pogoji, puščati po njih prikazovati in skrivati dele strani itd. Vse boste izvedeli v poglavju o validaciji obrazcev.
Privzete vrednosti
Elementom obrazca običajno nastavimo privzete vrednosti:
$form->addEmail('email', 'E-pošta')
->setDefaultValue($lastUsedEmail);
Pogosto je koristno nastaviti privzete vrednosti vsem elementom hkrati. Na primer, ko obrazec služi za urejanje zapisov. Preberemo zapis iz baze podatkov in nastavimo privzete vrednosti:
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
Pokličite setDefaults()
šele po definiciji elementov.
Izris obrazca
Standardno se obrazec izriše kot tabela. Posamezni elementi izpolnjujejo osnovno pravilo dostopnosti – vse oznake so
zapisane kot <label>
in povezane z ustreznim elementom obrazca. Pri kliku na oznako se kazalec samodejno
pojavi v polju obrazca.
Vsakemu elementu lahko nastavimo poljubne HTML atribute. Na primer, dodamo placeholder:
$form->addInteger('age', 'Starost:')
->setHtmlAttribute('placeholder', 'Prosimo, izpolnite starost');
Načinov, kako izrisati obrazec, je res veliko, zato je temu namenjeno ločeno poglavje o izrisu.
Preslikava v razrede
Vrnimo se k obdelavi podatkov obrazca. Metoda getValues()
nam je vračala poslane podatke kot objekt
ArrayHash
. Ker gre za generični razred, nekaj kot stdClass
, nam bo pri delu z njim manjkalo določeno
udobje, kot je na primer predlaganje lastnosti v urejevalnikih ali statična analiza kode. To bi lahko rešili tako, da bi za
vsak obrazec imeli konkreten razred, katerega lastnosti predstavljajo posamezne elemente. Npr.:
class RegistrationFormData
{
public string $name;
public ?int $age;
public string $password;
}
Alternativno lahko uporabite konstruktor:
class RegistrationFormData
{
public function __construct(
public string $name,
public int $age,
public string $password,
) {
}
}
Lastnosti podatkovnega razreda so lahko tudi enumi in pride do njihove samodejne preslikave.
Kako Nette sporočiti, naj nam podatke vrača kot objekte tega razreda? Lažje, kot si mislite. Dovolj je le ime razreda ali objekt za hidracijo navesti kot parameter:
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
Kot parameter lahko navedete tudi 'array'
in potem podatke vrne kot polje.
Če obrazci tvorijo večnivojsko strukturo, sestavljeno iz vsebnikov, ustvarite za vsakega ločen razred:
$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;
}
Preslikava nato iz tipa lastnosti $person
prepozna, da mora vsebnik preslikati v razred
PersonFormData
. Če bi lastnost vsebovala polje vsebnikov, navedite tip array
in razred za preslikavo
predajte neposredno vsebniku:
$person->setMappedType(PersonFormData::class);
Načrt podatkovnega razreda obrazca si lahko pustite generirati s pomočjo metode
Nette\Forms\Blueprint::dataClass($form)
, ki ga izpiše na stran brskalnika. Kodo nato samo kliknite, označite in
kopirajte v projekt.
Več gumbov
Če ima obrazec več kot en gumb, moramo praviloma razlikovati, kateri od njih je bil pritisnjen. To informacijo nam vrne
metoda isSubmittedBy()
gumba:
$form->addSubmit('save', 'Shrani');
$form->addSubmit('delete', 'Izbriši');
if ($form->isSuccess()) {
if ($form['save']->isSubmittedBy()) {
// ...
}
if ($form['delete']->isSubmittedBy()) {
// ...
}
}
Vprašanja $form->isSuccess()
ne izpustite, s tem preverite veljavnost podatkov.
Ko se obrazec pošlje z gumbom Enter, se šteje, kot da je bil poslan s prvim gumbom.
Zaščita pred ranljivostmi
Nette Framework daje velik poudarek varnosti in zato skrbno pazi na dobro zaščito obrazcev.
Poleg tega, da obrazce zaščiti pred napadom Cross Site Scripting (XSS) in Cross-Site Request Forgery (CSRF), izvaja veliko drobnih zaščit, na katere vam ni treba več misliti.
Tako na primer iz vhodov filtrira vse kontrolne znake in preveri veljavnost UTF-8 kodiranja, tako da bodo podatki iz obrazca vedno čisti. Pri izbirnih poljih in seznamih radijskih gumbov preverja, ali so bili izbrani elementi resnično iz ponujenih in ni prišlo do ponarejanja. Že smo omenili, da pri enovrstičnih besedilnih vnosih odstranjuje znake konca vrstice, ki jih je tja lahko poslal napadalec. Pri večvrstičnih vnosih pa normalizira znake za konce vrstic. In tako naprej.
Nette za vas rešuje varnostna tveganja, za katera veliko programerjev sploh ne ve, da obstajajo.
Omenjeni napad CSRF temelji na tem, da napadalec žrtev zvabi na stran, ki neopazno v brskalniku žrtve izvede zahtevo na strežnik, na katerem je žrtev prijavljena, in strežnik domneva, da je zahtevo izvedla žrtev po svoji volji. Zato Nette preprečuje pošiljanje obrazca POST iz druge domene. Če iz kakršnega koli razloga želite zaščito izklopiti in dovoliti pošiljanje obrazca iz druge domene, uporabite:
$form->allowCrossOrigin(); // POZOR! Izklopi zaščito!
Ta zaščita uporablja SameSite piškotek z imenom _nss
. Zato ustvarite objekt obrazca še pred pošiljanjem
prvega izpisa, da bo mogoče piškotek poslati.
Zaščita s pomočjo SameSite piškotka morda ni 100% zanesljiva, zato je priporočljivo vklopiti še zaščito s pomočjo žetona:
$form->addProtection();
Priporočamo, da tako zaščitite obrazce v administrativnem delu spletnega mesta, ki spreminjajo občutljive podatke
v aplikaciji. Ogrodje se proti napadu CSRF brani z generiranjem in preverjanjem avtorizacijskega žetona, ki se shranjuje
v sejo. Zato je treba pred prikazom obrazca imeti odprto sejo. V administrativnem delu spletnega mesta je običajno seja že
zagnana zaradi prijave uporabnika. Sicer sejo zaženite z metodo Nette\Http\Session::start()
.
Tako, za nami je hiter uvod v obrazce v Nette. Poskusite si še pogledati v imenik examples v distribuciji, kjer boste našli dodatno inspiracijo.