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

Formuláře změnily Internet, z akademické sítě učinily komerční prostředí. Najednou bylo možno odesílat objednávky na e-shopech nebo poptávky přes kontaktní formuláře. Jak vypadá tvorba formuláře v Nette Framework?
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:
Ještě než si napíšeme v Nette Framework první formulář, je potřeba říci, že formuláře jsou sice dobří sluhové, ale zlí páni. Jestliže jsem se o sessions zmiňoval jako o potenciálním zdroji největších bezpečnostních děr, o formulářích musím prohlásit to stejné. Opět ale platí, že Nette Framework klade velký důraz na bezpečnost aplikací, a proto vynakládá značné úsilí i pro zabezpečení formulářů. Dělá to zcela transparentně, nevyžaduje nic manuálně nastavovat a troufám si říci, že v této oblasti má velký náskok před ostatními frameworky.
Nette Framework ochrání vaše aplikace před útokem Cross-Site Request Forgery (CSRF), odfiltruje ze vstupů kontrolní znaky, ujistí se, že všechny textové vstupy představují validní UTF-8 řetězce, že položky označené v select boxech skutečně patří mezi nabízené, automaticky ořeže mezery na jednořádkovém textovém políčku atd.
Registrační formulář
Pojďme si vytvořit registrační formulář (kód si můžete stáhnout):
require 'Netteloader.php';
// pokud používáte verzi pro PHP 5.3, odkomentujte následující řádek:
// use NetteFormsForm, NetteDebug;
Debug::enable();
$countries = array(
'Evropa' => array(
'CZ' => 'Česká republika',
'FR' => 'Francie',
'DE' => 'Německo',
'SK' => 'Slovensko',
'GB' => 'Velká Británie',
),
'AU' => 'Austrálie',
'CA' => 'Kanada',
'?' => 'jiná',
);
$sex = array(
'm' => 'muž',
'f' => 'žena',
);
$form = new Form;
$form->addText('name', 'Jméno:');
$form->addText('age', 'Věk:');
$form->addRadioList('gender', 'Pohlaví:', $sex);
$form->addText('email', 'E-mail:');
$form->addCheckbox('promo', 'zasílejte mi reklamu');
$form->addSelect('country', 'Země:', $countries);
$form->addPassword('password', 'Heslo:');
$form->addPassword('password2', 'Heslo pro kontrolu:');
$form->addSubmit('register', 'Registrovat');
echo $form;
Vykreslí se vám následující formulář:
Upozornění: skripty musí být uloženy v UTF-8.
Samotný kód je dostatečně samovysvětlující a ve spojení s obrázkem asi nepotřebuje dalšího komentáře. Snad jen dodám, že u každé metody addXyz() představuje první parametr interní identifikátor, tedy jakési ID prvku. K jednotlivým prvkům lze poté přistupovat pomocí hranatých závorek, podobně jako k prvkům pole. Takže třeba $form['name']
je objektem třídy NetteFormsTextInput
a představuje první položku formuláře.
Vykreslený formulář splňuje základní pravidlo přístupnosti – všechny popisky jsou označeny jako <label>
a provázané s formulářovým prvkem. Můžete tedy myší kliknout na popisku a kurzor se přesune do prvku. Zároveň jsou ošetřeny chyby prohlížečů (tedy vlastně prohlížeče Internet Explorer) týkající se select boxu: kliknutí na popisku nebo otočení kolečkem myši nesmaže výběr. HTML podoba formuláře se dá kompletně změnit, jak si ukážeme v dalším pokračování.
Už v prvním díle seriálu jsme si říkali o životním cyklu formuláře, který začíná zrozením (tedy definicí) formuláře, pokračuje testem na to, zda byl odeslán (is submitted?) a jestli je validní (is valid?), v případě kladných odpovědí vstupní data zpracujeme, v opačném případě jej zobrazíme uživateli.
Za definici formuláře tedy vložte kód:
// jestliže byl formulář odeslán
if ($form->isSubmitted()) {
// a jestliže jsou všechny položky vyplněny správně
if ($form->isValid()) {
echo '<h1>Formulář byl odeslán</h1>';
$values = $form->getValues();
Debug::dump($values);
exit;
}
} else {
// a jestliže nebyl odeslán, nastavíme výchozí hodnoty
$form->setDefaults(array(
'promo' => TRUE,
));}
Dotazem $form->isSubmitted()
lze detekovat první zobrazení formuláře – pokud vrací false, formulář nebyl odeslán a je tedy uživateli předložen poprvé. V takovém případě mu nastavíme výchozí hodnoty metodou setDefaults
. Výchozí hodnoty tvoří pole, kde jednotlivé klíče představují výše zmíněné ID prvků.
Pokud naopak formulář odeslán byl, je potřeba ještě metodou isValid()
ověřit, zda byl vyplněn korektně. Existují ale případy, kdy můžeme jednat i bez ověření validace – například pokud formulář obsahuje tlačítko Cancel
nebo Zpět
a toto bylo stisknuto. Zda byl vícetlačítkový formulář odeslán konkrétním tlačítkem, zjistíme dotazem např. if ($form['register']->isSubmittedBy())
.
Aby bylo ověřování validace smysluplné, musíme formuláři nastavit nějaká validační pravidla. K tomu složí metoda addRule()
, kde první argument říká, jakou vlastnost chceme ověřovat a druhý argument je text chybové hlášky. Asi nejčastěji se budete setkávat s pravidlem Form::FILLED
, které požaduje, aby prvek byl vyplněn (nebo měl zvolenou hodnotu v případě select boxu či radio listu):
$form->addText('name', 'Jméno:')
->addRule(Form::FILLED, 'Zadejte jméno');
Zajímavostí je, že Nette Framework automaticky detekuje, že prvek je povinný, a proto mu nastaví CSS třídu required
. Pokud stylem .required { color: darkred }
změníme prvkům barvu, hned se nám vykreslí takto:
Podobně budeme vyžadovat i povinné vyplnění věku, navíc přidáme kontrolu, zda jde o číslo ( Form::INTEGER
) a zda je v povoleném rozsahu ( Form::RANGE
). Zde využijeme třetí parametr metody addRule
, kterým předáme validátoru informaci o rozsahu:
$form->addText('age', 'Věk:')
->addRule(Form::FILLED, 'Zadejte věk')
->addRule(Form::INTEGER, 'Věk musí být číslo')
->addRule(Form::RANGE, 'Věk musí být v rozmezí od 5 do 120 let', array(5, 120));
Zde vzniká prostor pro drobný refactoring. V chybové hlášce a třetím parametru se duplikuje číselná informace, což není nikdy dobře. Navíc v dalším pokračování se dostaneme k překládání formulářů a pokud by se hláška obsahující čísla přeložila do více jazyků, ztížila by se případná úprava intervalu. Z toho důvodu je možné použít zástupné znaky v tomto formátu:
...
->addRule(Form::RANGE, 'Věk musí být v rozmezí od %d do %d let', array(5, 120));
Dalším políčkem, které budeme chtít validovat, je e-mailová adresa. Její platnost ověřuje pravidlo Form::EMAIL
.
$form->addText('email', 'E-mail:')
->addRule(Form::EMAIL, 'E-mailová adresa není platná');
Pravidlo Form::FILLED
přidáme prvkům country
, password
a password2
. Heslo budeme ještě kontrolovat na minimální délku ( Form::MIN_LENGTH
), opět s využitím zástupného znaku:
$form->addPassword('password', 'Heslo:')
->addRule(Form::FILLED, 'Zvolte si heslo')
->addRule(Form::MIN_LENGTH, 'Zadané heslo je příliš krátké, zvolte si heslo alespoň o %d znacích', 3);
A druhé heslo zkontroluje rovnost (tj. Form::EQUAL
) s heslem prvním (všimněte si odvolávky na první heslo):
$form->addPassword('password2', 'Heslo pro kontrolu:')
->addRule(Form::FILLED, 'Zadejte heslo ještě jednou pro kontrolu')
->addRule(Form::EQUAL, 'Zadané hesla se neshodují', $form['password']);
Hotový příklad si opět můžete stáhnout a vyzkoušet. Uvidíte, že kromě validace na straně serveru se automaticky dostala ke slovu i validace na straně prohlížeče (tj. javascriptová validace). Všimněte si, že po chybové zprávě se vždy kurzor umístí do příslušného políčka.
Uživatel se dostal do kolečka na křečka: dokud formulář nevyplní správně, je mu předkládán stále dokola s výpisem chyb.
Tímto ovšem validační možnosti zdaleka nekončí. Předností Nette Framework jsou tzv. validační podmínky, pomocí nichž uděláme políčko s e-mailem nepovinné. Ty najdete v příštím pokračování.
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í.
Nehodilo by se i obecné validační pravidlo, kde jen zadám v parametru
regulární výraz? Nebo je raději vynecháno, aby si člověk psal jednoduché
validační funkce a nesvádělo ho to k hůře čitelným regulárům?
Validace na bázi regulárních výrazů je možná, viz třeba http://forum.nettephp.com/…rms-podminky?…
V odkazovanem foru, ktere je zavreno si lide stezuji na nemoznost validace
urcitych bloku – po odeslani jednim tlacitkem chci validovat, druhym nechci.
Tohle ma docela dobre vyreseno Prado, ma tzv. validacni skupiny, takze muzu
rict, ze tri inputy a jedno tlacitko jsou v jedne skupine a dalsi v druhe. Pak
v zavislosti na odeslani formu danym tlacitkem se provede validace jen nad temi
prvky co jsou s nim ve skupine.
Btw, jak se projevi odeslani formulare Entrem? pak tam neni zadny cudlik,
kterym byl form odeslan.
$form->addRadioList(‚gender‘, ‚Pohlaví:‘, $sex);
$form->addText(‚email‘, ‚E-mail:‘);
$form->addCheckbox(‚promo‘, ‚zasílejte mi reklamu‘);
…
Já nevím … když pominu to, že to je ikstá varianta toho, co se dá už
léta splašit třeba na phpclasses.org i jinde, mi uniká smysl toho, abych
pokaždé dynamicky vytvářel formulář, který pak vždy
vypadá stejně. Jako utilita pro tvorbu HTML šablon
formulářů to je dobré. Ale do aplikace … Co třeba metody pro vytváření
dynamických seznamů nebo check boxů, načítajích data
z databáze, jsou?
Tak šup, alespoň jeden odkaz na phpclasses.org s komponentou, která umí
totéž a disponuje stejnými bezpečnostními prvky. Těším se.
Stejně by se dalo říct, že na HotScripts.com nebo kdekoliv jinde jich je
spousta ale většina je buď hodně mizerná nebo je jejich použití
všelijaké. Valná většina navíc postrádá alespoň základní
dokumentaci.
David: Asi by se tam dalo něco podobného najít. Já jsem to vzdal po
8 pokusech a napsal si vlastní. Manuel tam sice má třídu, která vypadá
kompletní ale nevyznám se v tom množství souborů a neměl jsem náladu
studovat použití, takže těžko říct.
Ano, Lemosovy třídy, dále Clonefish a samozřejmě Zend_Form. Form helpery
jsou v podstatě v každém frameworku.
Tak třeba Zend_Form mimo jiné nedisponuje:
nedostatek)
v době, kdy jsem se na něj díval)
špatném hrobě)
Díváte se na celou věc pouze povrchně (což prosím neberte jako
invektivu!), proto soudíte, že jde o další variaci na totéž. Nejde. Na
první pohled to tak může vypadat, ale uvnitř zeje kvalitativní propast.
Týjo, komentáře tu statečně požírají veškeré pokusy
o formátování. Nejde to Texy nějak vypnout? ;)
Doporučuji kontaktovat naše vývojové oddělení. Oon ten celý nový
systém je pořád takový neodladěný polotvat 8-(
Ale já tady neřeším, kdo z vás ho má většího na základě
množství kódu a co kdo napsal dříve … pouze jsem vyjádřil své lehké
znechucení nad tím, že dynamicky generujete vždy stejný obsah – na to
můžete namítnout, že formuláře se nezobrazují tisíckrát za sekundu a
proto je cache zbytečná … nechápu, proč to odvádíte jinam.
Druhý problém mám s tím, že takto máte konfiguraci UI zadrátovanou do
kódu – pokud by si zákazník chtěl změnit popisek pole, musí do
PHP kódu.
Reagoval jsem na větu „když pominu to, že to je ikstá varianta toho, co
se dá už léta splašit třeba na phpclasses.org i jinde“. Neodvádím to
jinam, jen reaguji postupně.
K tomu generování stejného obsahu: Mícháte hrušky s baňama. Nikde
jsem přece „nenamítal, že cache je zbytečná“. Naopak, Nette Framework
disponuje velmi silným aparátem
pro kešování. A když mám aparát, nebojím se ho použít ;)
Ad popisky: to je dobrá připomínka, proto popisky takto uvedené v kódu
jsou volitelné. Pro jednoduché weby je pragmatické uvést je přímo
v kódu. Pro složitější weby je lze nastavovat mimo ve zvláštní rutině.
A nakonec pro multijazyčné weby lze použít místo popisek konstanty, které
se poté překladačem automaticky nahradí za skutečné popisky v daném
jazyce.
Jak jste, ‚sim Vás, přišel k té přezdívce? :-) Jistě, na
phpclasses.org a desítkách dalších zdrojů seženete takových rutin tuny.
Nějakou tu „vodotoňákfunguje“ třídu pro formuláře si navíc zbastlí
každý začínající programátor. Ovšem kvalitně naprogramovat a
zakomponovat do frameworku už tak snadné není.
„Co třeba metody pro vytváření dynamických seznamů nebo check boxů,
načítajích data z databáze, jsou?“ – a co třeba si takovou
specifickou záležitost naprogramovat sám? Nette tomu nijak nebrání… Pokud
ovšem nejste ten typ programátora, který celou aplikaci poslepuje z torz
z phpclasses.org apod.
Ad 1) Narážky ad hominem ve stylu „jé, ty máš blbej nick“ nemám
rád
Ad 2) Ano, to se samozřejmě napsat dá. Ale proč v Nette? :-)
Ad 3) Já cizí kód používám, nevidím důvod, proč bych měl třeba HTML
Purifier psát znovu jen proto, abych světu dokázal, že jsem těžkej borec a
všechno přede mnou bylo blbě … a je mi úplně jedno, jestli ho najdu
v PEARu, na hotscripts nebo jinde, rozhodující je licence, jednoduchost,
konsistence a celková úspora času.
Ja tedy v php neprogramuji, tak je muj dotaz asi dost nejapny, ale …
neni to trosku luxus generovat vsechno dynamicky pres php, bez spetky
hotoveho HTML? napr. napr jinak staticke formulare, veskere tagy sablonach
a pod.
I tohle je možné, záleží na potřebách nebo „lenosti“
programátora. Dostanu s k tomu v dalším pokračování.
Prostě a jednoduše super, jestli ne celé Nette a strukturu kterou
nabízí, tak minimálně Forms použiju určitě. Díky za ty dary.
Jen ještě rychlý dotaz – to je ok, že emailová kontrola schválí
adresu jako „lang@glavew.orks.czz“? Ještě bych chápal tu tečku
uprostřed, ale .czz? Jdou ty pravidla nějak editovat?
TLD nejsou nijak předem definovány ⇒ proč ne?
Aha, ok, diky
Pravda je ale taková, že kontrola e-mailové adresy v Nette je velmi
benevolentní. Povolí třeba i nevalidní adresu
.@;.aa
.