Obrazci, ki se uporabljajo samostojno

Nette Forms bistveno olajša ustvarjanje in obdelavo spletnih obrazcev. V svojih aplikacijah jih lahko uporabljate povsem samostojno, brez preostalega ogrodja, kar bomo prikazali v tem poglavju.

Če pa uporabljate aplikacijo Nette in predstavnike, je za vas na voljo priročnik: Obrazci v predstavnikih.

Prvi obrazec

Poskusili bomo napisati preprost obrazec za registracijo. Njegova koda bo videti takole (celotna koda):

use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');

Izpišimo ga:

$form->render();

in rezultat naj bo videti takole:

Obrazec je objekt razreda Nette\Forms\Form (razred Nette\Application\UI\Form se uporablja v predstavitvah). Dodali smo mu kontrolne elemente ime, geslo in gumb za pošiljanje.

Zdaj bomo obrazec oživili. Z vprašanjem $form->isSuccess() bomo ugotovili, ali je bil obrazec poslan in ali je bil pravilno izpolnjen. Če je odgovor pritrdilen, bomo podatke izpisali. Za definicijo obrazca bomo dodali:

if ($form->isSuccess()) {
	echo 'The form has been filled in and submitted correctly';
	$data = $form->getValues();
	// $data->name vsebuje ime
	// $data->password vsebuje geslo
	var_dump($data);
}

Metoda getValues() vrne poslane podatke v obliki objekta ArrayHash. Kako to spremeniti, bomo pokazali pozneje. Spremenljivka $data vsebuje ključa name in password s podatki, ki jih je vnesel uporabnik.

Običajno podatke pošljemo neposredno v nadaljnjo obdelavo, ki je lahko na primer vstavljanje v podatkovno zbirko. Vendar pa lahko med obdelavo pride do napake, na primer uporabniško ime je že zasedeno. V tem primeru napako posredujemo nazaj v obrazec s pomočjo addError() in ga pustimo ponovno narisati, s sporočilom o napaki:

$form->addError('Sorry, username is already in use.');

Po obdelavi obrazca bomo preusmerili na naslednjo stran. S tem preprečimo, da bi obrazec nenamerno ponovno poslali s klikom na gumb osvežitev, povratek ali s premikanjem zgodovine brskalnika.

Privzeto se obrazec pošlje z metodo POST na isto stran. Oboje lahko spremenite:

$form->setAction('/submit.php');
$form->setMethod('GET');

To je vse :-) Imamo funkcionalen in popolnoma zavarovan obrazec.

Poskusite dodati več kontrolnih elementov obrazca.

Dostop do kontrolnih elementov

Obrazec in njegove posamezne kontrole se imenujejo komponente. Ustvarjajo drevo komponent, katerega koren je obrazec. Do posameznih kontrolnih elementov lahko dostopate na naslednji način:

$input = $form->getComponent('name');
// alternativna sintaksa: $input = $form['name'];

$button = $form->getComponent('send');
// alternativna sintaksa: $button = $form['send'];

Kontrolne elemente odstranite z uporabo funkcije unset:

unset($form['name']);

Pravila potrjevanja

Tu je bila uporabljena beseda valid, vendar obrazec še nima pravil potrjevanja. To popravimo.

Ime bo obvezno, zato ga bomo označili z metodo setRequired(), katere argument je besedilo sporočila o napaki, ki se prikaže, če ga uporabnik ne izpolni. Če argument ni naveden, se uporabi privzeto sporočilo o napaki.

$form->addText('name', 'Name:')
	->setRequired('Please enter a name.');

Poskusite oddati obrazec brez izpolnjenega imena in videli boste, da se bo prikazalo sporočilo o napaki, brskalnik ali strežnik pa ga bo zavrnil, dokler ga ne izpolnite.

Hkrati sistema ne boste mogli prevarati tako, da boste v vnos vnesli samo presledke, na primer. Na noben način. Nette samodejno obreže leve in desne bele prostore. Preizkusite. To je nekaj, kar bi morali vedno narediti pri vsakem enovrstičnem vnosu, vendar se na to pogosto pozablja. Nette to stori samodejno. (Lahko poskusite prevarati obrazce in kot ime pošljete večvrstični niz. Tudi v tem primeru se Nette ne bo pustil preslepiti in prelomi vrstic se bodo spremenili v presledke.)

Obrazec se vedno potrdi na strani strežnika, vendar se ustvari tudi potrditev JavaScript, ki je hitra in uporabnik takoj izve za napako, ne da bi mu bilo treba obrazec poslati v strežnik. Za to poskrbi skripta netteForms.js. Dodajte jo na stran:

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

Če pogledate izvorno kodo strani z obrazcem, lahko opazite, da Nette vstavi zahtevana polja v elemente z razredom CSS required. Poskusite v predlogo dodati naslednji slog in oznaka “Ime” bo rdeča. Na eleganten način označimo zahtevana polja za uporabnike:

<style>
.required label { color: maroon }
</style>

Dodatna pravila potrjevanja bomo dodali z metodo addRule(). Prvi parameter je pravilo, drugi je spet besedilo sporočila o napaki, sledi pa lahko neobvezni argument pravila potrjevanja. Kaj to pomeni?

Obrazec bo dobil še en izbirni vnos starost s pogojem, da mora biti številka (addInteger()) in v določenih mejah ($form::Range). In tu bomo uporabili tretji argument addRule(), tj. samo območje:

$form->addInteger('age', 'Age:')
	->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]);

Če uporabnik polja ne izpolni, se pravila potrjevanja ne bodo preverila, saj je polje neobvezno.

Očitno je na voljo prostor za majhno preoblikovanje. V sporočilu o napaki in v tretjem parametru so številke navedene podvojeno, kar ni idealno. Če bi ustvarjali večjezični obrazec in bi bilo treba sporočilo s številkami prevesti v več jezikov, bi bilo spreminjanje vrednosti težje. Zato lahko uporabimo nadomestne znake %d:

	->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]);

Vrnimo se k polju geslo, naredimo ga zahtevno in preverimo najmanjšo dolžino gesla ($form::MinLength), pri čemer ponovno uporabimo nadomestne znake v sporočilu:

$form->addPassword('password', 'Password:')
	->setRequired('Pick a password')
	->addRule($form::MinLength, 'Your password has to be at least %d long', 8);

Za preverjanje bomo obrazcu dodali polje passwordVerify, kamor uporabnik ponovno vnese geslo. Z uporabo pravil potrjevanja preverimo, ali sta obe gesli enaki ($form::Equal). Kot argument pa podamo sklic na prvo geslo z uporabo oglatih oklepajev:

$form->addPassword('passwordVerify', 'Password again:')
	->setRequired('Fill your password again to check for typo')
	->addRule($form::Equal, 'Password mismatch', $form['password'])
	->setOmitted();

Z uporabo setOmitted() smo označili element, katerega vrednost nas v resnici ne zanima in ki obstaja le za potrjevanje. Njegove vrednosti ne posredujemo v $data.

Imamo popolnoma funkcionalen obrazec s preverjanjem v PHP in JavaScript. Nettejeve zmožnosti potrjevanja so veliko širše, saj lahko ustvarite pogoje, v skladu z njimi prikažete in skrijete dele strani itd. Vse boste izvedeli v poglavju o potrjevanju obrazca.

Privzete vrednosti

Pogosto nastavljamo privzete vrednosti za kontrolne elemente obrazca:

$form->addEmail('email', 'Email')
	->setDefaultValue($lastUsedEmail);

Pogosto je koristno nastaviti privzete vrednosti za vse kontrole naenkrat. Na primer, ko se obrazec uporablja za urejanje zapisov. Zapise preberemo iz podatkovne zbirke in jih nastavimo kot privzete vrednosti:

//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);

Po definiranju kontrolnih elementov pokličemo setDefaults().

Prikazovanje obrazca

Obrazec je privzeto prikazan kot tabela. Posamezni kontrolni elementi upoštevajo osnovne smernice spletne dostopnosti. Vse oznake so ustvarjene kot <label> elementi in so povezani z njihovimi vhodi, klik na etiketo pa premakne kazalec na vhod.

Za vsak element lahko nastavimo poljubne atribute HTML. Dodajte na primer nadomestno mesto:

$form->addInteger('age', 'Age:')
	->setHtmlAttribute('placeholder', 'Please fill in the age');

V tem poglavju je veliko načinov upodabljanja obrazca, zato je namenjeno prav upodabljanju.

Prikazovanje v razrede

Vrnimo se k obdelavi podatkov iz obrazca. Metoda getValues() je posredovane podatke vrnila kot predmet ArrayHash. Ker je to splošen razred, nekaj podobnega kot stdClass, bomo pri delu z njim prikrajšani za nekaj udobja, na primer za dopolnjevanje kode za lastnosti v urejevalnikih ali statično analizo kode. To bi lahko rešili tako, da bi za vsak obrazec imeli poseben razred, katerega lastnosti bi predstavljale posamezne kontrole. Npr:

class RegistrationFormData
{
	public string $name;
	public int $age;
	public string $password;
}

Uporabite lahko tudi konstruktor:

class RegistrationFormData
{
	public function __construct(
		public string $name,
		public int $age,
		public string $password,
	) {
	}
}

Lastnosti podatkovnega razreda so lahko tudi enumi in bodo samodejno preslikani.

Kako reči Nettu, naj nam podatke vrne kot predmete tega razreda? Lažje, kot si mislite. Vse, kar morate storiti, je, da kot parameter navedete ime razreda ali objekta, ki ga želite hidrirati:

$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;

Kot parameter lahko navedete tudi 'array', nato pa se podatki vrnejo kot polje.

Če so obrazci sestavljeni iz večnivojske strukture, sestavljene iz vsebnikov, ustvarite ločen razred za vsakega od njih:

$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;
}

Prikazovanje nato na podlagi vrste lastnosti $person ugotovi, da mora vsebnik prikazati v razred PersonFormData. Če bi lastnost vsebovala polje vsebnikov, navedite tip array in razred, ki ga je treba preslikati neposredno na vsebnik:

$person->setMappedType(PersonFormData::class);

Predlog podatkovnega razreda obrazca lahko ustvarite z metodo Nette\Forms\Blueprint::dataClass($form), ki ga izpiše na strani brskalnika. Nato lahko preprosto kliknete, da izberete in kopirate kodo v svoj projekt.

Več gumbov za pošiljanje

Če ima obrazec več kot en gumb, moramo običajno razlikovati, kateri je bil pritisnjen. Metoda isSubmittedBy() gumba nam vrne to informacijo:

$form->addSubmit('save', 'Save');
$form->addSubmit('delete', 'Delete');

if ($form->isSuccess()) {
	if ($form['save']->isSubmittedBy()) {
		// ...
	}

	if ($form['delete']->isSubmittedBy()) {
		// ...
	}
}

Ne izpustite $form->isSuccess(), da preverite veljavnost podatkov.

Ko je obrazec oddan s tipko Enter, se obravnava, kot da bi bil oddan s prvo tipko.

Zaščita pred ranljivostmi

Okvir Nette si zelo prizadeva biti varen, in ker so obrazci najpogostejši uporabniški vnos, so obrazci Nette tako dobro kot neprepustni.

Poleg zaščite obrazcev pred napadi dobro znanih ranljivosti, kot sta Cross-Site Scripting (XSS ) in Cross-Site Request Forgery (CSRF), opravi veliko manjših varnostnih opravil, o katerih vam ni treba več razmišljati.

Na primer, iz vnosov filtrira vse kontrolne znake in preverja veljavnost kodiranja UTF-8, tako da bodo podatki iz obrazca vedno čisti. Pri izbirnih poljih in radijskih seznamih preveri, ali so bili izbrani elementi dejansko izmed ponujenih in ni prišlo do ponarejanja. Omenili smo že, da pri enovrstičnem vnosu besedila odstrani znake s konca vrstice, ki bi jih lahko tja poslal napadalec. Pri večvrstičnih vnosih normalizira znake na koncu vrstice. In tako naprej.

Nette za vas odpravi varnostne ranljivosti, za katere večina programerjev nima pojma, da obstajajo.

Pri omenjenem napadu CSRF gre za to, da napadalec žrtev zvabi k obisku strani, ki v brskalniku žrtve tiho izvrši zahtevo strežniku, v katerega je žrtev trenutno prijavljena, strežnik pa verjame, da je zahtevo po svoji volji izvršila žrtev. Zato Nette prepreči, da bi bil obrazec oddan prek protokola POST iz druge domene. Če iz nekega razloga želite izklopiti zaščito in dovoliti oddajo obrazca iz druge domene, uporabite:

$form->allowCrossOrigin(); // POZOR! Izklopi zaščito!

Ta zaščita uporablja piškotek SameSite z imenom _nss. Zato ustvarite obrazec, preden izpraznite prvi izhod, da se lahko pošlje piškotek.

Zaščita piškotkov SameSite morda ni 100-odstotno zanesljiva, zato je dobro vklopiti zaščito s žetoni:

$form->addProtection();

To zaščito je priporočljivo uporabiti za obrazce v upravnem delu aplikacije, ki spreminjajo občutljive podatke. Okvir ščiti pred napadom CSRF z generiranjem in potrjevanjem avtentikacijskega žetona, ki je shranjen v seji (argument je sporočilo o napaki, ki se prikaže, če je žeton potekel). Zato je treba pred prikazom obrazca zagnati sejo. V upravljalnem delu spletnega mesta je seja običajno že začeta zaradi prijave uporabnika. V nasprotnem primeru sejo zaženite z metodo Nette\Http\Session::start().

Tako smo na hitro spoznali obrazce v Nette. Za več navdiha poskusite poiskati v imeniku s primeri v distribuciji.

različica: 4.0