Nette Framework: Neprůstřelné formuláře II

Nette

Validační pravidla formulářů mohou být komplikovaná, pojďme se proto podívat, jak vyždímat z Nette Framework maximum.

Seriál: Začínáme s Nette Framework (17 dílů)

  1. Nette Framework: zvyšte svoji produktivitu 10.3.2009
  2. Nette Framework: Odvšivujeme 17.3.2009
  3. Nette Framework: MVC & MVP 24.3.2009
  4. Nette Framework: Refactoring 31.3.2009
  5. Nette Framework: Chytré šablony 7.4.2009
  6. Nette Framework: adresářová struktura aplikace 14.4.2009
  7. Nette Framework: AJAX 21.4.2009
  8. Nette Framework: AJAX (pokračování) 28.4.2009
  9. Nette Framework: AJAX (dokončení) 5.5.2009
  10. Nette Framework: Sessions 12.5.2009
  11. Nette Framework: Přihlašování uživatelů 19.5.2009
  12. Nette Framework: Ověřování oprávnění a role 26.5.2009
  13. Nette Framework: Neprůstřelné formuláře 2.6.2009
  14. Nette Framework: Neprůstřelné formuláře II 9.6.2009
  15. Nette Framework: Neprůstřelné formuláře III 16.6.2009
  16. Nette Framework: Cache 23.6.2009
  17. Nette Framework: Co se do seriálu nevešlo? 30.6.2009

minulém díle seriálu o Nette Framework jsme se pustili do tvorby registračního formuláře. Výsledkem byl dobře fungující formulář s validací na straně prohlížeče i serveru, který navíc framework transparentně ochránil před vložením kontrolních znaků nebo neplatných řetězců útočníkem.

Vraťme se k políčku pro zadávání e-mailu. Tomu jsme nastavili validační pravidlo, které zkontroluje, zda je vložená adresa platná:

$form->addText('email', 'E-mail:')
        ->addRule(Form::EMAIL, 'E-mailová adresa není platná'); 

Protože prázdný řetězec pochopitelně není platnou emailovou adresou, tak nevyplnění e-mailu povede k chybové hlášce E-mailová adresa není platná, což není právě uživatelsky přívětivé a bylo by proto vhodné přidat na začátek pravidlo Form::FILLED. Co ale v případě, že chceme e-mailovou adresu mít nepovinnou, tj. kontrolovat její platnost jen v případě, že ji uživatel vyplnil? Tohle lze vyřešit přes tzv. validační podmínky. Ty se zapisují podobně jako pravidla, jen místo addRule použijeme metodu addCondition (chybová hláška se pochopitelně neuvádí):

$form->addText('email', 'E-mail:')
        ->addCondition(Form::FILLED) // podmínka: pokud je e-mail vyplněn
                ->addRule(Form::EMAIL, 'E-mailová adresa není platná'); // pak musí být platný 

Podmínku je možné vázat i na jiný prvek, než ten aktuální. Stačí addCondition nahradit za addConditionOn a jako první parametr uvést odvolávku na jiný prvek. V tomto případě se bude e-mail vyžadovat tehdy, zaškrtne-li se checkbox (tj. jeho logická hodnota bude TRUE):

$form->addCheckbox('promo', 'zasílejte mi reklamu');
$form->addText('email', 'E-mail:')
        ->addConditionOn($form['promo'], Form::EQUAL, TRUE) // podmínka: pokud je checkbox zaškrtnut
                ->addRule(Form::FILLED, 'Zadejte e-mailovou adresu'); // pak musí být e-mail zadaný 

Pravidla a podmínky je možné negovat přidáním ~, tj. addRule(~Form::EMAIL, ...). Také lze z podmínek vytvářet komplexní struktury za pomoci metod elseCondition()endCondition().

Jak vidíte, jazyk pro formulování podmínek a pravidel je velice silný. Výhodou je, že framework podle něj provede nejen validaci na straně serveru, ale vygeneruje i javascriptovou podobu. Disponuje celou řadou předdefinovaných validačních konstant:

Obecné pravidla
Form::EQUAL test rovnosti
Form::IS_IN testuje, zda hodnota spadá do výčtu hodnot
Form::FILLED je prvek vyplněn?
Form::VALID je prvek vyplněn správně?
Pro tlačítka
Form::SUBMITTED bylo tlačítko stisknuto?
Pro textové políčka
Form::MIN_LENGTH minimální délka
Form::MAX_LENGTH maximální délka
Form::LENGTH délka
Form::EMAIL je hodnota platná e-mailová adresa?
Form::URL je hodnota absolutní URL?
Form::REGEXP test oproti regulárnímu výrazu
Form::INTEGER je hodnota celočíselná?
Form::FLOAT je hodnota číslo?
Form::RANGE je hodnota v daném rozsahu?
Nahrávání souborů
Form::MAX_FILE_SIZE maximální velikost souboru
Form::MIME_TYPE ověření MIME type

Nic vám ovšem nebrání přidat si vlastní validátory:

// uživatelský validátor: testuje, zda je hodnota dělitelná argumentemfunction myValidator($item, $arg)
{
        return $item->getValue() % $arg === 0;
}

$form->addText('number', 'Číslo:')
        ->addRule('myValidator', 'Číslo musí být dělitelné %d.', 8); 

Prázdné hodnoty

Někdy se formulářovým políčkům nastavuje výchozí hodnota, která plní čistě vizuální úlohu. Příkladem je třeba vyhledávací formulář s popiskem Vyhledat přímo v textovém políčku. Jiným případem je políčko na zadání e-mailu, kde je předvyplněn zavináč, protože uživatelé mívají problém tento znak na klávesnici napsat.

V obou případech jde o hodnotu, kterou po odeslání formuláře nechceme v získaných datech mít. Vlastně ji musíme odfiltrovat už před validací, protože jinak nemusí proběhnout podle očekávání. Například podmínka Form::FILLED může být vyhodnocena jako splněná, i když prvek obsahuje jen tuto speciální hodnotu.

Nette Framework nabízí řešení v podobě tzv. „prázdné hodnoty“, kterou nastavíme metodou setEmptyValue(). Kompletní kód pro nepovinný zadávací prvek s e-mailovým políčkem a předvyplněným zavináčem bude vypadat takto:

$form->addText('email', 'E-mail:')
        ->setEmptyValue('@') // zavináč bude předvyplněn
        ->addCondition(Form::FILLED) // podmínka: pokud je e-mail vyplněn
                ->addRule(Form::EMAIL, 'E-mailová adresa není platná'); // pak musí být platný 

Pokud uživatel odešle formulář a prvek email bude obsahovat předvyplněný zavináč, v datech získaných metodou getValues() bude prázdný řetězec.

Prázdné hodnoty v select boxech

U select boxů často mívá první položka také speciální význam, slouží jako výzva k akci. V našem formuláři máme povinný prvek country pro uvedení země. Pole hodnot můžeme rozšířit o výzvu:

$countries = array(
        'Zvolte zemi', // <-- výzva k akci
        'Evropa' => array(
                'CZ' => 'Česká republika',
                'FR' => 'Francie',
                'DE' => 'Německo',
                'SK' => 'Slovensko',
                'GB' => 'Velká Británie',
        ),
        'AU' => 'Austrálie',
        'CA' => 'Kanada',
        '?'  => 'jiná',
); 

Aby však validace fungovala podle očekávání, musíme prvku nastavit, že první položka má tento speciální význam a při validaci je potřeba ji přeskakovat. K tomu slouží metoda  skipFirst():

$form->addSelect('country', 'Země:', $countries)
        ->skipFirst()
        ->addRule(Form::FILLED, 'Vyberte zemi'); 

Pokud nyní uživatel odešle formulář a jako země bude vybrána položka „Zvolte zemi“, zobrazí se uživateli chybová hláška „Vyberte zemi.“

Cross-Site Request Forgery

Nette Framework ochrání vaše aplikace před útokem Cross-Site Request Forgery (CSRF). Stačí k tomu vynaložit minimální úsilí:

$form->addProtection('Vypršel ochranný časový limit, odešlete prosím formulář ještě jednou'); 

A v tuto chvíli je váš formulář chráněn proti CSRF. Tedy vedle automatické ochrany proti celé řadě dalších útoků (XSS, UTF-8 attack, …).

Vylepšený zdrojový kód formuláře si můžete opět stáhnout.

Příště se podíváme na možnosti vykreslování formulářů.


Autor článku je vývojář na volné noze, specializuje se na návrh a programování moderních webových aplikací. Vyvíjí open-source knihovny Texy, dibi a Nette Framework a pravidelně pořádá školení pro tvůrce webových aplikací, které od podzimu 2009 nabídne kurz vývoje AJAXových aplikací.

David Grudl školí, je autorem PHP knihoven Nette Framework, databázové vrstvy dibi a formátovače HTML kódu Texy!.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 16

Přehled komentářů

int Form::MIME_TYPE
David Grudl Re: Form::MIME_TYPE
cckar kratsi a kratsi
Martin Hassman Re: kratsi a kratsi
Mat Validacni pravidla
melkor Ach jo
Mastodont Dotázek
Onanym Re: Dotázek
Mastodont Re: Dotázek
keff Re: Dotázek
Onanym Re: Dotázek
Onanym Re: Dotázek
Jiří Knesl Re: Dotázek
klikač ochrana před CSRF?
David Grudl Re: ochrana před CSRF?
MareceK Existuje validacia datumu?
Zdroj: http://www.zdrojak.cz/?p=3026