[2009-07-16] beforePrepare() & prepare<View>() deprecated. Co s tím?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 8128
+
0
-

Aneb kuchařka pro upgradování Nette na 0.9.

Nejprve musím poznamenat, že současný vývoj na 0.9 se mi hodně libí. A možná i proto, že vím, co ještě nevíte ;)) Nicméně chápu programátory, co se naučili dělat na 0.8 (nebo dokonce 0.7) a teď nerozumí, proč věc, která framework provází roky, je najednou deprecated. Pokusím se proto celou věc popsat velmi podrobně.

Jak nahradit prepare snadno a rychle?

Existence metod Presenter::beforePrepare() & prepare<View>() od revize 425 vyhazuje E_USER_WARNING. Co s tím?

Začnu tím nejjednodušším ale ne správným řešením: jde to velmi snadno. Ač se to nezdá, mezi metodami action<Action> a before<View> nebyl doposud téměř žádný principiální rozdíl – takže pokud neměníte název view, dá se vždy obsah before<View> přifařit na konec odpovídajícího action<Action> a máte hotovo. Což je nejsnadnější cesta pro ty, kterým se to nechce řešit/nemají čas, ale nechtějí setrvat u Nette 0.8.

Proč vůbec nahrazovat prepare?

Začnu v historických souvislostech. Jak už jsem možná zmiňoval, koncepci frameworku jsem měl hotovou někdy v roce 2004, ale ty podružné věci jako „jak co implementovat“ nebo „kterou zvolit cestou, když všechny vedou do říma“ trval pět let. Jinými slovy trvá stále.

Jednou z potřeb bylo mít k dispozici strom komponent v okamžiku volání signálu, ale započít s přípravou šablony až poté. Věc se dala řešit několika cestami a všechny měly své „ale“ – nakonec jsem zvolil rozdělení na část prepare a render. Vycházel jsem z toho, že pro lidi bude jasnější a srozumitelnější všechny komponenty zapojit a poskládat v jednom místě. Nicméně ukázalo se, že tohle rozhodnutí bylo špatné. Továrničky, chytřejší továrničky a ještě chytřejší továrničky uvítala spousta nettistů a evoluce si našla cestru navzdory stvořiteli :-)

Důvod obliby je přitom jasný. Aplikace přepsaná do továrniček má už od pohledu hezčí a srozumitelnější kód. Při volbě prepare metod jsem si totiž neuvědomil jednu zásadní věc – samotné vytvoření a konfigurace komponenty není záležitost na jeden řádek. V metodách tak začal vznikat spaghetti kód. Oddělení do továrniček problém řeší. Navíc řeší ještě něco dalšího, o čem se zmíním později.

Tohle všechno dohromady společně s mým přesvědčením, že kód nemá dávat alternativy (viz Zen of Python), mě nutí prepare metody zrušit.

Jak nahradit prepare dobře a pomaleji?

  1. vytvoření každé komponenty přeneste do samostatné chráněnné metody createComponent<Name>(), kde <name> je název té kompenty. A název komponenty je to, co se obvykle uvádí jako druhý parametr v konstruktoru.

Těmto metodám se říká továrničky, protože mají za úkol komponentu vyrobit (a jen vyrobit). Poté ji mohou vrátit přes return nebo rovnou připojit k presenteru, tj. např. volat obvyklé $form = new AppForm($this, 'loginForm'). Továrničce je jako jediný volitelný argument předáván název komponenty.

Příklad:

	/**
	 * Login form component factory.
	 * @return mixed
	 */
	protected function createComponentLoginForm()
	{
		$form = new AppForm;
		$form->addText('username', 'Username:')
			->addRule(Form::FILLED, 'Please provide an username.');
		$form->addPassword('password', 'Password:')
			->addRule(Form::FILLED, 'Please provide a password.');
		$form->addSubmit('login', 'Login');
		$form->addProtection('Please submit this form again (security token has expired).');

		$form->onSubmit[] = array($this, 'loginFormSubmitted');
		return $form;
	}

Ke komponentám se přistupuje přes hranaté závorky. Takže $this['loginForm'] vrací komponetu formuláře s názvem loginForm (a pokud dosud neexistuje, zavolá se createComponentLoginForm('loginForm'), která ji vytvoří).

(pozn. komponenty už nemusíte předávat do proměnných šablony, preferovanou cestou je užití $presenter['loginForm'] přímo v šabloně nebo vykreslení pomocí makra {control loginForm}, které volá $presenter['loginForm']->render())

  1. co když má jedna komponenta vazbu na jinou? Nevadí, klidně se můžete v továrničce na jinou komponentu odkázat přes $this['jinakomponenta'] a použít ji.
  2. co když mají komponenty mezi sebou cyklickou vazbu? Zvolte techniku master-slave. Všechny továrničky vytvoří komponenty bez vazby, jen jedna je navzájem prováže. Je potřeba ale dávat pozor na to, aby se přistupovalo z venčí právě přes master-komponentu.
  3. co když přístup přes master-komponentu nelze zajistit, protože jsou všechny rovnocenné? Tak je vytvořte a pospojujte buď v startup() nebo action<Action>(), nebo ještě lépe, abyste netratili výhodu lazy-loadingu, v metodě createComponent(). Totiž v rámci volání továrničky můžete sestavit klidně i několik komponent. Je to čisté řešení – vlastně továrnička je navržena záměrně tak, aby to šlo udělat.
  4. co když továrnička potřebuje mít přístup k argumentům metody prepare<View>()? Získejte je přes $this->getParam('...'). Jen pozor, tyto hodnoty nebudou automaticky přetypované (pokud je v metodě prepare<View>() parametr s výchozí hodnotou např. 10, je každý argument přetypován také na číslo).
  5. co když továrnička potřebuje mít přístup k proměnným, které vytváříte uvnitř prepare<View>()? Inu, to se asi neobejde bez refactoringu. Ale věřte mi, že půjde zpravidla o refactoring, který kód osvěží a posune na vyšší úroveň. Ale připouštím, že někdo narazí na situaci, kdy tomu tak nebude – nebojte se ozvat. 6) co když továrnička potřebuje mít přístup k proměnným, které vytváříte uvnitř prepare<View>()? Inu, to se asi neobejde bez refactoringu. Ale věřte mi, že půjde zpravidla o refactoring, který kód osvěží a posune na vyšší úroveň. Ale připouštím, že někdo narazí na situaci, kdy tomu tak nebude – nebojte se ozvat. Framework je od toho, aby život zjednodušoval. Pokud odhalíte limity nového řešení, musíme řešení vylepšit.

Co že jsem se to nezmínil?

O něčem jsem se chtěl zmínit později. Při návrhu frameworku ještě AJAX neexistoval, nebo alespoň šlo o technologii s velmi nejistou budoucností. Jak šel čas, Nette Framework začal AJAXu čím dál tím víc nadbíhat a dnes už se AJAXově tvoří poměrně pohodlně. Sám to vidím nejlíp na školeních.

Nicméně stale tu existovalo několik nepřekonatelných překážek. A jejich neblahých důsledků, jako například zavináčová magie. Blýsklo se na lepší časy a již brzy bude fungovat šablonovací systém bez nich. Bude umět vykreslit nějakou svou vnitřní část, aniž by bylo potřeba celou šablonu procházet.

Když v té jedné části bude třeba jen jedna komponenta (nebo dokonce část komponenty), získá na obrovské důležitosti schopnost presenteru jen tu jednu komponentu samotinkou vytvořit. Což je obrovské plus pro továrničky.

jasir
Člen | 746
+
0
-

Špička, skvělý článek a skvělé zprávy, jsem opravdu nadšený. Moc díky.

p.s. to s tou evolucí a stvořitelem mě fakt dostalo ;-)

p.p.s. v bodu 6 a v části ohledně snadného řešení je copy/paste error, jsou tam stejné informace 2×

Editoval jasir (17. 7. 2009 10:16)

Jod
Člen | 701
+
0
-

Teším sa spolu s Nette :)

Ondřej Mirtes
Člen | 1536
+
0
-

Těším se na nové šablony a článek o nich :)

Ondřej Brejla
Člen | 746
+
0
-

Paráda…na šablony se těším víc jak na Ježíška :D

Cifro
Člen | 245
+
0
-

Vývoj Nette ide rýchlo, len dokumentácia nejako nestíha. A v súčastnej dokumentácii je životný cyklus presentera pre verziu 0.8. až 0.9r425. Ja si myslím, že asi by sa mala dokumentácia rezdeliť na verzie pre 0.8, 0.9.

A viac screencastov, nemusia byť ani hovorené, stačí len pozerať ako sa to kodí a popis by sa daval do komentárov v php :)

Ale inak tieto zmeny sú fajné, o dva metódy menej ^_^

Petr Motejlek
Člen | 293
+
0
-

EDIT: Něco ohledně stable a development už tu zaznělo (pardon, dostal jsem se k tomu až teď, tenhle topic je přeci jen novější a stable a development je navrhováno o něco níže v seznamu).

To s tou dokumentací je určitě pravda. Před 3 dny jsem se životním cyklem Presenteru zabýval trochu podrobněji a celkem se mi tam metody prepare<View>() zalíbily. Já jsem dokumentaci pochopil tak, že ve fázi interaction se předpokládá nejčastější výskyt chyb (rozuměj tak, že v této fázi by na výstupu nemělo ještě nic být, takže jakékoliv přesměrování je možné a i žádané, pokud se tedy potřebuji spojit s databázi a něco si vybrat (byť třeba s použítím modelu), měl bych se o to pokusit ještě v této fázi, aby případná chyba způsobila přesměrování na ErrorPresenter. Většinu věcí, co jsem dřív dělal přes render<View> jsem přesunul do prepare<View> (a ten kód se o dost lépe četl, protože jsem přesně věděl, že v render<View> nastavuji jen věci pro šablonu a v prepare<View> mám načítání a přípravu (u presenteru pro RSS feed tam např. mám nastavení ContentTypu na application/xml).

Je zřejmé, že i když databáze selže v rámci foreach cyklu při vykreslení šablony, tak už se přesměrování nedočkáme, toho se vyvarovat nejde, ale když budu vědět, že se k databázi dostanu už během prepare, tak bude času pro selhání (a následné přesměrování) dost a dost.

Asi nebudu mít problém s předěláním toho, co zatím mám, na systém bez prepare<View> a beforePrepare, ale je škoda, že neexistuje víc verzí dokumentace. Už by to chtělo uvažovat o tom, jestli nedělat pro každý tag v svnku novou verzi dokumentace a řídit se pravidlem – udělám-li tag, budu v něm upravovat jen chyby a nebudu nic měnit nebo přídávat. Už jsme ve fázi, že je třeba řešit i zpětnou kompatibilitu (i když to ne každý rád slyší) a tohle bude jedině krok správným směrem. Věřím tomu, že dost nových uživatelů může pár takových „zbrklých“ změn odradit, protože to může být dost matoucí. Přece každý, když přijde k něčemu novému, si potřebuje stáhnout něco, co bude funkčně shodné s dokumentací. Pokud mu bude něco chybět, nahlédne do dokumentace jiné verze a tam to buď najde, nebo nenajde.

Možná ten přístup s tagama není úplně nejlepší, určitě by to šlo řešit i jinak. Třeba k tomu přidat ještě další větve a dělat to třeba tak, jak to řeší např. Debian (mít stable, unstable a testing větev a pro každou takovou větev udržovat dokumentaci). Vyvstane tady potřeba řešit i životní cyklus vývoje – tzn. kdy se z unstable stane testing, kdy z testing stable, atd.

Vím, že to, co jsem napsal výše, jsou jen takový výkřiky, ale už dlouho jsem to chtěl někomu říct a tenhle topic mě k tomu přímo navedl.

Editoval m0t3jl (17. 7. 2009 11:47)

David Grudl
Nette Core | 8128
+
0
-

Tzv. „dokumentační projekt“ skutečně zamrzl a budu se muset do dokumentace pustit sám. Každopádně mám v plánu co nejdříve vydat nějaké release candidate 0.9, kde budou všechny zásadní změny promítnuty a budu moci k tomu napsat dokumentaci. Tato verze bude nabízena jako výchozí ke stažení a 0.8 zůstane jen v archívu. Tudíž verzování dokumentace zatím nebude potřeba.

ad prepare<View> – obecně chyba a přesměrování může nastat kdykoliv. Na výstup se totiž něco posílá až po zavolání render<View> (tedy pokud aplikace sleduje obvyklý životní cyklus). Z toho důvodu by třeba nastavování hlaviček Content-Type mělo být až na konci render<View>, ideálně přímo v šabloně. Proto myslím, že eleminace prepare v těchto věcech dělá více jasno.

Petr Motejlek
Člen | 293
+
0
-

David Grudl napsal(a):

Tzv. „dokumentační projekt“ skutečně zamrzl a budu se muset do dokumentace pustit sám. Každopádně mám v plánu co nejdříve vydat nějaké release candidate 0.9, kde budou všechny zásadní změny promítnuty a budu moci k tomu napsat dokumentaci. Tato verze bude nabízena jako výchozí ke stažení a 0.8 zůstane jen v archívu. Tudíž verzování dokumentace zatím nebude potřeba.

Teď tomu tak trochu nerozumím – říkáš, že 0.8 bude v archívu a jak v tomto vlákně, tak jinde lidem, kteří používají 0.8 doporučuješ, aby u ní zůstali, pokud nehledají něco nového. Pro ně by ale přece dokumentace pro 0.8 někde být měla, ne? Pak by měla být pro lidi s 0.9. A potom by mělo být něco jako devel verze, do které budeš házet (z ní mazat) co se ti zachce a dokumentaci nebudeš muset moc řešit, protože tu verzi nebudeš oficiálně podporovat jako stabilní.

Myslím, že něco podobné tomu postupu z Debianu by se do Nette mělo dostat a mělo by to postihnout jak zdrojový kód, tak dokumentaci. Vážně si představ nováčka, který na stránky Nette zavítá. Něco si stáhne, zkusí – hezké, napíše si web (to mu bude trvat třeba 2 měsíce) přičemž během té doby neudělá žádnou aktualizaci, protože to prostě nepotřebuje – vše mu běhá, jak má. Před vydáním narazí na něco, co předtím nepotřeboval (nebo si myslel, že je mu to jasné). Tak tedy mrkne do dokumentace – aha, ono tohle se dělá jinak (předtím se to třeba dělalo tak, jak to zkoušel, ale protože dokumentace je nová a jeho Nette staré, nejde to dohromady). Zkusí použít tu informaci z dokumentace, to ale taky nefunguje, protože v jeho staré verzi není to, co se v dokumentaci uvádí. Dobře, tak si řekne, že je prostě nová verze – udělá update a hle – přestane mu fungovat téměř celá aplikace, protože použil něco, co bylo vyhozeno nebo naprosto předěláno :D. Teď by mě zajímalo, kde takový chudák najde informace o tom, jak to má buď udělat ve staré verzi, nebo jak předělat jeho kód na novou verzi. Mě osobně by se zamlouval postup asi takový:

  1. Současnou 0.8 prohlásíme za stable a udělá se přesně pro ní dokumentace.
  2. Úplně nové featury se začnou tvořit v unstable (development?) větvi, ke které nemusí být dokumentace, měl by postačit Changelog).
  3. Jakmile se dodělá 0.9 do úrovně, že je použitelná, prohlásí se za testing (už se do ní nebude nic přidávat ani z ní nic odebírat) a začne se přesně pro ni tvořit dokumentace.

Na stable a testing větve se budou dělat záplaty kvůli překlepům atp (však to známe všichni), ale nebude so do nic nic přidávat ani z nich nic odebírat.

4. Ta development verze jistě časem dojde do stádia, kde je teď 0.9, takže proces by zafungoval tak, že stable (současná 0.8) i s dokumentací se přesune do archivu. Stable místo přijme současná 0.9 a testing dostane v té době aktuální development větev.

Myslím, že je to docela průhledné a použitelné, co myslíš ty, Davide? ;).

>

ad prepare<View> – obecně chyba a přesměrování může nastat kdykoliv. Na výstup se totiž něco posílá až po zavolání render<View> (tedy pokud aplikace sleduje obvyklý životní cyklus). Z toho důvodu by třeba nastavování hlaviček Content-Type mělo být až na konci render<View>, ideálně přímo v šabloně. Proto myslím, že eleminace prepare v těchto věcech dělá více jasno.

To o chybě v průběhu foreach v rámci šablony jsem se snažil naznačit už v mém postu. To je samozřejmě věc, které se nedá utéct. Pokud to tedy je tak, že skutečné renderování („vykreslení“, tedy „odeslání prohlížeči“) se má dít až po render<View>, tak by logicky vzato spíš ta metoda prepare<View> měla zůstat a render<View> by se mělo zrušit, protože k odeslání prohlížeči se tedy dělá jinde.

ad Content-Type v šabloně – to je zajímavá myšlenka (pokud šablona tuší, jak se má vracet – např. RSS šablona (application/xml), XHTML šablona (application/xml), HTML šablona (text/html), tak by bylo dobré to nějak rozumně zařadit přímo do šablony – takový úvodní blok v šabloně, který by se načetl a takovéto informace by se z něj zjišťovaly (pokud by to bylo jen kvůli Content-Type, tak by to byl trochu overkill, ale zajímavá myšlenka to je ;)). Na druhou stranu jsou chvíle, kdy mám presenter, který šablonu nepotřebuje a vůbec ji nerenderuje – např. věc, která se připojí k DB, vytáhne z ní binární data a ty vybleje uživateli (tam by šablona byla na 2 věci ;)); mám dokonce dojem, že tenhle případ použití se tady ani nikde neřeší :D. Když nad tím tak přemýšlím, jak bys ten Content-Type nastavil z šablony? Pomocí $this->getHttpResponse()->setContentType() nebo je na to nějaká super proměnná přímo pro šablonu (pokud jo, byla by to novinka o které se také nikde nepíše :D)).

David Grudl
Nette Core | 8128
+
0
-

m0t3jl napsal(a):

1. Současnou 0.8 prohlásíme za stable a udělá se přesně pro ní dokumentace.

Ono se to lehko řekne „udělá se přesně pro ní dokumentace,“ ale ono to znamená, že si na měsíc sednu a budu psát dokumentaci. Což ostatně budu, mám na to vyhrazený celý srpen. Ale když už ji budu psát, tak rovnou pro 0.9, kterou do té doby chci dotlačit do verze testing.

Můžu ti dát v mnoha věcech za pravdu, ale prostě realita je neúprosná.

ad Content-Type v šabloně – to je zajímavá myšlenka (pokud šablona tuší, jak se má vracet

Spíš si říkám, kdo jiný by to měl tušit. V šabloně proto můžeš použít makro {contentType text/html}

Petr Motejlek
Člen | 293
+
0
-

David Grudl napsal(a):

m0t3jl napsal(a):

1. Současnou 0.8 prohlásíme za stable a udělá se přesně pro ní dokumentace.

Ono se to lehko řekne „udělá se přesně pro ní dokumentace,“ ale ono to znamená, že si na měsíc sednu a budu psát dokumentaci. Což ostatně budu, mám na to vyhrazený celý srpen. Ale když už ji budu psát, tak rovnou pro 0.9, kterou do té doby chci dotlačit do verze testing.

Můžu ti dát v mnoha věcech za pravdu, ale prostě realita je neúprosná.

ad Content-Type v šabloně – to je zajímavá myšlenka (pokud šablona tuší, jak se má vracet

Spíš si říkám, kdo jiný by to měl tušit. V šabloně proto můžeš použít makro {contentType text/html}

Jak jsem psal – buď to tuší ta šablona (v případě, že se používá), nebo to tuší sám presenter (resp. view), pokud šablona není ;).

Nemusíš plnit tu mou vizi do puntíku, nicméně to ber jako takový hlas prozřetelnosti (:D), jednou to prostě musí přijít. Myslím, že by verzováním dokumentace taky odpadlo dost dotazů tady na fóru, který jsou přesně na tohle zaměřený – něco se změnilo, v dokumentaci to není, a bohužel ne každej nahlídne do kódu. Fórum by se pak konečně věnovalo těm velkým věcem :).

marek.dusek
Člen | 99
+
0
-

„Vážně si představ nováčka, který na stránky Nette zavítá“

To jsem presne ja – prvni skeleton jsem spustil dnes rano. Bylo fajn proklikat quick start, ale nebylo uplne prijemne, ze web ukazal snippet a), v prilozenem archivu byl snippet b) a s posledni revizi kod vubec nefungoval :)

Kazdopadne klobouk dolu pred autorem – ja bych si Nette piplal sam, bez „zavazku“ „komunite“, bez nutnosti myslet na nejakou BC a dokumentaci – stoji ta „slava“ vubec za to?

Jerry123456789
Člen | 37
+
0
-

Jasně, ta „sláva“ stojí za to vždycky. Davidových 15 minut slávy trvá už… no… jak dlouho tu máme Nette, Texy a Dibi? :D