Nette Framework: Refactoring

Minule jsme si ukázali vývoj jednoduché webové aplikace podle architektury Model-View-Presenter v Nette Frameworku. Dnes se ji pokusíme vylepšit, poukázat na kritická místa a předvést jejich správné řešení.
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
Automat na kávu v minulém díle demonstroval, jak lze webovou aplikaci rozdělit do tří logických vrstev, tedy model, presenter a pohled (view). Zůstaňme u tohoto dělení i nadále a podívejme se, jak můžeme jednotlivé vrstvy vylepšit.
Model
Tuto rovinu aplikace v seriálu záměrně ošidím. Nette Framework je zaměřený především na podporu prezentační vrstvy, přičemž pro model nabízí „jen“ vyšší standard programování v PHP reprezentovaný třídami Object a Debug, dále autoloading nebo třídu Environment, které na vás čekají v šestém díle seriálu. Žádné jiné konkrétní knihovny, třeba pro práci s databází, v něm nenajdeme.
Jde o poměrně důležitý rys frameworku, který lze přeložit jako: „používejte knihovnu která vám vyhovuje, žádný styl práce vám nenutíme.“ Důsledná nezávislost na databázové vrstvě vám dovolí používat ORM nástroje jako je Doctrine či Propel, knihovnu Zend_Db_Table nebo z opačného ranku například Dibi. Poslední jmenovaná knihovna je preferovaná díky úzké vazbě na Laděnku a dokonce ji najdete přímo v distribučním archívu.
Pro hnidopichy: pokud máte pocit, že prezentuji jako přednost něco, co je ve skutečnosti nedostatek frameworku, pak si prostě představte, že databázovou vrstvu frameworku tvoří Dibi a rozdělení do dvou samostatných projektů berte jen jako politické rozhodnutí.
Model tedy refaktoringu ušetříme, nikoliv však proto, že by to nepotřeboval, ale protože to seriál neposune dál.
Presenter
Na rozdíl od modelu nás u presenteru čeká pořádná práce. Jak už jsem naznačoval minule, reakce uživatele zhmotněná do odkazu může žádat o:
- změnu pohledu, presenteru nebo stavu (stavem je například hodnota parametru $money)
- vykonání příkazu (po vhození mince, stisknutí tlačítka)
Zapamatujte si důležitou zásadu: po vykonání příkazu by vždy mělo následovat přesměrování na další stránku. Nebo jinými slovy, URL žádající o provedení příkazu mohou být součástí HTML stránky, ale v adresním řádku prohlížeče by měly být jen URL určující stav. Po vykonání příkazového URL přesměrujeme na stavové URL.
Vezměme si automat na kávu. Pokud už v něm jsou 2 Kč a vhodím pětikorunu, tak prohlížeč zavolá příkazové URL:
index.php?money=2&do=insert&coin=5
ze kterého, jak káže moudré pravidlo, bychom měli přesměrovat na stavové URL říkající „v automatu je 7 Kč“:
index.php?money=7
Kdyby k přesměrování nedošlo, byl by to prohřešek proti logice HTTP protokolu, kde URL mají reprezentovat stav, proti SEO, neboť vyhledávač vidí stejný obsah na více rozdílných URL, a především proti použitelnosti. Do historie prohlížeče by se nám dostala adresa, po jejímž otevření, ať už tlačítkem obnovit nebo zpět, by došlo k nechtěnému vhození dalších mincí do automatu. Což zamrzí obzvlášť proto, že automat nevrací.
Nebylo by jednodušší generovat rovnou stavové URL? V tomto případě by to asi možné bylo, jsou ale situace, kdy to nelze. Představme si například katalog produktů řazených podle abecedy s odkazem na předchozí a následující položku. Zjištění konkrétního ID není úplně triviální a pokud by takových odkazů bylo na stránce hodně, mohlo by to aplikaci zbytečně brzdit. Stejně tak pokud se obsah katalogu často mění, nelze v době vykreslování přesně říci, který produkt bude při kliknutí na odkaz tím předchozím nebo následujícím. Odkaz proto bude lepší sestavit z ID aktuálního produktu s příkazem next
nebo prev
.
Ale stále platí pravidlo o přesměrování. Jinak by se mohlo stát, že na e-shopu, kde nabízejí od šroubku po lokomotivu, si budete prohlížet produkt „záložní zdroj“, načež kliknete na odkaz „další produkt“ a dostanete se na „zdarma parfém Sergio Tacchini“. To je něco pro přítelkyni, řeknete si, a odešlete jí odkaz e-mailem a připíšete: Na tohle by ses měla vážně podívat! Bohužel půjde o příkazový odkaz, jako třeba index.php?id=zalozni-zdroj&do=next
, a dříve, než se přítelkyně na stránku podívá, obchod rozšíří sortiment mimo jiné o přípravek proti „zápachu z úst“. Abecední řazení ho neomylně umístí ihned za záložní zdroj a zadělá vám tak na krizi vztahu. I kdyby to vaše polovička přešla, vy situaci znovu rozdmýcháte večer nevinnou otázkou: „Miláčku, dostala jsi ten e-mail? Co na to říkáš?“
K přesměrování slouží metoda redirect()
, která se používá stejně, jako metoda pro generování odkazů link()
. Takže upravíme metodu handleInsert
tak, aby po provedení úkonu přesměrovala. A kam? To je dobrá otázka. Cílem bude aktuální stránka, aktuální stav a k tomu použijeme speciální slovo this
.
public function handleInsert($coin)
{
// zvýšíme hodnotu vhozených mincí
$this->money += max(0, (int) $coin);
// po příkazu musí následovat přesměrování
$this->redirect('this');
}
Presenter a jeho pohledy
Řetězec this
doslova znamená aktuální pohled. Až dosud jsme si vystačili s presenterem majícím jediný pohled a jedinou šablonu, jehož název byl default
. Odsud třeba název šablony Machine.default.phtml
složený z názvu presenteru, pohledu a přípony .phtml
. Přesměrování na this
je pak ekvivalentní s přesměrováním na $this->redirect('default')
.
Kromě toho, že pohled určuje, která šablona se má zobrazit, zavolá Nette Framework také metodu render{NazevPohledu}()
, kde je možné naplnit šablonu daty. V případě pohledu default
by se tedy volala metoda renderDefault()
. Její existence je přitom nepovinná.
Na řadě je refactoring metody handleBuy()
. Navrhl bych následující postup: pokud se koupě kávy zdaří, přesměrovat na nový pohled coffee
, tedy takové potvrzení objednávky. V případě neúspěchu nepřesměrovávat vůbec.
public function handleBuy()
{
$model = new Model;
$result = $model->buyCoffee($this->money);
if ($result) {
$this->money = 0; // vynulujeme částku (automat nevrací)
$this->redirect('coffee'); // přesměrujeme na pohled coffee
} else {
$this->template->display = 'Málo peněz';
$this->template->robots = 'noindex,noarchive';
}
}
Všimněte si, že odkazy na příkazy se od odkazů na pohledy odlišují vykřičníkem. Tj. $presenter->link('buy!')
volá metodu handleBuy()
(v rámci stále stejného pohledu default
), zatímco odkaz $presenter->link('coffee')
resp. $presenter->redirect('coffee')
vede na pohled coffee
. Po přesměrování se Nette Framework pokusí nejprve zavolat metodu presenteru renderCoffee()
, která může naplnit šablonu daty, a poté vykreslí šablonu v souboru Machine.coffee.phtml
. Protože taková šablona neexistuje, odpovědí bude HTTP chyba 404 – Not Found. Takže šablonu vytvoříme – bude zobrazovat kávovar s kelímkem hotové kávy, bez mincí.
Zbývá vysvětlit, proč v případě chyby přesměrování pomíjím. Je to totiž situace, kde opakování požadavku tlačítkem Obnovit je naopak vítané. Pokud byla chyba na straně automatu (došla voda), po jejím napravení lze objednávku zopakovat a zakončit úspěchem. Pokud by však došlo k přesměrování na stránku s chybovou hláškou, obnovení stránky by nevedlo k dalšímu pokusu provést transakci a přesto, že by se chyba na straně automatu vyřešila, stránka by stále zobrazovala tutéž již neaktuální chybovou zprávu.
Ačkoliv nedojde k přesměrování, doporučil bych stránku vyřadit z indexu vyhledávačů. Za tímto účelem jsem do šablony předal proměnnou $robots
.
Poslední úprava presenteru souvisí s tím, že už máme dva pohledy: default
a coffee
. V pohledu coffee
nestojíme o to, aby se na displeji zobrazovala výzva k vhození peněz, kterou má na svědomí metoda startup()
. Naplnění šablony daty proto přesuneme na vhodnější místo – do již zmíněné metody renderDefault()
.
public function renderDefault()
{
if (empty($this->template->display)) {
// na displeji zobrazíme celkovou částku nebo výzvu k vhození peněz
$this->template->display = $this->money ? "$this->money Kč" : ('Vhoď ' . Model::COFFEE_PRICE . ' Kč');
}
}
Zazněly tu názvy metod jako startup()
, handle{Příkaz}()
, render{Pohled}()
… nejvyšší čas se podívat na životní cyklus presenteru:

Životní cyklus presenteru
Netrapte se tím, že obsahuje celou řadu metod, které jsme zatím neprobírali. Dostaneme se k nim později. Teď je důležité pořadí, v jakém se jednotlivé metody volají (odshora dolů). Vidíte, že metoda renderDefault()
se volá později, než handleBuy()
. Protože v ní v případě neuskutečněné objednávky naplníme $this->template->display = 'Málo peněz'
, doplnil jsem renderDefault()
o podmínku, jestli je proměnná display
prázdná, abychom si ji nepřepsali.
Optimalizujeme pohled
Máme už dva pohledy a také dvě šablony Machine.default.phtml
a Machine.coffee.phtml
. Obě se přitom liší jen obsahem elementu <body>...</body>
, zbytek je stejný. Protože kód by se neměl opakovat ani v případě šablon, přistoupíme k refactoringu spočívajícím v zavedení šablony layoutu. Tu uložíme do souboru @layout.phtml
. Zavináč na začátku názvu slouží k přehlednému odlišení od šablon pohledů.
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en" />
<?php if (isset($robots)):?><meta name="robots" content="<?php echo $robots ?>"><?php endif ?>
<title>Coffee Vending Machine in Nette Framework</title>
<style type="text/css">
...
</style>
</head>
<body>
<?php $content->render() ?>
</body>
</html>
V souborech Machine.default.phtml
a Machine.coffee.phtml
zůstane jen vnitřek stránky. Vloží se do šablony layoutu v místě volání $content->render()
.
V podstatě jsme připraveni na poslední krok, a tím je slibovaná podpora AJAXu. Pokud se však podíváte na šablony, ruku na srdce, nejsou zbytečně moc ukecané a nepřehledné? Co byste řekli na to, kdybychom mohli místo zápisu:
<div id="machine">
<p id="display"><?php echo htmlSpecialChars($display) ?></p>
<a href="<?php echo $presenter->link('buy!') ?>"><img id="button" src="images/button.png" alt="Kup kávu" /></a>
<?php if (isset($coffee)): ?>
<a href="<?php echo $presenter->link('default') ?>"><img id="cup" src="images/cup.png" alt="Kelímek s kávou" /></a>
<?php endif ?>
</div>
používat raději něco přehlednějšího, úspornějšího a hezčího, třeba něco takového:
<div id="machine">
<p id="display">{$display}</p>
<a href="{link buy!}"><img id="button" src="images/button.png" alt="Kup kávu" /></a>
{if isset($coffee)}
<a href="{link default}"><img id="cup" src="images/cup.png" alt="Kelímek s kávou" /></a>
{/if}
</div>
Jak na to si ukážeme v příštím díle.
Autor článku je vývojář na volné noze, specializuje se na návrh a programování moderních webových aplikací. Pravidelně pořádá školení pro tvůrce webových aplikací, vyvíjí open-source knihovny Texy, dibi a Nette Framework.
DAvid Grudl je totalny saso. clovek ktory si vytvara vlastny svet, clovek ktory sa nedokaze pressadit nikde inde, clovek ktory by v timovom projekte ktory bezi uz roky a dalsie roky bude bezat nikdy nedokazal presadit, clovek ktory je mimo, clovek co vymysla ulohy na urovni lahsich semestralnych zadani, clovek ktory si vymysli problem aby na neho napisal jedno z mnohych rieseni, trapos, ktory si mysli ze je genialny, ale pritom je to podpriemerny programator. Proste taky tutmacik, co ma 30 rokov, a zatial co it-ckari v jeho veku premyslaju nad tym kam vrazit tucne premie, tak on zobre o kazdu stokorunacku. proste „davidek debilek“ co sa strapnuje pred celym CS krajom, co sa prihlasuje na kadajeke mozne sutaze a webexpa lebo je presvedceny ze je genius. ;)
Tenhle generator jeste neznam, za pripadny link dik.
A ja si to uz davno rikal, ale nenasel jsem v sobe prezentacni potencial na vytvoreni podobneho shrnuti!
BTW, jake skoly/praxi je potreba mit aby clovek dokazal sesmolit neco jako tohle?
Nejaka nova verze slovenskyho Hulanatoru?
Prosím o zmazanie príspevku "saso pocmafrany", ktorý je vulgárny, urážajúci a porušuje elementárne pravidlá slušného správania, nič nové neprináša a znižuje úroveň diskusie na Zdrojáku. Nehovoriac o tom, že autor toho príspevku je zbabelec, ktorý sa ani nedokáže podpísať pod svoje slová. Možno preto, že nikdy nedokázal ani to, čo DG, keď má čas písať takéto primitivizmy. Podotýkam, že DG je mi celkom ukradnutý, nie som členom jeho fanklubu, ale uráža ma, keď musím čítať takého vulgarizmy na kohokoľvek. Zdroják považujem sa seriózny portál pre webových vývojárov a veľmi si cením prácu Martina Hassmana, ale chcel by som redaktorov poprosiť, aby si strážili kvalitu diskusií, aby bola zachovaná odbornosť a vecnosť a nebola zaspamovaná podobnými výlevmi zakomplexovaných primitívnych hulvátov. Potom môžete zmazať aj môj príspevok, lebo nič nové do odbornej témy článku neprináša. Ďakujem.
1. Nemuzu si pomoct, ale "drzet" stav mincovniku a jednotlive akce resit pomoci GETu i pres tvoje argumenty mi prijde jako zhuverilost. Chapu ze je to pro priklad, ale…
2. Proc upravovat zobrazeni dat v controleru? Jednak pripadna lokalizace se bude hledat po celym kodu a jednak to nejde koncepcne moc dohromady pro vic moznych verzi zobrazeni(html, xml, json…). Jestli v tomhle spociva "presenter" tak uprimne fuj :P
3. K cemu htmlSpecialChars($display) – v te osekane sablone to snad probuh nedela automaticky ze ne?
2. Celkem souhlasím – proč by se měl controler o to zajímat? View to může zobrazit obrázkem, poslat id přes JSON, …
3. Cože? Jak to chceš udělat? Něco ala magic_quotes_gpc? Pokud k 2. říkáš "fuj?", pak k tomto říkám "dvakrát fuj!".
ad 3) viz http://nettephp.com/cs/quick-start-6
{$variable} => <?php echo htmlspecialchars($variable) ?>
{!$variable} => <?php echo $variable ?>
atd.
Add 2. Metoda renderXYZ(…) patří do pohledu (MVC neříká že kontroler musí být samostatná třída, ale že by se tyto činnosti měli oddělit. Jak to uděláš (a to záleží na konkrétním programátorovi) záleží na tobě.
ad 2: Moc otázce nerozumím. Co myslíš tím upravováním zobrazení dat? Předpokládám že controller měl být presenter.
ad 3: pokud myslíš v tom posledním příkladu, tak ano, tam se to (probuh) děla automaticky. Velmi užitečná feature.
Myslím, že 2. odpovídá tomu, co jsem k tomu připsal – View si může zobrazit stav jakkoli, třeba obrázkem. Je tedy blbost, aby mu to Presenter diktoval. Nebo ne? Pokud se mýlím, rád bych z toho omylu byl vyveden.
U 3. jsem pochopil "osekané šablony" jako to bez projetí metodou CurlyBracketFilter::invoke. Právě proto jsem se proti tomu bouřil.
Naopak, pokud myslí "ořezanými šablonami" prostě tu kratší formu, pak můžu jen souhlasit, že se to dělá "samo".
Nechci se domýšlet, na co konkrétně vedla otázka, proto se ptám. Co se konkrétně stalo v článku v presenteru takového, že to "nejde moc dohromady pro vic verzi zobrazeni(html, xml, json…)"?
Navíc v dalším pokračováním dokonce dojde k záměně HTML za JSON.
"Co se konkrétně stalo v článku v presenteru takového, že to "nejde moc dohromady pro vic verzi zobrazeni(html, xml, json…)"?"
Odpovím za sebe:
Například:
$this->template->display = 'Málo peněz';
V HTML může být výstup třeba Málo peněz, ale taky třeba JS, obrázek (no dobře, proti tomu lze něco namítnout…), může být potřeba měnit html třídu a další věci. Tato logika by se musela přesunout do Presenteru, kde mi nevoní – je to logika Viewu.
V XML by to mohlo být třeba <error id="not-enough-money"> a podobně v JSON.
"Navíc v dalším pokračováním dokonce dojde k záměně HTML za JSON."
Vyžaduje to manipulaci s Controllerem?
OT: "v dalším pokračováním" není správně – má být "v dalším pokračování". SRY za OT, ale uhodilo mě to do očí.
Tady je jasně definované rozhraní mezi presenterem a šablonou, do nějž patří string $display. Zdůrazňuji, že nijak neupravený pro "předpokládané" potřeby výstupu. Prostě čistý řetězec.
Je pak věcí šablony, jestli jej zobrazí v <p id="display">{$display}</p>, v JavaScriptovém alert($display) nebo třeba XML <error id="not-enough-money">$display</error> (pro tento případ by bylo vhodné rozšířit API o příznak chyby nebo chybový kód).
Řekl bych, že si děláš ve věci zmatek žonglováním s termíny controller, presenter, view a šablona. Controller není presenter, view není šablona. Píšeš "tohle mi nevoní, tohle je logika view" ale hovoříš o šabloně – ptám se, je také to "logika šablony"? Neplatí, že hranice mezi částmi Model-View-Controller kopíruje hranici tříd (nebylo tomu tak ani v tzv. klasickém MVC, kde view a controller implementovala jedna třída).
"Řekl bych, že si děláš ve věci zmatek žonglováním s termíny controller, presenter, view a šablona."
Na tom asi něco bude.
"Neplatí, že hranice mezi částmi Model-View-Controller kopíruje hranici tříd" + "pro tento případ by bylo vhodné rozšířit API o příznak chyby nebo chybový kód"
Někdy je to IMHO vhodnější. Nevoní mi přidávání nějakého stavového kódu v Presenteru nebo Controlleru (tady je to jedno, ne?) jen kvůli tomu, že to chce View?
Pokud to chápu, tak tady se přesouvá část View do Presenteru, aby místo celého View byla šablona.
"nebylo tomu tak ani v tzv. klasickém MVC"
Proti této argumentaci lze použít citaci z http://zdrojak.root.cz/clanky/nette-framework-mvc–mvp/ : "Historický exkurz měl ukázat, jak různorodé bylo pojetí MVC už v okamžiku vzniku. Byla to holt doba pionýrská. Nechápejte proto MVC dogmaticky!"
Pokud jde jen o nějaké "mimochodem", pak budiž.
> Pokud to chápu, tak tady se přesouvá část View do Presenteru, aby místo celého View byla šablona.
Dalo by se říct, že metoda renderDefault() je součástí vrstvy View, společně se šablonou. Takže přesouvání kódu mezi renderDefault a šablonu se děje spíš v zájmu toho, co je vhodné mít v šablonovací logice a co už nikoliv. Ale ať už je to tam nebo tam, vždy je to součást View.
Rovnou odpovím i na otázku, proč tedy presenter nerozdělit na svě samostatné třídy: v Nette to lze poměrně snadno provést, ale nejpozději ve chvíli, kdy se začnou používat komponenty, se to ukáže jako velmi nepraktické. Proto místo dělení na dvě třídy se používá dělení na životní fáze.
> různorodé bylo pojetí MVC už v okamžiku vzniku
Tím "klasickým MVC" se obvykle myslí jeho první implementace. Která původní návrh moc přesně nedodržovala.
No já si teď neumím přesně představit ty problémy, i když možná tuším – počkám, až ukážeš komponenty a pak to rozeberu.
Přiznám se, že Nette se mi původně zdálo divné, ale vypadá to, že všechny ty divnosti mají svůj účel a logické opodstatnění.
A na klasické MVC bych se neodkazoval, když mluvíme o současnosti…
2. Myslel jsem přesně to co psal v6ak. Přeci i tak pracuješ v šabloně s logikou (isset($coffee)), proč tedy mít logiku na dvou místech? …to není problém frameworku, jen mé pragmatické smýšlení nechápe proč to dělat takhle, když to víc věcí komplikuje…
3. Pokud existuje i varianta {!…} tak samozřejmě ok, jen se chytám toho, že ze spousty "automatických" featur zvětšiny pro začínající "programátory" při pokročilejším programování smrděly problémy.
Tak zkus schválně navrhnout jiné a lepší řešení tohoto konkrétního úkolu. Jaké proměnné dávat do šablony a jakou logiku mít v šabloně.
Spíš než návrh ti napíšu jak to funguje v Symfony. Má taky svoje nevýhody, ale řekněme že funkční kód od šablony odděluje imho velice dobře.
1. Má sloty, což vypadá asi takhle:
layout:
<meta name="robots"><?php if(!include_slot('robots')): ?>index,archive<?php endif; ?>" />
a v html šabloně erroru:
<?php slot('robots', 'noindex,noarchive') ?>
2. Má komponenty a helpery. Šablona komponenty Display (sf_user je v šablone obal okolo session, v akci je pod $this->getUser()) includovaná do layoutu:
<?php echo $sf_user->hasMoney() ? format_currency($sf_user->getMoney()) : __("Insert coins") ?>
3. A z akce v controleru ti zbyde např. jenom
executeBuy() {
if(!$this->getUser()->buyCoffie()) return sfView::ERROR;
}
Ve finále máme čistej controler, logika html šablon je čistě v šablonách, můžu si navymýšlet šablony pro xml i json aniž bych musel šáhnout do "funkčního" kódu.
Díky, takto je to mnohem názornější.
Ve své podstatě se snažíš veškerou zobrazovací logiku přenést do šablony. V případě chyby se použije jiná (error) šablona, o obsahu displeje rozhoduje šablona na základě surových dat z modelu.
Po technické stránce lze totéž udělat i v Nette (jen místo "slotu" je "block", helper zůstává helperem a "return sfView::ERROR" se nahradí za "$this->setView('error')"). Rozdíl zůstává v rovině řekněme filosofické.
Namítl bych, že jsi do šablon umístil nikoliv "logika html šablon", ale "logiku celého view", ačkoliv šablony jsou jen podmnožinou view. Tohle se mi nelíbí. V tomto konkrétním případě mi také vadí duplicita šablon, existence cca stejných šablon automatu lišících se jen hláškou na displeji.
O "čistej controller" tady nejde – v Nette je kontroler víceméně součástí frameworku a controller != presenter. Presenter na sebe bere břemeno udržet čisté šablony a udržet také čistého sám sebe.
V praxi je jednodušší zadat kodérovi výrobu šablony s tím, že "v této proměnné je obsah displeje, v této indikátor chyby, atd.", než "tady najdeš cenu nápoje, tady obsah mincovníku a v tomto dokumentu je popsána logika, kterou musíš respektovat". Nebo ne?
No, můj způsob práce je co nejvíc oddělit formu zobrazení a kód, který manipuluje s objektama před zobrazením(ať už tomu říkáme jakkoliv) abych si mohl se šablonama dělat co chci a v kontroleru mi zase nerušily řetězce. Ty budeš takhle asi vždycky vázán na presenter, který s jinou formou prezentace budeš muset měnit. Já si v routování akorát nastavím "koncovky" šablon – a mám z výstupu instantní rss, json atd. Jestli správně hádám tak ty vytvoříš buď další presenter(opakující se nebo nadbytečný kód) nebo budeš nastavovat šablonu podle nějaké proměnné(budeš směšovat kód pro různé šablony)…
Nicméně máš pravdu, s kodérem to budeš mít mírně jednoduší ty, já bych teda z nich nedělal přímo cvičené opičky…s lehčí nápovědou taky lecos pochopí :)
To, co se v Symfony nazývá akce, je v Nette rozdělené na akci a view. Metoda renderDefault patří k view a plní obvykle podobnou funkci jako Code Behind v ASP.NET (prostě věci, které se člověku nechce dávat do šablony, ale přitom je to pouhá prezentační logika).
Pokud by k akci chtěl přidat další view, tak by přidal pouze další renderNěco metody, ve kterých by si data upravil pro jiné šablony (při použití push šablon), kód samotné akce by se neopakoval. Akorát by teda do akce musel přidat nějakou logiku pro výběr view, třeba na základě parametru z routy, např:
Viz http://nettephp.com/cs/action-vs-view
Chtěl jsem se zeptat, v jakém SW vznikl "Životní cyklus presenteru" – tedy obrázek: http://i.iinfo.cz/urs/nette-401-123844392320589.gif ?
Experimentoval jsem s ArgoUML, ale to tak hezky nestínuje.
Omlouvám se za OT k tomuto článku.
To je kombinace stařičkého Corelu a bitmapového editoru pro přidání stínu ;)
Podobne obrazky lze relativne snadno tvorit v OmniGraffle (Mac-only, sorry ;-)) – velmi doporucuju.
Pan Grudl, prestante bojovat s veternymi mlynmi ;-), skuste sa pozriet na http://ics.upjs.sk/~novotnyr/java/spring/spring-mvc.pdf Mozno tam najdete odpovede na mnohe svoje otazky. S pozdravom uspesny konverter zo sveta PHP do sveta Javy. ;-)
Ak náhodou nie si nejaký geroj, čo sa zamestnal v nejakej firme, berie pravidelne plat za to že obetuje svojmu zamestnávateľovi X hodín zo svojho bezvýznamného programátorského života tak by tento tvoj komentár mohol mať aj nejakú výhu, v opačnom prípade músim skončtatovať, že je podstatný rozdiel medzi životom robotníka v kompromise a životom človeka ktorý „riadi“. Hlavne je tak trochu trápne mu dávať nejaké rozumy. Ja osobne mám väčší rešpekt voči ľuďom ktorí sa nad vecami zamýšľajú a rišenia prinášajú ako nad tými ktorí ich „iba“ akceptujú.
S týmto súvisí aj môj obľúbený citát o (priemyselnom) dizajne. „Design is about how something works, not how it looks. It's what's inside that counts.
The best designs are the result of someone's questioning everything around them.“ (James Dyson)
Treba sa občas snažiť aj o veciach premýšľať. Nie sa na ne iba slepo adaptovať. Hlavne v prípade ak človek chce niekam pokročiť vo vývoji.
Lenze to vobec nie je o tom, ze sa nad niekym chcem vyvysovat. Pan Grudl je urcite inteligentna ludska bytost a snazi sa optimalne riesit problemy suvisiace s tvorbou webovych aplikacii. V PHP som programoval zhruba tri roky a takisto som si naprogramoval spustu kniznic, ktore mi v danej chvili riesili problemy – a na druhej strane vytvarali nove. S casovym odstupom mozem tvrdit, ze som vtedy programoval dost s klapkami na ociach. Budete sa divit ale prave konfrontovanie s pracou inych ludi ma posunulo niekam inam. Ono je totiz rozdiel vymyslat aplikaciu , ktoru bude programovat jeden clovek a za mesiac sa odovzda, objedna sa cez nu par objednavok na nete a po pol roku domena zanikne. Studoval som framework pana Grudla, nette aj dibi, avsak pri vsetkej ucte si dovolim tvrdit, ze nie je vobec vhodny pre timovu pracu na projekte ktory trva roky a na ktorom spolupracuje desat dvadsat programatorov. A o tom to je, naco vymyslat mini pidi frameworcok, bez ambicii aby prekrocil tien miniwebiku, ktory je one man show? Zrejme ste si len potrebovali slovne ulavit, alebo mate stereotypnu predstavu o tom ze timova firemna praca programatora spociva len v bezhlavom prijimani prikazov od skusenejsich programatorov a lepenie kodu len aby fungoval. Divili by ste sa kolko toho vasho hlbaveho premyslania na dizajne sa v takomto projekte vyskytne. ;-)
Každý framework se hodí na něco jiného. Že se podle Vás nehodí Nette na projekt na 30 člověkolet, to je docela dobře možné. Uvědomte si ale, že takových projektů je ze všech vznikajících webů sotva půl procenta (Zdroj: vycucáno z prstu).
Nette může být naopak mnohem lepší/jednodušší/snažší na údržbu a na kvalitu zdrojáků pro 80 % projektů.
Nechci a nebudu tady kopat ani do Springu, ani do Nette. Každý framework má to svoje. A brečet, že se assembler nehodí na kódování webu a ruby na programování operačních systémů (nadsázka), to je prostě zcela mimo.
Zopar poznamok.. je kopec zle urobenych projektov (ci uz je to pidi projekt alebo enterprise solution) ktore funguju a zarabaju peniaze cele roky.. a je jedno ci ich robi velka alebo mala firma.. to vedia vsetci co robili vo velkych aj malych firmach. Nejde totiz o to na com sa to robi, ale presne o to hlbave premyslanie, o analyzu, riadenie projektu a ostatne hovadiny. Je fakt jedno ci je to one man show alebo velky projekt, pre to ci projekt bude mat navratnost alebo ustoji svoj rast to nie je absolutne podstatne. Najviac sa mi pacia vsetky tie korporacie co sa snazia po mesiacoch usilia na webe pretlacit svoje portaly a smejem sa ked to ich sefstvo zasa po mesiacoch zaklincuje pretoze to nefunguje a potom sa pozries kusok vedla kde na kolene niekto zbuchal nejaku somarinu ktora zaraba miliony dolarov rocne. A potom pride nejaky mudrlant a zacne rozpravat nieco o timoch a jedinom uzasnom skvelom frameworku. ;-) To je ta relativita.
Kazdopadne. Myslim ze si ma uplne presne pochopil. To mi staci, netreba riesit. Kludne si uzivaj svoj jednoduchy zivot a povazuj to za spravne rozhodnutie vo svojom zivote. Mozno je to naozaj to spravne rozhodnutie. Zamestnat sa, mat pravidelny prijem, postavit dom zasadit strom, postarat sa o rodinu. Kazdy je strojcom svojho osudu. Niektori si budu pidizlikat nejake smiesne vlastne riesenia a trpiet cely zivot.. mozno z toho nic nebude.. ale pre pokrok je to rovnako dolezite ako udrzanie rodu. ;-)
Přece jen segment menších webů je celkem velký na to, aby i pro nějmohli existovat frameworky, ktere jejich vyrobu zefektivní. Na ty velke tu jsou pak pracanti jako vy.
To bylo celkem trefné. Stejnětak, dohnáno do extrému, pro víceméně statické stránky obsahující kontakt, otevírací dobu a základní info bude MVC spíš zbytečné zdržování. Dynamické stránky se mohou použít tak nanejvýš na jednoduché šablony pro kostru stránky. Nette je zde sice asi použitelné, ale spíš zbytečné.
Podobně nelze Spring cpát kamkoli…
Cele PDF jsem poctive precetl a prekvapilo (a samozrejme povzbudilo) me to, ze je to Spring MVC, kdo by se mohl od Nette hodne ucit. I kdyz je otazka jak moc je ten dokument vypovidajici, protoze treba o MVC jsou tam velmi chybne informace. Ale nechci se prit, kdo je nebo neni lepsi, je to proste muj dojem.
Vas pri citani Spring MVC prekvapilo ze by sa mohol od Nette ucit? A kde ste na to narazili, mozno som prave ten slajd preskocil, ked to tam bolo uvedene. Nuz neviem, skromnosti zrejme nemate na rozdavanie. Tvrdit ze Spring – celosvetovo rozisreny od pidifiriem po korporacie sa moze od vas ucit. Zeby nova konkurencia pre Roda Johnsona? ;-)
namiesto nekonkretnych blabolov by ste obaja mohli prejst k samotnemu boxu.. teda ak je na to vola.. prednost by mohol mat obhajca springu.. nech sa paci ;)
Ano, ano, přesně to tvrdím a skromnosti mám doufám přiměřeně. Nepleťme si skromnost a pokoru. Tu jsem v reakci na váš komentář odhodil, i když chápu, že jste to myslel v dobrém, nicméně jsem chtěl být také provokativní. Ovšem argument "jak by něco lokálního mohlo být v jakémkoliv směru lepší než to, co vytvořill člověk s anglickým jménem", ten rozhodně neberu ;)
Pokud bychom skutečně chtěli oba frameworky srovnat, musí se to odehrát konstruktivně, v rovině rozumných argumentů – já jsem pro a klidně začnu.
První a nejdůležitější věc – nika Javy a PHP se téměř neprotíná, tyto jazyky si téměř nekonkurují. Dosud tu není plnohodnotná platforma pro hostování Javy stylem ověřeným pro PHP, stejně tak PHP nemá žádnou enterprise edici. Tudíž ani frameworky si nekonkurují a nelze říct "k čemu kvalitní framework pro PHP, když existuje Java a Spring?".
Srovnání začnu od Modelu, protože to je oblast, kterou Nette záměrně přenechává jiným knihovnám. Důvod je ten, že způsob "class diagram = E-R diagram = formulář", je sice na pohled hodně elegantní a dnes i populární, ale s praxí se překrývá jen v 80-90 % a zbývající procenta se strašně těžko pokrývají. Nette hledá jiné cesty, viz třeba http://phpfashion.com/mvc-paradox-a-jak-jej-resit.
Vedle toho controller a view můžeme velmi dobře porovnat. Podle odkazovaného PDF jsou oba frameworky v mnoha směrech velmi podobné, zaměřím se tedy na rozdíly:
– URL vrstva: zatímco Nette Framework ji zcela odděluje, Springu je s URL úzce svázaný. Díky tomu jsem mohl třeba před dvěma týdny přídáním JEDNOHO řádku http://jdem.cz/a6gq7 dosáhnout toho, že tvar všech URL v demonstrační aplikaci se dynamicky změní podle konfigurace serveru. Ve Springu by podobný efekt (opět vycházím z PDF) znamenal změnu na mnoha místech kódu kontroleru a šablon.
– kontroller: ve Springu se jim může stát jakákoliv třída, v Nette musí implementovat interface IPresenter. Je to rozdíl, ale nedokážu jej zhodnotit. Fungování metod s parametry je v obou frameworcích prakticky totožné. Předávání návratových hodnot naopak Nette nemá.
– view: odvození jeho názvu a namapování na šablonu je opět velmi podobné, jen Nette se vyhýbá konfiguraci v XML. Podobná je dokonce i syntaxe šablon. Z PDF těžko říct, který jazyk je v praxi silnější, jak to funguje v Nette uvidíte v příštím díle.
– validace dat: Nette věc pojímá trošku jinak, ale nějaký zásadní rozdíl v tom nevidím.
– odkazování: opět to souvisí s oddělenou URL vrstou, takže místo return "redirect:nejake-url.do" ve Springu se v Nette odkazuje přímo na název metody kontroleru, tj. $this->redirect('metoda'); V tom vidím zásadní přínos, místo, kde by se Spring mohl inspirovat.
Suma sumárum, Spring MVC vypadá jako velmi dobrý a šikovný framework. Nevidím ale nic, kvůli čemu bych měl padnout na kolena a opustit Nette. Naopak řada vlastností by mi chyběla.
Spring je v URL zviazany v dvoch oblastiach:
——–
* Sufixy URL adries su uvadzane v anotaciach pre kontrolery.
Tohto sa mozno alebo napisanim vlastneho mapovania requestov na kontrolery, ktory moze byt taky dynamicky ako sa ziada a jednym riadkom ho deklarovat v XML.
Nette adresy generuje plne dynamicky. Ak som pochopil spravne, tak trik spociva v pritomnosti premennej $presenter, ktorej metody vygeneruju URL. Vyzera to ako dobry napad, pretoze naozaj sa zbavite URL adries vo view vrstve.
Ale: Spring MVC absolutne neriesi, v com bude napisany view. JSP? Freemarker? JasperReports? RSS? Staci, ze view rendered dostane modelovu mapu a ako ju zrenderuje je na nom. Nette podporuje primarne PHP, ako je to s inymi vrstvami?
* Modelom moze byt lubovolna trieda. Myslim, ze uz po Struts 1.x sa ludia poucili a zistili, ze nema zmysel, aby model musel byt nieco specialne. Automaticke vkladanie navratovych hodnot do modelu je super vec. Tu sa zhodneme
* Kontroler. Spring MVC razi zasadu, ze obsluzne triedy maju byt jednoduche, najlepsie bezne triedy s anotaciou. (Anotacia je ekvivalentna marker interfacu bez metod).
* Mapovanie nazvu view na konkretnu implementaciu je riesene jednym riadkom v XML.
* Odkazovanie. "redirect:" je skratka pre pohodlnych. Pokojne mozete vratit specialny RedirectView, kde uvediete logicke meno viewu a model (presny ekvivalent "->redirect()"). Alternativne mozete view do kontrolera zadrotovat cez dependency injection a kontroler ani nebude tusit, ze nejaky redirect sa deje.
Tu by som podotkol, ze netreba mat totalnu paranoju z XML. Bezna springova aplikacia ma aj tak XML subor pre dependency injection, cize tam je to jedno (a pozor: DI je velmi dobry pattern, ktory vyvoj velkych aplikacii sprehladnuje).
////////////////////////////////////////////
Inak som velmi poteseny, ze vidim nejaky slubny MVC framework pre PHP. PHP si ho zasluzi.
Mam len otazku:
* ako sa riesia ine formaty view? (Vid vyssie)
* ako je mozne pouzivat vlastne formulare (Nette generuje kod formularov dynamicky, toto Spring MVC nema, view vrstvu nechava na vyber implementatora)
To hore som ja :-)
Este mi napadlo: ktore vlastnosti by vam chybali?
Mna primarne zaujalo to generovanie formularov spolu s JS validaciou. To vyplyva z generovania formularov cez PHP.
Co by mi chybělo? Ze zmíněných věcí asi ta oddělená URL vrstva a pohodlné generování formulářů, které stačí jednou popsat jazykem PHP a automaticky se vygeneruje HTML, JavaScript a provede validace na straně serveru.
Naopak uživatelům Springu by asi nejvíc chybělo propojení s modelem a databázi.
Ono se na to těžko odpovídá, protože Spring jsem viděl jen z rychlíku, přitom sílu frameworku člověk pozná, až když s ním pracuje.
Podobně to funguje i v Nette. Zítra by měl vyjít další díl seriálu,
který se týká právě renderování šablon, takže tam to bude popsáno
podrobně.
Anotace PHP prakticky nezná. Zkusil jsem s nimi přijít v Nette, ale
narazilo to na technické a „filosofické“ problémy, takže se držím
zpět ;)
Tady mi šlo spíš o ty URL, které Nette generuje dynamicky. Tedy ačkoliv
když vrátím RedirectView, tak se jich nezbavím.
(teda jestli to dobře chápu).
Ano, to je věc zvyku. Ve světě PHP jsou preferovanější INI soubory,
nejspíš proto, že samotné PHP se tímto způsobem konfiguruje.
Bezpochyby!
V podstatě lze volit v řadě úrovní od prostého manuálního
vykreslení a obsluhy formuláře až po plně automatické v režii objektu
Form (například automatická
obsluha + manuální vykreslení v šabloně). Asi největší vývoj
probíhá na straně automatického vykreslování, protože je to pohodlné
(čti: uživatelé to chtějí), ale zároveň by to mělo být co nejlépe
konfigurovatelné (čti: každý formulář vypadá úplně jinak).
Nechcem liat ohen do ohna v debate "PHP je pre lamy, Java rulez!", pretoze ta je uplne zbytocna.
Prezentacia (ktorej som zhodou okolnosti autorom) pokryva ,,highlights" zo stylu vyvoja Spring MVC, ktory je zalozeny na konvenciach a anotaciach. Paralelne s nou existuje ,,stara" moznost, kde su konvencie vymenene za XML konfiguraciu. Ale zakladna filozofia ostava taka ista.
V ktorych konkretnych bodoch vidite vyhody Nette oproti Spring MVC? (Prirodzene, su to dva rozlicne frameworky pre dva rozne programovacie jazyky.)
pane, prosim vas budte tak laskav a odeberte se zpet do maroka, ano?
Poctivo prechadzam serial a coraz viac dochadzam k nazoru, ze Nette mi nebude sediet ( co je obrovska skoda, veci ako Object alebo Ladenka su genialne). Vadi mi MVP paradigma, ktore Nette pouziva.
V tejto casti sa definoval novy view, ale bohuzial ked kukam na kod presentera, nedokazem z kodu „handle“ fnci urcit co sa bude renderovat. Vadi mi, ze v app je viac Views a napr. ked pozeram na kod handleInsert() nedokazem povadat co sa bude diat po zavolani $this->redirect(‚this‘) – co sa vykresli.
Ako uz hore niekto pisal, spojenie logiky P a V nie je good. A skutocne. Prechadzam aj ten diagram, ale nie som schopny urcit, kde a aky View sa pouzije.
V ZF je toto IMO omnoho logickejsie. Je jediny View. A na zaklade dispatchingu sa vykona nejaka akcia a k nej zdruzena sablona sa preda do toho jedineho View. Velmi podobne je to v Symfony.
Aha, pozeram, ze aktualny view je mozne odvodit zo sablony pomocou tych (ne)vykricnikov v odkazoch $presenter->link().
Hodilo by se, kdyby byl na konci zobrazen vždy nějaký ucelený kod nebo možnost stáhnout kompletní projekt. Někdy se ztrácím co je model,presenter a o jaký presenter se jedná.
asi to je otazka odveci a ani niesom niejako zvlast dobry v kodeni no ale stala sa mi taka veci zmenil som subor model v dokoncenom projekte teda ked som ho robil sam neslo to ladenka mi davala chybu :InvalidStateException
Cannot set HTTP code after HTTP headers have been sent (output started at /domains1/do2538000/public/www_root/CoffeeVendingMachine/app/models/Model.php:1).
akvsak zlvastne je natom to ze:1, prava su ake maju byt,
kodovanie suboru je utf8
zmena bola napr z const COFFEE_PRICE = 10; na const COFFEE_PRICE = 30; staci ze znamem 1 a dam 3 a uz je to tam ladenka to hodi a ak prekopirujem z povodneho rozbaleneho projektu subor model.php uz to ide a potom znovan napr len zmazem a s5 napisem 1 aby akoze prebehla zmena v subore a ladenka hodi tu chybu, prosim vas mozete mi niekto stym pomoct o co tu ide? budem fakt vdacny a ospravedlnujem sa ak to nieje vhodne vzhladom na temu alebo je to hlupa utazka.