Ach ty formuláře (zamyšlení)

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
medhi
Generous Backer | 257
+
+2
-

Podle Sandboxu, tohoto článku a čehosi zaslechnutého zde na fóru lze usuzovat, že se změnil doporučovaný způsob vytváření formulářů. Dává to smysl, formulář by měl být jako komponenta, někde extra v souboru. V presenteru, kde ho chcete použít si ho jenom vytvoříte a dořešíte třeba přesměrování po zpracování.

Jenže ono je to strašně blbě použitelné. Pokud nepotřebuji k formuláři vlastní šablonu, tak to ještě celkem jde, viz Sandbox. Jenže k formuláři potřebujete vlastní šablonu vždycky. V tu chvíli se to začíná ošklivě komplikovat (při psaní tohoto gistu jsem měl pocit, že píšu program pro sondu, která bude přistávat na rotující kometě v elipticko-konkávní dráze sluneční soustavy 2 parseky odtud)

Zatím asi nejschůdnější se mi zdá řešení ze Sandboxu s tímto: https://github.com/…ign/in.latte#L6

Tedy include šablony formuláře, který si pak zavolá potřebnou komponentu.

Jenže pořád je to takové brrr. Nechtěl bych nováčkovi odpovídat na otázku, jak se udělá v Nette formulář.

Chápu, že takto je to správně, dává to smysl a tak dále. Ale nešlo by nad to postavit nějaké něco, co by udělalo tvorbu formulářů příjemnější?

Nevím jak, možná to nejde. Je to jenom pohled člověka, který tolik nechápe vnitřní souvislosti Nette a chce ho snadněji používat.

enumag
Člen | 2118
+
+1
-

Ten gist je stejné řešení jako sám používám (až na to že většinou mi stačí nějaký BootstrapRenderer místo šablony) a osobně s ním nemám vůbec problém. V podstatě jako jedinou nevýhodu vnímám to že je potřeba přidat a modifikovat poměrně dost souborů na to, že „chci akorát primitivní formulář“.

Jediné usnadnění co mne napadá je napsat command pro kdyby/console který by vygeneroval základ komponenty + ten interface, přidal ho do configu etc.

medhi
Generous Backer | 257
+
0
-

potřeba přidat a modifikovat poměrně dost souborů na to, že „chci akorát primitivní formulář“.

Právě to mám na mysli :)

enumag
Člen | 2118
+
0
-

Co se dá zjednodušit je přepsat továrničku v presenteru pomocí kdyby/autowired. Není to úplně čisté, ale ušetříš tu property pro inject a bude to lazy.

  	protected function createComponentFooForm(IFooFormFactory $factory)
  	{
  	  	$form = $factory->create($this->fooParam);
  	  	$form->onFormSuccess[] = function () {
  	  		$this->redirect('Nekam:presmerovat');
  	  	};
  	  	return $form;
  	}

Mimochodem řešení ze sandboxu s Factory jsem používal dříve změnil to na to co máš v tom gistu. Bylo to kvůli tomu že se mi u formulářů občas hodí přidat nějaký signál pro ajax a v tu chvíli už komponentu potřebuji. Předělávat factory na komponentu až ve chvíli kdy to potřebuju… brrr, zkoušel jsem, nikdy více. :-)

David Grudl
Nette Core | 8282
+
+11
-

Vůbec nic se nemění! Dál si formuláře vytvářej tak, jak ti to nejlépe vyhovuje.

Udělal jsem takové jednoduché demo.

  1. můžu vytvářet formuláře komplet v presenteru, tak jako doposud
  2. pokud potřebuju, aby formuláře měly nějakou společnou vlastnost (nastavený translator, renderer, protection atd), tak $form = new Form v presenteru nahradím za $form = $this->factory->create() a použiju jednoduchou továrnu. Viz Sign2Presenter.php a FormFactory.php. Není třeba žádný interface.
  3. Pokud budu mít stejný přihlašovací formulář na více stránkách, přemístím jej do továrny celý, viz SignPresenter.php a SignFormFactory.php. Továrna přitom může využívat základní továrnu FormFactory, když bude chtít. Není třeba žádný interface.
  4. Pokud budu chtít formulář renderovat pomocí určité šablony, udělám si na to jednoduchou komponentu, viz TemplateControl.php a Sign3Presenter.php. Nepotřebuji žádný interface. A je dokonce jedno, jestli formulář vyrobím v presenteru nebo jinde, viz Sign4Presenter.php.
  5. Pokud mi vadí vytváření komponenty mezi formulářem a presenterem, můžu na to jít jinak, třeba improvizovaně přes vykreslovač, viz Renderer.php a Sign5Presenter.php.

Mně to přijde krásně škálovatelné a jednoduché.

bazo
Člen | 620
+
+2
-

ano, je to viac pisania. ale nemas tie zavislosti nacpane v presenteri, obsluzny kod nemas v presenteri, cize mas prehladnejsie presentery, v komponente mas iba relevantny kod. a kedze je komponenta izolovana, znizuje ti moznost robit prasaciny. a nove zavislosti ti staci pridat do konstruktora komponenty a nemusis ich injektovat do presentera, potom v tovarnicke predavat komponente.

v konecnom dosledku to vedie k lepsiemu a prehladnejsiemu kodu. a lahsie sa premiestnuju komponenty z presenterov do inych, ak potrebujes, lahko ich zanoris, atd.

interface tovarniciek je u mna vacsinou copy paste, rename, cize to vela casu nezaberie. keby php podporovalo generika stacila by jedna :)

Kcko
Člen | 473
+
0
-

@DavidGrudl Hezký výčet Davide, forkuji a budu odkazovat (možná by se to hodilo jako výrazný odkaz do dokumentace na web do sekce formuláře).

medhi
Generous Backer | 257
+
0
-

David Grudl napsal(a):

Vůbec nic se nemění! Dál si formuláře vytvářej tak, jak ti to nejlépe vyhovuje.

Udělal jsem takové jednoduché demo.

  1. můžu vytvářet formuláře komplet v presenteru, tak jako doposud
  2. pokud potřebuju, aby formuláře měly nějakou společnou vlastnost (nastavený translator, renderer, protection atd), tak $form = new Form v presenteru nahradím za $form = $this->factory->create() a použiju jednoduchou továrnu. Viz Sign2Presenter.php a FormFactory.php. Není třeba žádný interface.
  3. Pokud budu mít stejný přihlašovací formulář na více stránkách, přemístím jej do továrny celý, viz SignPresenter.php a SignFormFactory.php. Továrna přitom může využívat základní továrnu FormFactory, když bude chtít. Není třeba žádný interface.
  4. Pokud budu chtít formulář renderovat pomocí určité šablony, udělám si na to jednoduchou komponentu, viz TemplateControl.php a Sign3Presenter.php. Nepotřebuji žádný interface. A je dokonce jedno, jestli formulář vyrobím v presenteru nebo jinde, viz Sign4Presenter.php.
  5. Pokud mi vadí vytváření komponenty mezi formulářem a presenterem, můžu na to jít jinak, třeba improvizovaně přes vykreslovač, viz Renderer.php a Sign5Presenter.php.

Mně to přijde krásně škálovatelné a jednoduché.

Jenže mně to právě nevyhovuje. Je to asi tím, že jsem na jakémsi pomezí, kdy 1. a 2. jsou pr mě sice jednoduché, ale vidím jejich nedostatky (zaplácnutý presenter, opakující se kód) a 3. – 5. je pro mě už těžko stravitelné, prostě se v tom nevyznám. Samozřejmě budu se snažit se v tom zorientovat a dát si vše do souvislostí, ale nyní se mi zdá, že nemohu používat formuláře přehledným způsobem, aniž bych neznal vnitřní souvislosti a principy Nette továren, komponent a jejich vazby na šablony. Rozhodně mi to nepřijde KISS, ani sexy.

Pokud jsem sám, kdo to tak vnímá, pak asi není co řešit a já budu více kroutit mozkové závity. Taky je pravděpodobné, že za půl roku mi to jasné přijde a budu se smát sám sobě.

Chtěl jsem jenom otevřít diskusi a docela by mě zajímaly názory méně zkušených Nettařů.

David Grudl
Nette Core | 8282
+
+7
-

Možná ti pomůže se na to dívat tak, že nejsou žádné Nette továrny a Nette principy, ale obyčejné továrny a jejich standardní principy. Nette z toho můžeš klidně vyškrtnout. To se stará jen o to, že v konstruktoru nebo v proměnné s anotací @inject najdeš očekávaný objekt. Bez Nette by to fungovalo taky, jen bys musel vytvářet objekty sám a předávat si je do konstruktoru nebo proměnné – což fakt není sexy.

V podstatě celé se to dá zjednodušit do tohoto: pokud objekt potřebuje vyrobit jiný objekt, tak buď:

  1. si to vyrobí sám
  2. nechá ho vyrobit někoho jiného (ten se pak označuje jako továrna).

A továrnu (nebo ostatně jakákoliv jiný objekt) buď:

  1. si vyrobí sám
  2. si nechá předat

Permanentní uplatňování těchto pravidel se nazývá programování a je s úžasem, že za to některé firmy slušně platí ;-)


Tedy pokud se rozhodneš, že formulář nechceš vyrábět v presenteru, tak to přesuneš do jiné třídy. V presenteru pak voláš metodu té jiné třídy, která ti formulář vyrobí. Tohle asi zřejmé.

Ta metoda může být statická, nebo ji voláš nad objektem, který si předáš (Nette ti ho samo předá do proměnné s anotací @inject). Statické volání je jen o chlup snazší, ale má omezené možnost, lepší je proto spíš jít cestou předání objektu.

Šaman
Člen | 2668
+
+1
-

Taky jsem ti udělal asi nejjednodušší ukázku formuláře mimo presenter. Důležitá je ta továrnička, tady s Nette opravdu nijak nesouvisí, ani nedědí od Nette\Object. Souvislost je tedy jen přes třídu Form. Tuhle továrnu mám uloženou v modelu hned vedle entity a repozitáře (proto neřeš kód, ve kterém ukládám data – to je ORM, nikoliv Nette\Database).

V těch dvou dalších souborech je ukázka použití v komponentě, nebo v presenteru. Ještě se předpokládá, že máš tu továrnu zapsanou v configu, ale to už není věc formulářů, ale obecné DI. (Klidně si ji můžeš zcela bez DI vytvořit operátorem new, ale to se dnes už bere jako prasení – viz DI).

Komponentu se vyplatí vytvořit, pokud chceš stejný formulář používat na více místech a chceš mu vytvořit šablonu. Jinak klidně použij tu továrnu v presenteru.


Otázka trochu offtopic na @DavidGrudl: Dá se nějak při základním renderování DefaultRendererem říct, že pokud nemám vyplněný label, tak že se má vzít name? Jde mi o to, že ten formulář v modelu chci mít zcela prost všech textů a dalších s modelem nesouvisejících věcí. Všechno se mi podařilo eliminovat, naposledy chybové hlášky které se dají zapsat externě. Ale pro testování dokud formulář ještě nemá šablonu chci vědět, které políčko je které. Proto bych se přimlouval, jestli by DefaultRenderer nemohl vzít $name, pokud je $label === NULL.

Zax
Člen | 370
+
+1
-

Heh, ten Šamanův nápad se mi celkem líbí, co třeba rovnou i s prefixy ve stylu $name = "$formName.$containerName.$inputName";? To by mohlo bejt celkem luxusní v kombinaci s Kdyby/Translatorem.

akadlec
Člen | 1326
+
+1
-

No já teda nevim, osobně mám formy jako komponenty a daná komponenta si řeší vše, tj. sestavení formuláře a i jeho zpracování. Vůbec se mi nelíbí kdybych měl v presenteru psát handle na zpracování formuláře, sice je většina formů jen na jednom místě, ale občas se stane že jej potřebuju na dvou a více místech a takto je to úplně jednoduché, jen si přidám vytvoření komponenty formuláře a mám hotovo, nemsuím řešit kopírování handle atd…za mě je to jednodušší a přehlednější kompletně ten form oddělit včetně všech jeho závislostí.

Šaman
Člen | 2668
+
0
-

@akadlec: V presenteru jsem do formuláře stejně zasahoval, pokud ten form nebyl jednoúčelový. Typicky kvůli přesměrování – jednou chci na detail, jindy na seznam. Univerzální je přesměrovat sám na sebe, ale to je zase většinou nevhodné. Další věc jsou flash message. Když mám formulář v komponentě, tak buď vypisuji hlášky do komponenty, nebo se odkazuji na presenter (což není úplně čisté). A když do komponenty vypíšu zprávu o úspěšném uložení a redirectnu na seznam položek (kde ten form už není), tak se ta zpráva nemá kde zobrazit. Takže ji zase musí vypsat presenter.

A hlavně – takhle, jak mám ten formulář v modelu, tak do modelu IMHO opravdu patří. Mám nějakou entitu, DAO/Repository pro operace s ní a k tomu formulář pro její vytvoření. Ale ten nesmí dělat nic jiného, než definovat pole a pomocí dalších tříd modelu entitu uložit. Cokoliv jiného by udělalo model nepřenosným.

David Grudl
Nette Core | 8282
+
0
-

@Šaman to myslím neumí.

@medhi je mi jasné, že to vůbec nemusí vypadat jednoduše, takže neváhej a ptej se.

medhi
Generous Backer | 257
+
+1
-

@DavidGrudl Díky. S @chemix jsme dnes vybírali, jaká kombinace se nám zdá nejblíže ideálu. Mně se docela líbí použití vlastního rendereru a formuláře v oddělené továrně. Výhodou je celkem pěkné volání formuláře v šabloně pomocí {control myForm $template}, formulář má vlastní šablonu hned u sebe a v presenteru je pouze vytvoření a zavěšení událostí. Vypadá to takhle:

https://gist.github.com/…dcdab2c6a269

Je to smysluplné a jde to třeba ještě nějak zjednodušit?

akadlec
Člen | 1326
+
0
-

@Šaman: tak to je už jen doplnění události, to samo taky na vybraných místech dělám, ale to je již jen dokončení chování. Flash zprávičky jsem taky řešil a vyřešil jsem to tak že si prostě té komponentě co dělá form, či něco jiného předám informaci kde ta flash bude zobrazena a o zbytek se postará komponenta, takže buď je flash v dané komponente formuláře a nebo probublá až do presenteru…to sem řešil hlavně pro případy kdy mám form či jinou komponentu s akcemi v modálním okně, tam by mě bylo vykresleni flashe v presenteru trochu na nic ;)

akadlec
Člen | 1326
+
+2
-

@medhi: jak chceš v tom tvém řešení řešit kdy bude šablona formu potřebovat další proměnné? Budeš přetěžovat renderer?

medhi
Generous Backer | 257
+
0
-

@akadlec: Nešly by tam předat spolu se šablonou pomocí {control fooFrom $template} ?

Jaký případ máš třeba na mysli?

akadlec
Člen | 1326
+
0
-

Já si třeba předávám do formu uživatele kterého edituju, zobrazím tam nějaké info nebo nastavení, prostě data co nejsou v políčkách formu apod.

medhi
Generous Backer | 257
+
0
-

S dovolením nad celou věcí pořád přemýšlím a myšlenky se mi trochu urovnávají. Zkusil jsem skoro všechny způsoby navrhované @DavidGrudl a popsal k nim jednotlivé nevýhody:

  1. můžu vytvářet formuláře komplet v presenteru, tak jako doposud
  • moc kódu v presenteru
  • nelze použít opakovaně
  • opakující se kód (translator, render, protection)
  1. pokud potřebuju, aby formuláře měly nějakou společnou vlastnost (nastavený translator, renderer, protection atd), tak $form = new Form v presenteru nahradím za $form = $this->factory->create() a použiju jednoduchou továrnu. Viz Sign2Presenter.php a FormFactory.php. Není třeba žádný interface.
  • moc kódu v presenteru
  • nelze použít opakovaně
  1. Pokud budu mít stejný přihlašovací formulář na více stránkách, přemístím jej do továrny celý, viz SignPresenter.php a SignFormFactory.php. Továrna přitom může využívat základní továrnu FormFactory, když bude chtít. Není třeba žádný interface.
  • Nelze mít vlastní šablonu
  1. Pokud budu chtít formulář renderovat pomocí určité šablony, udělám si na to jednoduchou komponentu, viz TemplateControl.php a Sign3Presenter.php. Nepotřebuji žádný interface. A je dokonce jedno, jestli formulář vyrobím v presenteru nebo jinde, viz Sign4Presenter.php.
  • Cestu k šabloně píšu v presenteru ⇒ opakovaný kód ⇒ když se cesta k šabloně změní, musím to přepisovat ve všech presenterech, kde je formulář použit.
  • do šablony se blbě posílají další parametry (?)
  1. Pokud mi vadí vytváření komponenty mezi formulářem a presenterem, můžu na to jít jinak, třeba improvizovaně přes vykreslovač, viz Renderer.php a Sign5Presenter.php.
  • do šablony se blbě posílají další parametry (?). Předpokládám použití formuláře v oddělené továrně, nikoli v presenteru, jako je v příkladu.

Chápu, že to lépe už asi vymyslet nejde a musím prostě zvolit vždy ten způsob, který se hodí na danou situaci. Z nějakého důvodu (přehlednost, pochopitelnost kódu po čase, rychlé přidání dalšího formuláře) mi ale přijde mít dělané v jedné aplikaci všechny formuláře stejným způsobem.

Pokud tedy jsou mé myšlenky správným směrem a formulář by měl splňovat všechny tyto podmínky,

  • oddělenost v souboru
  • vlastní šablona, definovaná na jednom místě, s možností předat parametry

tak existuje pouze jeden způsob, a to komponenta s interfacem. Tam je navíc nutné definovat interface, render metodu a vlastní event, aby šel použít v presenteru.

No a moje původní zamyšlení právě spočívalo v tom, jestli tento způsob, který je jinak perfektní – ale složitě se zapisuje – by nemohl umět framework „obalit“ nebo „nahradit“ pomocí nějaké syntaxe a sám třeba potřebné věci vygenerovat za mě. Jako to dělá třeba DI a Nette 2.3 už s určitým zjednodušením vlastně přišlo, když není v configu potřeba vypisovat všechny předáváné parametry navíc.

Editoval medhi (4. 2. 2015 11:47)

akadlec
Člen | 1326
+
0
-

No já ti nevím…v tom 2 a 3 ty negativa co zmiňuješ nevidím…Já osobě je řeším takto:

  1. Mám dvě formFactory, jedna vytváří klasický form a druhá entity form pro doctrineforms. Tyto továrny mají v sobě předání translatoru do formu.
  2. Udělám si komponentu NejakyFormControl
  3. V té komponentě si vytáhnu zvolenou factory a na ní zavolám create
  4. Na vytvořeném formu z factory nadefinuju potřebná pole co má mít a mám hotovo
  5. Udělám šablonu komponenty NejakyFormControl ve které si ten form ručně vykreslím.
  6. Pokud do šablony formu potřebuju dostat další proměnné tak jednoduše přes tu komponentu

Form je pak jako celek a je jedno kde jej vložím, v presenteru či komponentě pak už jen klasicky createComponet a předám továrničku formKomponenty.

Jinak já mám ještě factory doplněnou o form processory, tj. služby které se starají právě o zpracování formu, tj. metody onSuccess, ovValidation atd.

medhi
Generous Backer | 257
+
0
-

@akadlec: Tak to mi přijde jako to řešení s interfacem, které zmiňuji na konci, a které vidím i v Tvém kódu.

akadlec
Člen | 1326
+
0
-

No co by mohl FW nahradit tak je třeba tvorba těch interface pro komponenty, ale to by muselo být striktně dáno a to by se asi někomu nelíbilo. Zase na druhou stranu je to jen pár řádků kde se zadefinuje create metoda takže mě osobně to až tak nevadí.