Životní cyklus presenteru a POST-Redirect-GET formuláře
- setka
- Člen | 10
Zdravím, narazil jsem na jeden problém, který je nejvíc patrný při zpracování formulářů s využitím doporučovaného patternu PRG (HTTP přesměrování po validním odeslání formuláře).
Situaci bych ilustroval na jednoduchém příkladu: stránka (třeba blogu) s výpisem článku, pod článkem formulář pro přidání komentáře a výpis diskuse.
Formulář (AppForm
) se vytváří v presenteru pomocí
továrničky. A co je důležité: data pro šablonu (obsah článku,
komentáře) se načítají z databáze v actionVypisClanku
metodě presenteru.
Co se stane v okamžiku odeslání fomuláře? Zavolá se příslušný
signál (URL s parametrem ?do=něco
), který formulář zvaliduje
a je-li validní, volá definovanou onSubmit
metodu. Validní
odeslání formuláře nakonec přidá komentář do databáze a v duchu PRG
provede HTTP redirect (obvykle na 'this'
, ale klidně jinam).
Zrada ale je, že ještě před zpracováním formuláře (resp. jeho
signálu) se volala metoda actionVypisClanku
, která se úplně
zbytečně připojila k databázi a načetla z ní třeba text článku,
který jsme nevyužili!
Zatím používám řešení, kdy obsah pro šablonu z databáze načítám
až v metodě renderVypisClanku
, která se volá až po
zpracování formuláře. Potom ale pro Action
metody nemám
využití, a nemůžu ani používat užitečné „přepínání View“
podle https://doc.nette.org/…tion-vs-view
Navíc: pokud se podívám na obrázek cyklu presenteru na stránce https://doc.nette.org/…tion-vs-view
, tak posledním okamžikem pro přesměrování je Action
.
V případě formulářů, jak jsem psal výše, ale přesměrovávám až v
handle<Signal>
!
Vzchází mi z toho, že by měla existovat ještě jedna fáze před
Action
, která by se používala právě na zpracování
formulářů. Nebo jsem něco přehlédl a dívám se na celou věc
špatně?
- Ondřej Mirtes
- Člen | 1536
Nepochopil jsem poslední dva odstavce, asi jsi myslel obrázek tady. Nevím, proč by před action měla být ještě jedna fáze, když zpracování formulářů probíhá právě až po ní.
Cos popsal, tak děláš dobře, jsem příjemně překvapen, že nováček na fóru zná PRG pattern a frameworku očividně rozumí :)
K tvému dotazu – k plnění šablony musí docházet až ve fázi render, k čemuž je také určena. Proč je životní cyklus presenteru postaven takhle? Protože v handle fázi (kam patří i zpracování formulářů) může dojít k nějaké akci, která ovlivní aktuálně vykreslovanou stránku – např. smažeš jednu položku z článků (a vykresluješ seznam článků). Kdybys šablonu naplnil už v action a v handle jeden článek smazal, tak ho po vykreslení stále uvidíš, ale přitom už nebude existovat.
Action fáze slouží např. k rozhodovací logice, zda-li se má Presenter na pozadí (aniž by o tom návštěvník věděl) na jiné view, nebo např. pro naplnění formuláře defaultními hodnotami, nebo pro nějaké jeho dodatečné nastavení (např. pokud v něm potřebuješ v nějakém hidden poli určit, jakou položku právě edituješ). V hodně případech tvoje presentery vůbec action metody mít nemusí.
Když se na to takhle podíváš, tak v action metodě opravdu nic
zbytečného (co se při vykreslení formuláře nepoužije) být nemusí.
Protože i když tam na základě nějaké logiky (třeba i po kontrole
položky v databázi) přepneš view a zároveň formulář splňuje požadavky
pro PRG pattern, k vykreslení stránky dojít může – např. když má
uživatel vypnutý Javascript a nevalidně vyplnil formulář, anebo pokud na
základě nějakého rozhodování v modelu na formulář zavoláš vlastní
$form->addError("zpráva")
.
Ale i kdyby u tebe nastala situace, že něco provádíš v action metodě a přišels na to, že to pro zpracování formuláře není nezbytné, nelámal bych si s tím hlavu. On to server určitě vydrží a ty se můžeš zabývat důležitějšími věcmi :)
- setka
- Člen | 10
Ondřej Mirtes napsal(a):
Nepochopil jsem poslední dva odstavce, asi jsi myslel obrázek tady. Nevím, proč by před action měla být ještě jedna fáze, když zpracování formulářů probíhá právě až po ní.
Omlouvám se, vložil jsem špatný odkaz, ale přesně tenhle obrázek jsem
myslel. Po delším uvažovaní souvislostí jsem právě došel k tomu, že
Action
skoro nikdy nevyužiju, a nějak jsem cítil, že to možná
nedělám úplně správně.
Na tom obrázku je naznačeno (obdélníčkem Redirect?
) a
přesněji popsáno v textu, že Action
je poslední okamžik pro
přesměrování. Pochopil jsem z toho, že v handleXXX
už by se
přesměrovávat nemělo. No ale už jsem tady na fóru viděl obecně a
rozumné doporučení, že po každém zpracování signálu, které mění stav
(třeba smazání nějaké položky) by se mělo přesměrovat.
Je tedy „v duchu Frameworku“ přesměrovávat ve fázi zpracování
signálů (handle
)? Pokud ano, pak mě ten obrázek trochu zmátl,
ale všechno je v pořádku.
Cos popsal, tak děláš dobře, jsem příjemně překvapen, že nováček na fóru zná PRG pattern a frameworku očividně rozumí :)
Nováček na fóru nemusí být nutně nováček v PHP/programování :)
Při hledání frameworku jsem se nějakou dobu zabýval Zendem (několik lidí
mi ho doporučilo, dokumentace se zdála dobrá), ale koukal jsem i na situaci
okolo Nette. Když jsem v Zendu řešil formuláře a šablony, a přitom
viděl příklady jak to řeší Nette, hned jsem poznal, že Nette na to jde
„chytře“ a „tak jak bych to udělal sám (ale za dlouho a několikrát
bych to mezitím předělal)“.
Z pohledu nového uživatele Nette se mi zdá současná dokumentace
dostačující. Jak fungují složitější věci jde krásně dohledat ze
zdrojáku, naopak kód Zendu je hnus a jeho dokumentace pokrývá jen
základy.
K tvému dotazu – k plnění šablony musí docházet až ve fázi render, k čemuž je také určena. Proč je životní cyklus presenteru postaven takhle? Protože v handle fázi (kam patří i zpracování formulářů) může dojít k nějaké akci, která ovlivní aktuálně vykreslovanou stránku – např. smažeš jednu položku z článků (a vykresluješ seznam článků). Kdybys šablonu naplnil už v action a v handle jeden článek smazal, tak ho po vykreslení stále uvidíš, ale přitom už nebude existovat.
Přesně takhle jsem uvažoval, ale trochu mě zmátl třeba tenle post: https://forum.nette.org/…cni-formular?…
(pravda, nejde přímo o plnění šablony, ale o výchozí hodnoty ve
formuláři, což vnímám jako to samé). Podle mě je nastavování
Defaults
takhle zbytečné, protože při validním odeslání
formuláře výchozí hodnoty nepotřebuju, ale zbytečně je načítám. Takže
je lepší na tohle použít radší render<View>
.
Action fáze slouží např. k rozhodovací logice, zda-li se má Presenter na pozadí (aniž by o tom návštěvník věděl) na jiné view, nebo např. pro naplnění formuláře defaultními hodnotami, nebo pro nějaké jeho dodatečné nastavení (např. pokud v něm potřebuješ v nějakém hidden poli určit, jakou položku právě edituješ). V hodně případech tvoje presentery vůbec action metody mít nemusí.
No právě já i to plnění formulářů výchozími hodnotami dělám v
Render
(protože až tam otevírám databázi). Takže
Action
využiju tím spíš méně často.
Když se na to takhle podíváš, tak v action metodě opravdu nic zbytečného (co se při vykreslení formuláře nepoužije) být nemusí. Protože i když tam na základě nějaké logiky (třeba i po kontrole položky v databázi) přepneš view a zároveň formulář splňuje požadavky pro PRG pattern, k vykreslení stránky dojít může – např. když má uživatel vypnutý Javascript a nevalidně vyplnil formulář, anebo pokud na základě nějakého rozhodování v modelu na formulář zavoláš vlastní
$form->addError("zpráva")
.Ale i kdyby u tebe nastala situace, že něco provádíš v action metodě a přišels na to, že to pro zpracování formuláře není nezbytné, nelámal bych si s tím hlavu. On to server určitě vydrží a ty se můžeš zabývat důležitějšími věcmi :)
Jasné.
Ještě jedna otázka: Má nějaký význam, že se signály zpracovávají
až po Action
? Z mého pohledu by to bylo lepší obráceně.
Neumím si teď představit signál, jehož zpracování by záviselo na
provedení Action
. Leda že by v Action
proběhlo
přesměrování, ale to by se zas takový signál „ztratil“, takže mi
taková situace nedává smysl.
Ale věřím, že to je teď vymyšlené dobře.
- Ondřej Mirtes
- Člen | 1536
To s tím „přesměrováním nejpozději v action“ se týká té metody
$this->setView()
, na volání $this->redirect()
není nic špatného ani v renderu. V handle se toho využívá při PRG
vždycky, to jinde ani nejde :) Tohle je v dokumentaci popsané docela
nešťastně.
Plnění defaultních hodnot v render – jo, lidé to tak dělají a
nenapadá mě zrovna nějaký problém, který by s tím mohl nastat. Ale
přesto mi přijde, že je dobré, pokud formulář o těch defaultních
položkách při zpracování už ví (třeba je tam nechává, pokud uživatel
dané pole nevyplní? teď si nejsem jistý). I David Grudl kdesi doporučil
(koukám, že na ten příspěvek odkazuješ :)) dát plnění defaultních
položek do továrničky či action fáze, asi to tedy nějaký důvod má :)
Továrnička má nevýhodu v tom, že tam si musíš parametr aktuálního view
vytáhnout pomocí $this->getParam('nazevParametru')
a kdybys
přitom formulář vykresloval i v nějakém view, kde žádný takový
parametr nemáš (čemuž systém továrniček nijak nebrání), nedopadlo by
to dobře.
Smysl action fáze – tam se prostě provádějí věci, které se musí před handlem provést. Můžeš tam mít nějaké nastavování atributů třídy, které pak v handleru využíváš, můžeš tam kontrolovat nějaká oprávnění a vyhodit případně BadRequestException výjimku s kódem 403, čímž zabráníš handleru a renderu ve spuštění. Action metodu sice nevyužiješ všude, ale svůj smysl má. Vymyšlené to je dobře :)