Nette Documentation Preview

syntax
Formuláře použité samostatně
****************************

.[perex]
Nette Forms zásadně usnadňují vytváření a zpracování webových formulářů ve vašich aplikacích. V této kapitole se seznámíte s používáním formulářů samostatně bez presenterů.

Pokud ale používáte presentery a Nette Application, je pro vás určen návod pro [použití v presenterech|in-presenter].

Instalace:

```shell
composer require nette/forms
```


První formulář
==============

Zkusíme si napsat jednoduchý registrační formulář. Jeho kód bude následující:

```php
use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Jméno:');
$form->addPassword('password', 'Heslo:');
$form->addSubmit('send', 'Registrovat');
```

Velmi snadno ho vykreslíme:

```php
$form->render();
```

a v prohlížeči se zobrazí takto:

[* form-cs.webp *]

Formulář je objekt třídy `Nette\Forms\Form` (třída `Nette\Application\UI\Form` se používá v presenterech). Přidali jsem do něj tzv. prvky jméno, heslo a odesílací tlačítko.

A teď formulář oživíme. Dotazem na `$form->isSuccess()` zjistíme, zda byl formulář odeslán a zda byl vyplněn validně. Pokud ano, data vypíšeme. Za definici formuláře tedy doplníme:

```php
if ($form->isSuccess()) {
	echo 'Formulář byl správně vyplněn a odeslán';
	$data = $form->getValues();
	// $data->name obsahuje jméno
	// $data->password obsahuje heslo
	var_dump($data);
}
```

Metoda `getValues($asArray = false)` vrací odeslaná data v podobě objektu [ArrayHash |utils:arrays#ArrayHash]. Pokud by vám více vyhovovalo získat data jako čisté pole, použijeme `getValues(true)`. Proměnná `$data` obsahuje klíče `name` a `password` s údaji, které vyplnil uživatel.

Obvykle data rovnou posíláme k dalšímu zpracování, což může být například vložení do databáze. Během zpracování se ale může objevit chyba, například uživatelské jméno už je obsazené. V takovém případě chybu předáme zpět do formuláře pomocí `addError()` a necháme jej vykreslit znovu, i s chybovou hláškou.

```php
$form->addError('Omlouváme se, toto uživatelské jméno už někdo používá.');
```

Po zpracování formuláře přesměrujeme na další stránku. Zabrání se tak nechtěnému opětovnému odeslání formuláře tlačítkem *obnovit*, *zpět* nebo pohybem v historii prohlížeče.

Formulář se standardně odesílá metodou POST a to na stejnou stránku. Obojí se dá změnit:

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

A to je vlastně vše :-) Máme funkční a perfektně [zabezpečený|#Ochrana před zranitelnostmi] formulář.

Zkuste si přidat i další [formulářové prvky|controls].


Přístup k prvkům
================

Formulář i jednotlivé jeho prvky nazýváme komponentami. Tvoří komponentový strom, kde kořenem je právě formulář. K jednotlivým prvkům formuláře se dostaneme tímto způsobem:

```php
$input = $form->getComponent('name');
// alternativní syntax: $input = $form['name'];

$button = $form->getComponent('send');
// alternativní syntax: $button = $form['send'];
```

Prvky se odstraní pomocí unset:

```php
unset($form['name']);
```


Validační pravidla
==================

Padlo tu slovo *validní,* ale formulář zatím žádná validační pravidla nemá. Pojďme to napravit.

Jméno bude povinné, proto je označíme metodou `setRequired()`, jejíž argument je text chybové hlášky, která se zobrazí, pokud uživatel jméno nevyplní. Pokud argument neuvedeme, použije se výchozí chybová hláška.

```php
$form->addText('name', 'Jméno:')
	->setRequired('Zadejte prosím jméno');
```

Zkuste si odeslat formulář bez vyplněného jména a uvidíte, že se zobrazí chybová hláška a prohlížeč či server jej bude odmítat do té doby, dokud políčko nevyplníte.

Zároveň systém neošidíte tím, že do políčka napíšete třeba jen mezery. Kdepak. Nette levo- i pravostranné mezery automaticky odstraňuje. Vyzkoušejte si to. Je to věc, kterou byste měli s každým jednořádkovým inputem vždy udělat, ale často se na to zapomíná. Nette to dělá automaticky. (Můžete zkusit ošálit formulář a jako jméno poslat víceřádkový řetězec. Ani tady se Nette nenechá zmást a odřádkování změní na mezery.)

Formulář se vždy validuje na straně serveru, ale také se generuje JavaScriptová validace, která proběhne bleskově a uživatel se o chybě dozví okamžitě, bez nutnosti formulář odesílat na server. Tohle má na starosti skript `netteForms.js`.
Vložte jej do stránky:

```latte
<script src="https://nette.github.io/resources/js/2/netteForms.min.js"></script>
```

Pokud se podíváte do zdrojového kódu stránky s formulářem, můžete si všimnout, že Nette povinné prvky vkládá do elementů s CSS třídou `required`. Zkuste přidat do šablony následující stylopis a popiska „Jméno“ bude červená. Elegantně tak uživatelům vyznačíme povinné prvky:

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

Další validační pravidla přidáme metodou `addRule()`. První parametr je pravidlo, druhý je opět text chybové hlášky a může ještě následovat argument validačního pravidla. Co se tím myslí?

Formulář rozšíříme o nové nepovinné políčko „věk“, které musí být celé číslo (`addInteger()`) a navíc v povoleném rozsahu (`$form::RANGE`). A zde právě využijeme třetí parametr metody `addRule()`, kterým předáme validátoru požadovaný rozsah jako dvojici `[od, do]`:

```php
$form->addInteger('age', 'Věk:')
	->setRequired(false)
	->addRule($form::RANGE, 'Věk musí být od 18 do 120', [18, 120]);
```

.[tip]
Pokud uživatel políčko nevyplní, nebudou se validační pravidla ověřovat, neboť prvek je nepovinný.

Zde vzniká prostor pro drobný refactoring. V chybové hlášce a ve třetím parametru jsou čísla uvedená duplicitně, což není ideální. Pokud bychom tvořili vícejazyčné formuláře a hláška obsahující čísla by byla přeložena do více jazyků, ztížila by se případná změna hodnot. Z toho důvodu je možné použít zástupné znaky `%d` a Nette hodnoty doplní:

```php
	->addRule($form::RANGE, 'Věk musí být od %d do %d let', [18, 120]);
```

Vraťme se k prvku `password`, který taktéž učiníme povinným a ještě ověříme minimální délku hesla (`$form::MIN_LENGTH`), opět s využitím zástupného znaku:

```php
$form->addPassword('password', 'Heslo:')
	->setRequired('Zvolte si heslo')
	->addRule($form::MIN_LENGTH, 'Heslo musí mít alespoň %d znaků', 8);
```

Přidáme do formuláře ještě políčko `passwordVerify`, kde uživatel zadá heslo ještě jednou, pro kontrolu. Pomocí validačních pravidel zkontrolujeme, zda jsou obě hesla stejná (`$form::EQUAL`). A jako parametr dáme odvolávku na první heslo pomocí [hranatých závorek|#Přístup k prvkům]:

```php
$form->addPassword('passwordVerify', 'Heslo pro kontrolu:')
	->setRequired('Zadejte prosím heslo ještě jednou pro kontrolu')
	->addRule($form::EQUAL, 'Hesla se neshodují', $form['password'])
	->setOmitted();
```

Pomocí `setOmitted()` jsme označili prvek, na jehož hodnotě nám vlastně nezáleží a která existuje jen z důvodu validace. Hodnota se nepředá do `$data`.

Tímto máme hotový plně funkční formulář s validací v PHP i JavaScriptu. Validační schopnosti Nette jsou daleko širší, dají se vytvářet podmínky, nechávat podle nich zobrazovat a skrývat části stránky atd. Vše se dozvíte v kapitole o [validaci formulářů|validation].


Výchozí hodnoty
===============

Prvkům formuláře běžne nastavujeme výchozí hodnoty:

```php
$form->addEmail('email', 'E-mail')
	->setDefaultValue($lastUsedEmail);
```

Často se hodí nastavit výchozí hodnoty všem prvkům současně. Třeba když formulář slouží k editaci záznamů. Přečteme záznam z databáze a nastavíme výchozí hodnoty:

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

Volejte `setDefaults()` až po definici prvků.


Vykreslení formuláře
====================

Standardně se formulář vykreslí jako tabulka. Jednotlivé prvky splňují základní pravidlo přístupnosti - všechny popisky jsou zapsány jako `<label>` a provázané s příslušným formulářovým prvkem. Při kliknutí na popisku se kurzor automaticky objeví ve formulářovém políčku.

Každému prvku můžeme nastavovat libovolné HTML atributy. Třeba přidat placeholder:

```php
$form->addInteger('age', 'Věk:')
	->setHtmlAttribute('placeholder', 'Prosím vyplňte věk');
```

Způsobů, jak vykreslit formulář, je opravdu velké množství, takže je tomu věnována [samostatná kapitola o vykreslování|rendering].


Více tlačítek
=============

Pokud má formulář více než jedno tlačítko, potřebujeme zpravidla rozlišit, které z nich bylo stlačeno. Tuto informaci nám vrátí metoda `isSubmittedBy()` tlačítka:

```php
$form->addSubmit('save', 'Uložit');
$form->addSubmit('delete', 'Smazat');

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

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

Dotaz na `$form->isSuccess()` nevynechejte, ověříte tím validitu dat.

Když se formulář odešle tlačítkem <kbd>Enter</kbd>, bere se to jako kdyby byl odeslán prvním tlačítkem.


Ochrana před zranitelnostmi
===========================

Nette Framework klade velký důraz na bezpečnost a proto úzkostlivě dbá na dobré zabezpečení formulářů.

Kromě toho, že formuláře ochrání před útokem [Cross Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] a [Cross-Site Request Forgery (CSRF)|nette:glossary#Cross-Site Request Forgery (CSRF)], dělá spoustu drobných zabezpečení, na které vy už nemusíte myslet.

Tak třeba odfiltruje ze vstupů všechny kontrolní znaky a prověří validitu UTF-8 kódování, takže data z formuláře budou vždycky čistá. U select boxů a radio listů ověřuje, že vybrané položky byly skutečně z nabízených a nedošlo k podvrhu. Už jsme zmiňovali, že u jednořádkových textových vstupů ostraňuje znaky konce řádků, které tam mohl poslat útočník. U víceřádkových vstupů zase normalizuje znaky pro konce řádků. A tak dále.

Nette za vás řeší bezpečnostní rizika, o kterých spousta programátorů ani netuší, že existují.

Zmíněný CSRF útok spočívá v tom, že útočník naláká oběť na stránku, která nenápadně v prohlížeči oběti vykoná požadavek na server, na kterém je oběť přihlášena, a server se domnívá, že požadavek vykonala oběť o své vůli. Obranu před tímto útokem zapnete snadno:

```php
$form->addProtection();
```

Doporučujeme takto chránit formuláře v administrační části webu, které mění citlivá data v aplikaci. Framework se proti útoku CSRF brání vygenerováním a ověřováním autorizačního tokenu, který se ukládá do session. Proto je nutné před zobrazením formuláře mít otevřenou session. V administrační části webu obvykle už session nastartovaná je kvůli přihlášení uživatele.
Jinak session nastartujte metodou `Nette\Http\Session::start()`.

Tak, máme za sebou rychlý úvod do formulářů v Nette. Zkuste se ještě podívat do adresáře [examples|https://github.com/nette/forms/tree/master/examples] v distrubuci, kde najdete další inspiraci.

Formuláře použité samostatně

Nette Forms zásadně usnadňují vytváření a zpracování webových formulářů ve vašich aplikacích. V této kapitole se seznámíte s používáním formulářů samostatně bez presenterů.

Pokud ale používáte presentery a Nette Application, je pro vás určen návod pro použití v presenterech.

Instalace:

composer require nette/forms

První formulář

Zkusíme si napsat jednoduchý registrační formulář. Jeho kód bude následující:

use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Jméno:');
$form->addPassword('password', 'Heslo:');
$form->addSubmit('send', 'Registrovat');

Velmi snadno ho vykreslíme:

$form->render();

a v prohlížeči se zobrazí takto:

Formulář je objekt třídy Nette\Forms\Form (třída Nette\Application\UI\Form se používá v presenterech). Přidali jsem do něj tzv. prvky jméno, heslo a odesílací tlačítko.

A teď formulář oživíme. Dotazem na $form->isSuccess() zjistíme, zda byl formulář odeslán a zda byl vyplněn validně. Pokud ano, data vypíšeme. Za definici formuláře tedy doplníme:

if ($form->isSuccess()) {
	echo 'Formulář byl správně vyplněn a odeslán';
	$data = $form->getValues();
	// $data->name obsahuje jméno
	// $data->password obsahuje heslo
	var_dump($data);
}

Metoda getValues($asArray = false) vrací odeslaná data v podobě objektu ArrayHash. Pokud by vám více vyhovovalo získat data jako čisté pole, použijeme getValues(true). Proměnná $data obsahuje klíče name a password s údaji, které vyplnil uživatel.

Obvykle data rovnou posíláme k dalšímu zpracování, což může být například vložení do databáze. Během zpracování se ale může objevit chyba, například uživatelské jméno už je obsazené. V takovém případě chybu předáme zpět do formuláře pomocí addError() a necháme jej vykreslit znovu, i s chybovou hláškou.

$form->addError('Omlouváme se, toto uživatelské jméno už někdo používá.');

Po zpracování formuláře přesměrujeme na další stránku. Zabrání se tak nechtěnému opětovnému odeslání formuláře tlačítkem obnovit, zpět nebo pohybem v historii prohlížeče.

Formulář se standardně odesílá metodou POST a to na stejnou stránku. Obojí se dá změnit:

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

A to je vlastně vše :-) Máme funkční a perfektně zabezpečený formulář.

Zkuste si přidat i další formulářové prvky.

Přístup k prvkům

Formulář i jednotlivé jeho prvky nazýváme komponentami. Tvoří komponentový strom, kde kořenem je právě formulář. K jednotlivým prvkům formuláře se dostaneme tímto způsobem:

$input = $form->getComponent('name');
// alternativní syntax: $input = $form['name'];

$button = $form->getComponent('send');
// alternativní syntax: $button = $form['send'];

Prvky se odstraní pomocí unset:

unset($form['name']);

Validační pravidla

Padlo tu slovo validní, ale formulář zatím žádná validační pravidla nemá. Pojďme to napravit.

Jméno bude povinné, proto je označíme metodou setRequired(), jejíž argument je text chybové hlášky, která se zobrazí, pokud uživatel jméno nevyplní. Pokud argument neuvedeme, použije se výchozí chybová hláška.

$form->addText('name', 'Jméno:')
	->setRequired('Zadejte prosím jméno');

Zkuste si odeslat formulář bez vyplněného jména a uvidíte, že se zobrazí chybová hláška a prohlížeč či server jej bude odmítat do té doby, dokud políčko nevyplníte.

Zároveň systém neošidíte tím, že do políčka napíšete třeba jen mezery. Kdepak. Nette levo- i pravostranné mezery automaticky odstraňuje. Vyzkoušejte si to. Je to věc, kterou byste měli s každým jednořádkovým inputem vždy udělat, ale často se na to zapomíná. Nette to dělá automaticky. (Můžete zkusit ošálit formulář a jako jméno poslat víceřádkový řetězec. Ani tady se Nette nenechá zmást a odřádkování změní na mezery.)

Formulář se vždy validuje na straně serveru, ale také se generuje JavaScriptová validace, která proběhne bleskově a uživatel se o chybě dozví okamžitě, bez nutnosti formulář odesílat na server. Tohle má na starosti skript netteForms.js. Vložte jej do stránky:

<script src="https://nette.github.io/resources/js/2/netteForms.min.js"></script>

Pokud se podíváte do zdrojového kódu stránky s formulářem, můžete si všimnout, že Nette povinné prvky vkládá do elementů s CSS třídou required. Zkuste přidat do šablony následující stylopis a popiska „Jméno“ bude červená. Elegantně tak uživatelům vyznačíme povinné prvky:

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

Další validační pravidla přidáme metodou addRule(). První parametr je pravidlo, druhý je opět text chybové hlášky a může ještě následovat argument validačního pravidla. Co se tím myslí?

Formulář rozšíříme o nové nepovinné políčko „věk“, které musí být celé číslo (addInteger()) a navíc v povoleném rozsahu ($form::RANGE). A zde právě využijeme třetí parametr metody addRule(), kterým předáme validátoru požadovaný rozsah jako dvojici [od, do]:

$form->addInteger('age', 'Věk:')
	->setRequired(false)
	->addRule($form::RANGE, 'Věk musí být od 18 do 120', [18, 120]);

Pokud uživatel políčko nevyplní, nebudou se validační pravidla ověřovat, neboť prvek je nepovinný.

Zde vzniká prostor pro drobný refactoring. V chybové hlášce a ve třetím parametru jsou čísla uvedená duplicitně, což není ideální. Pokud bychom tvořili vícejazyčné formuláře a hláška obsahující čísla by byla přeložena do více jazyků, ztížila by se případná změna hodnot. Z toho důvodu je možné použít zástupné znaky %d a Nette hodnoty doplní:

	->addRule($form::RANGE, 'Věk musí být od %d do %d let', [18, 120]);

Vraťme se k prvku password, který taktéž učiníme povinným a ještě ověříme minimální délku hesla ($form::MIN_LENGTH), opět s využitím zástupného znaku:

$form->addPassword('password', 'Heslo:')
	->setRequired('Zvolte si heslo')
	->addRule($form::MIN_LENGTH, 'Heslo musí mít alespoň %d znaků', 8);

Přidáme do formuláře ještě políčko passwordVerify, kde uživatel zadá heslo ještě jednou, pro kontrolu. Pomocí validačních pravidel zkontrolujeme, zda jsou obě hesla stejná ($form::EQUAL). A jako parametr dáme odvolávku na první heslo pomocí hranatých závorek:

$form->addPassword('passwordVerify', 'Heslo pro kontrolu:')
	->setRequired('Zadejte prosím heslo ještě jednou pro kontrolu')
	->addRule($form::EQUAL, 'Hesla se neshodují', $form['password'])
	->setOmitted();

Pomocí setOmitted() jsme označili prvek, na jehož hodnotě nám vlastně nezáleží a která existuje jen z důvodu validace. Hodnota se nepředá do $data.

Tímto máme hotový plně funkční formulář s validací v PHP i JavaScriptu. Validační schopnosti Nette jsou daleko širší, dají se vytvářet podmínky, nechávat podle nich zobrazovat a skrývat části stránky atd. Vše se dozvíte v kapitole o validaci formulářů.

Výchozí hodnoty

Prvkům formuláře běžne nastavujeme výchozí hodnoty:

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

Často se hodí nastavit výchozí hodnoty všem prvkům současně. Třeba když formulář slouží k editaci záznamů. Přečteme záznam z databáze a nastavíme výchozí hodnoty:

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

Volejte setDefaults() až po definici prvků.

Vykreslení formuláře

Standardně se formulář vykreslí jako tabulka. Jednotlivé prvky splňují základní pravidlo přístupnosti – všechny popisky jsou zapsány jako <label> a provázané s příslušným formulářovým prvkem. Při kliknutí na popisku se kurzor automaticky objeví ve formulářovém políčku.

Každému prvku můžeme nastavovat libovolné HTML atributy. Třeba přidat placeholder:

$form->addInteger('age', 'Věk:')
	->setHtmlAttribute('placeholder', 'Prosím vyplňte věk');

Způsobů, jak vykreslit formulář, je opravdu velké množství, takže je tomu věnována samostatná kapitola o vykreslování.

Více tlačítek

Pokud má formulář více než jedno tlačítko, potřebujeme zpravidla rozlišit, které z nich bylo stlačeno. Tuto informaci nám vrátí metoda isSubmittedBy() tlačítka:

$form->addSubmit('save', 'Uložit');
$form->addSubmit('delete', 'Smazat');

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

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

Dotaz na $form->isSuccess() nevynechejte, ověříte tím validitu dat.

Když se formulář odešle tlačítkem Enter, bere se to jako kdyby byl odeslán prvním tlačítkem.

Ochrana před zranitelnostmi

Nette Framework klade velký důraz na bezpečnost a proto úzkostlivě dbá na dobré zabezpečení formulářů.

Kromě toho, že formuláře ochrání před útokem Cross Site Scripting (XSS) a Cross-Site Request Forgery (CSRF), dělá spoustu drobných zabezpečení, na které vy už nemusíte myslet.

Tak třeba odfiltruje ze vstupů všechny kontrolní znaky a prověří validitu UTF-8 kódování, takže data z formuláře budou vždycky čistá. U select boxů a radio listů ověřuje, že vybrané položky byly skutečně z nabízených a nedošlo k podvrhu. Už jsme zmiňovali, že u jednořádkových textových vstupů ostraňuje znaky konce řádků, které tam mohl poslat útočník. U víceřádkových vstupů zase normalizuje znaky pro konce řádků. A tak dále.

Nette za vás řeší bezpečnostní rizika, o kterých spousta programátorů ani netuší, že existují.

Zmíněný CSRF útok spočívá v tom, že útočník naláká oběť na stránku, která nenápadně v prohlížeči oběti vykoná požadavek na server, na kterém je oběť přihlášena, a server se domnívá, že požadavek vykonala oběť o své vůli. Obranu před tímto útokem zapnete snadno:

$form->addProtection();

Doporučujeme takto chránit formuláře v administrační části webu, které mění citlivá data v aplikaci. Framework se proti útoku CSRF brání vygenerováním a ověřováním autorizačního tokenu, který se ukládá do session. Proto je nutné před zobrazením formuláře mít otevřenou session. V administrační části webu obvykle už session nastartovaná je kvůli přihlášení uživatele. Jinak session nastartujte metodou Nette\Http\Session::start().

Tak, máme za sebou rychlý úvod do formulářů v Nette. Zkuste se ještě podívat do adresáře examples v distrubuci, kde najdete další inspiraci.