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

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ů)
- Nette Framework: zvyšte svoji produktivitu 10. 3. 2009
- Nette Framework: Odvšivujeme 17. 3. 2009
- Nette Framework: MVC & MVP 24. 3. 2009
- Nette Framework: Refactoring 31. 3. 2009
- Nette Framework: Chytré šablony 7. 4. 2009
- Nette Framework: adresářová struktura aplikace 14. 4. 2009
- Nette Framework: AJAX 21. 4. 2009
- Nette Framework: AJAX (pokračování) 28. 4. 2009
- Nette Framework: AJAX (dokončení) 5. 5. 2009
- Nette Framework: Sessions 12. 5. 2009
- Nette Framework: Přihlašování uživatelů 19. 5. 2009
- Nette Framework: Ověřování oprávnění a role 26. 5. 2009
- Nette Framework: Neprůstřelné formuláře 2. 6. 2009
- Nette Framework: Neprůstřelné formuláře II 9. 6. 2009
- Nette Framework: Neprůstřelné formuláře III 16. 6. 2009
- Nette Framework: Cache 23. 6. 2009
- Nette Framework: Co se do seriálu nevešlo? 30. 6. 2009
Nálepky:
V 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()
a 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í.
Zajimalo by me, jak spolehlive je rozeznavani MIME-TYPE v Nette. Dokaze
odhalit i pokud si nekdo pohraje s hlavickou odpovedi a mime type prepise na
falesnou hodnotu? Aneb kontroluje se jen hodnota, ktera prijde v odpovedi, nebo
jsou pouzity nejake slozitejsi postupy?
To je dobrá otázka, nikdy by se nemělo věřit datům odesílaným
uživatelem.
V PHP 5.3 se typ zjišťuje analýzou obsahu souboru, informace zaslaná
browserem se rovnou zahazuje. V PHP 5.2 není ještě tahle funkce nativní,
takže se zkouší další dvě metody a teprve když není nic k dispozici,
vrátí se hodnota zaslaná prohlížečem.
Ve frameworku je nástroj Requirements-Checker, který ověřuje konfiguraci
serveru a oznámí, jestli je přítomna nějaká chytřejší funkce pro
detekci MIME-type, nebo se na hodnotu nelze spoléhat.
Uz ten serial natahujes a delas kapitoly kratsi, kvuli financim za autorstvi
nebo jsem jen ja nedockavejsi? :-)
Redakční poznámka: Davidovy články jsou po dohodě s autorem od
začátku celého seriálu nehonorované.
Nebylo by „hezčí“ (minimálně z pohledu intellisync :-) místo řetězce
určujícího validátor
používat něco jako (řešení používané např v extGWT [Java]):
A třída validátoru např.
Poznámka: co mám použít v komentářích, aby html tag „pre“
fungoval tak, jak by měl (zachovával formátování – mezery,
řádkování…)?
„Příště se podíváme na možnosti vykreslování formulářů.“
GRRRR!!!!!!
Tentokrát je to spíš jen popis části API, takže není co hodnotit
(jedině že bych opět jako minule zkritizoval konfiguraci zadrátovanou do
kódu, ve srovnání třeba se Symfony a tamními jednoduchými YAML
konfiguráky) …
Ale jeden dotaz bych měl – když jsem se podíval do kódu Nette, tak to
vypadá, jako že validace je pevně vázána na formuláře – to se opravdu
uvažuje se vstupem dat jen přes formulářová pole?? Jiné frameworky
mívají validační moduly samostatně …
U YAMLu je velká režie na zpracování, potažmo nutné cachování.
Ostatně dnes už v Symfony ani yaml validátory nejsou.
… dnes už v Symfony ani yaml validátory nejsou … „Dnes“ je snad
ještě pořád 1.2 a tam jsou (?)
Jinak to byl příklad, někdo má raději XML nebo INI.
A někdo má raději konfiguraci ve formátu PHP – nemusí kvůli ní
měnit IDE ani jazyk, a hlavně, má podporu debuggeru – můžu si na
vytvoření validační podmínky dát breakpoint, kouknout se na příslušnou
datovou strukturu, a krokovat přidávání prvků do formuláře, to
u XML/INI/whatever dost dobře nejde.
Jsem rád, že Nette podporuje konfiguraci formulářů v jazyce PHP
(Davide, nový bullet-point do feature listu :))
Milý Tomáši Kafko, vaše osobní preference nechme stranou, ale jednak asi
používáte špatné IDE, když nezvládá elementární formáty a jednak,
budete se divit, debugovat jdou stejně dobře.
Že Nette umí formuláře v PHP je nečekaný zázrak přímo epických
rozměrů (Davide, bullet rozhodně!)
Pravda, asi jsou, kvůli zpětné kompatibilitě, nicméně s 1.1 přišly
symfony forms které yaml nepoužívají – jsou takřka okopírované od
Zendu s tím, že je Symfony umí automaticky generovat(kam se hrabe Nette :P).
Ale jinak to správný příklad celkem je, na yamlu/xml stojí celý základ a
od 2.0 by se měl přidat ještě takto konfigurovatelné dependency
injection.
Nicméně u komplexnosti Symfony to dává smysl, u Nette to asi nebude
dvakrát žádoucí…
Nesouhlasím s tím, že Symfony forms jsou odvozené ze Zend_Form. Nette
má k Zend_Formu mnohem blíž. Symfony forms mi přijdou vzdálenější.
$form->addProtection(‚Vypršel ochranný časový limit,
odešlete prosím formulář ještě jednou‘); Tohle že ochrání
před Cross-Site Request Forgery (CSRF) nebo XSS?
Tohle nanejvýše ochrání před (CSRF) nebo XSS pokud útok proběhne
po timeoutu, ale ne obecně. Pokud bude timout dlouhý, bude dlouho
otevřena možnost k útoku. Pok bude krátký, bude uživatele prudit
odesílat formulář znova (a nejspíše ho serverem znova vygenerovat a
vypnit).
addProtection() nechrání přes XSS, to dělají formuláře automaticky.
Tohle se používá jako ochrana proti CSRF.
Délka timeoutu není rozhodující, chrání to spolehlivě nezávisle na
timeoutu. Nebo jste našel v kódu nějakou chybu?
Existuje v Nette validacia datumu? Kontrola ci je datum v urcitom rozsahu?
Nejhorší na tom být překladatel a současně programátor je čtení dokumentace psané pouze programátory. Obecné pravidla, textové políčka. Au.