Přejít k navigační liště

Zdroják » PHP » Nette Framework: Neprůstřelné formuláře II

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

Články PHP, Různé

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

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í.

Komentáře

Subscribe
Upozornit na
guest
17 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
int

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?

cckar

Uz ten serial natahujes a delas kapitoly kratsi, kvuli financim za autorstvi
nebo jsem jen ja nedockavejsi? :-)

Martin Hassman

Redakční poznámka: Davidovy články jsou po dohodě s autorem od
začátku celého seriálu nehonorované.

Mat

Nebylo by „hezčí“ (minimálně z pohledu intellisync :-) místo řetězce
určujícího validátor

 …->addRule(‚myVa­lidator‘, ‚Číslo musí být dělitelné %d.‘, 8); 

používat něco jako (řešení používané např v extGWT [Java]):

 email = new TextField();
email.setVali­dator(new EmailValidator());

A třída validátoru např.

 public class EmailValidator implements Validator {
public String validate(Field f, String value) {
if (nejaka_podminka) {
return null;
} else {
return „chybova hlaska“;
}
}
}

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í…)?

melkor

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

GRRRR!!!!!!

Mastodont

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ě …

Onanym

U YAMLu je velká režie na zpracování, potažmo nutné cachování.
Ostatně dnes už v Symfony ani yaml validátory nejsou.

Mastodont

… 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.

keff

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 :))

Onanym

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ě!)

Onanym

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í…

Jiří Knesl

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ší.

klikač

$form->addProtection(‚Vy­prš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).

David Grudl

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?

MareceK

Existuje v Nette validacia datumu? Kontrola ci je datum v urcitom rozsahu?

Michal Meysha Stárek

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.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.