Nette Framework: MVC & MVP

Když Trygve Reenskaug v roce 1979 popsal architekturu Model–View–Controller (MVC), zapsal se do dějin programování a jeho jméno by měl znát každý vývojář na celém světě. To by mu ovšem rodiče museli dát nějaké lépe zapamatovatelné.

Seriál: Začínáme s Nette Framework (17 dílů)

  1. Nette Framework: zvyšte svoji produktivitu 10.3.2009
  2. Nette Framework: Odvšivujeme 17.3.2009
  3. Nette Framework: MVC & MVP 24.3.2009
  4. Nette Framework: Refactoring 31.3.2009
  5. Nette Framework: Chytré šablony 7.4.2009
  6. Nette Framework: adresářová struktura aplikace 14.4.2009
  7. Nette Framework: AJAX 21.4.2009
  8. Nette Framework: AJAX (pokračování) 28.4.2009
  9. Nette Framework: AJAX (dokončení) 5.5.2009
  10. Nette Framework: Sessions 12.5.2009
  11. Nette Framework: Přihlašování uživatelů 19.5.2009
  12. Nette Framework: Ověřování oprávnění a role 26.5.2009
  13. Nette Framework: Neprůstřelné formuláře 2.6.2009
  14. Nette Framework: Neprůstřelné formuláře II 9.6.2009
  15. Nette Framework: Neprůstřelné formuláře III 16.6.2009
  16. Nette Framework: Cache 23.6.2009
  17. Nette Framework: Co se do seriálu nevešlo? 30.6.2009

Letos slaví MVC kulaté výročí a zdá se, že je populárnější než kdykoliv předtím. V čem je jeho síla? A hlavně – proč jej každý chápe trošku jinak? Pro odpověď se musíme vrátit do sedmdesátých let minulého století, do doby příchodu aplikací s interaktivním uživatelským rozhraním. Byla otázka, jak takové aplikace vůbec navrhovat. Nejjednodušší cestou bylo spojit uživatelské rozhraní s logikou aplikace v jednolitý celek. Tento přístup se dodnes často používá v Delphi, ASP.NET (Web Forms) nebo PRADO.

Disclaimer: Autor článku je hlavním vývojářem frameworku Nette.

Rychle se ale ukázalo, že přímočarost tohoto řešení si vybere daň v okamžiku, kdy je potřeba část aplikace změnit, nebo když aplikace získá na složitosti. Kvůli přílišné provázanosti jsou zásahy do kódu nejen komplikované, ale ani nelze dost dobře testovat. Řešením je oddělit kód uživatelského rozhraní od kódu aplikační logiky, označovaného jako model. Samotné rozhraní lze přitom ještě rozdělit na vrstvu, která obstarává výstup na monitor (view) a vrstvu zpracovávající vstupy z klávesnice nebo myši (controller). Poznámka: v sedmdesátých letech místo monitoru, klávesnice a myši používali tuším elektronku, děrovačku a dřevěnou pramyš.

Architektura Model–View–Controller

Architektura Model–View–Controller

Model o existenci pohledu (tj. view) nebo kontroleru neví. Pohled a kontroler jsou dvojčata, děvče a chlapec. Děvče prezentuje data modelu, jak nejlépe umí, a když uživatel zareaguje, úlohu převezme chlapec a zpracovanou reakci doručí modelu. Alespoň takto se chová historicky první implementace MVC v jazyce Smalltalk-80.

Všechno je jinak

Mám pro vás překvapení: originální návrh MVC z roku 1979 (v PDF, na straně tři je dokonce skvrna od Trygveovy prakávy). Na celé věci je pikantní, že spíše než o MVC jde o MVCE, kde „E“ znamená Editor. K čemu slouží tajemný editor? Kupodivu jde o totéž, co se ve zmíněné první implementaci nazývá Controller a je dokonce společně s View reprezentován jedinou třídou. A pozor: v původním návrhu spojení mezi modelem a controller/editorem obstarává view.

Proč vás tu drtím rádobyzajímavostmi z let, kdy vaši rodiče nacvičovali spartakiádu? 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! Jinak budete mít více otázek než odpovědí.

Vhodnější je MVC vnímat obecněji, jako princip mající za cíl přemostit mezeru mezi lidským mentálním modelem a modelem počítačovým, jehož ideálu zatím nebylo dosaženo. Proto se také mohou různé MVC implementace podstatně lišit. Mimochodem, Trygve Reenskaug oznámil, že problematika MVC má daleko více aspektů, než si v roce 1979 uvědomoval, a začal před pár lety pracovat na novém návrhu.

Architektura Nette Frameworku

Nette Framework prošel dlouhým vývojem, na jehož počátku byla (dnes si troufám říci) revoluční myšlenka: nechť je odkaz totéž, co zavolání funkce.

Co je pro programátora přirozenějšího, než zavolat funkci či metodu a předat jí argumenty? Cílem bylo oprostit se od uvažování v rovině URL, kde se nejprve musí nějak poskládat hypertextový odkaz, aby se poté mohl pomocí pravidel mod_rewrite ještě přeskupit a nakonec byl v cílové stránce analyzován. Zároveň jde o protipól přístupu ASP.NET, které umí na odkazy zavěsit handler.

Princip si nejlépe ukážeme na příkladu. Nevím jak vy, ale já bych si dal dobrou kávu, tak co si naprogramovat aplikaci „Automat na kávu“?

Uvnitř automatu je zásobník na suroviny a mince, tedy databáze. Zásobník je součástí modelu, jehož API bude tvořit jediná metoda buyCoffee($money) a konstanta COFFEE_PRICE určující cenu nápoje. Všimněte si, že model splňuje požadavek nezávislosti na uživatelském rozhraní a bude i snadno testovatelný. Vzhled aplikace lze měnit bez obav, že bychom museli zasáhnout do datové části nebo aplikační logiky.

class Model extends Object
{
        const COFFEE_PRICE = 10;

        public function buyCoffee($money)
        {
                if ($money < self::COFFEE_PRICE) {
                        return FALSE;
                }

                ... // uschovej peníze, vyrob kávu
                return TRUE;
        }

} 

Přístupme k uživatelské obsluze automatu (angl. vending machine). Tedy třídě, která bude přímo komunikovat s modelem a prezentovat jej v lidsky přívětivé formě. Odsud název presenter. API této třídy bude nabízet metodu pro přijetí vhozených mincí a pro vydání kávy. Zároveň bude počítat, kolik peněz již bylo vhozeno, a komunikovat s uživatelem pomocí displeje (prezentační logika).

Následující kód je nástin, jak by presenter mohl vypadat. Za chvíli jej ještě mírně upravíme.

// use NetteApplicationPresenter;

class MachinePresenter extends Presenter
{
        /**
         * Hodnota vhozených mincí.
         */
        public $money = 0;


        public function __construct()
        {
                // na displeji, který je součástí šablony, zobrazíme výzvu k vhození peněz
                $this->template->display = 'Vhoď ' . Model::COFFEE_PRICE . ' Kč';
        }


        public function insert($coin)
        {
                // zvýšíme hodnotu vhozených mincí
                $this->money += max(0, (int) $coin);

                // na displeji zobrazíme celkovou částku
                $this->template->display = $this->money . ' Kč';
        }


        public function buy()
        {
                // příkaz pro model
                $model = new Model;
                $result = $model->buyCoffee($this->money);

                if ($result) {
                        $this->money = 0; // vynulujeme částku (automat nevrací)
                        $this->template->display = 'Dobrou chuť';
                        $this->giveCoffee(); // vydáme uživateli kávu
                } else {
                        $this->template->display = 'Málo peněz';
                }
        }

}


// příklad použití
$presenter = new MachinePresenter;
$presenter->insert(5); // vložíme 5 Kč
$presenter->insert(2); // vložíme 2 Kč
$presenter->insert(2); // vložíme ještě 2 Kč
$presenter->buy(); // necháme si vydat kávu 

Zbývá nám navrhnout vizuální podobu uživatelského rozhraní. To může vypadat jako skutečný automat na kávu, vedle něhož vyskládáme obrázky mincí, které se kliknutím vhodí dovnitř. Na automat umístíme displej a tlačítko pro vydání kávy. Kromě grafiky připravíme HTML šablonu:

<body>
        <div id="machine"><!-- automat -->
                <p id="display"><?php echo htmlSpecialChars($display) ?></p>

                <a href="..."><img src="images/button.png" alt="Kup kávu" /></a>
        </div>

        <ul id="coins"><!-- mince -->
                <li><a href="..."><img src="images/coin-1.png" alt="Vhoď 1 Kč" /></a></li>
                <li><a href="..."><img src="images/coin-2.png" alt="Vhoď 2 Kč" /></a></li>
                <li><a href="..."><img src="images/coin-5.png" alt="Vhoď 5 Kč" /></a></li>
                <li><a href="..."><img src="images/coin-10.png" alt="Vhoď 10 Kč" /></a></li>
        </ul>
</body> 

Všechny odkazy zatím míří do prázdna. Jak už víte, Nette Framework chápe odkaz jako zavolání funkce. U jednotlivých odkazů tedy rovnou napíšeme, kterou z výše uvedených metod mají zavolat. Obrázek pětikoruny zavolá insert(5). Nemůže to být jednodušší. Nemuže to být přímočařejší!

Je pochopitelné, že nelze do šablony přímo psát, např.:

<a href="<?php $presenter->insert(5) ?>"><img src="images/coin-5.png" alt="Vhoď 5 Kč" /> 

protože PHP by příkaz vykonalo již při vykreslování šablony. My ho chceme vykonat až poté, co na něj uživatel klikne. Zápis proto jen mírně modifikujeme:

<a href="<?php echo $presenter->link('insert!', 5) ?>"><img src="images/coin-5.png" alt="Vhoď 5 Kč" /> 

a to je vše! Jakmile uživatel klikne na obrázek mince, zavolá se $presenter->insert(5). Nic víc nemusíme řešit!

Z bezpečnostního hlediska ale není dobré, aby bylo takto možné zavolat jakoukoliv metodu třídy MachinePresenter. Volatelné (sic) metody je potřeba nějakým způsobem označit. V úvahu připadá několik způsobů, z nichž nejschůdnější je asi prefixování, tedy přilepení klíčového slova před název metody. Pro naši situaci Nette používá prefix handle, takže metody insert() a buy() přejmenujeme na handleInsert()handleBuy().

Ještě je třeba zajistit, aby obsah proměnné $money byl trvalý, aby se nevynulovala při každém HTTP požadavku. K tomu slouží tzv. anotace  @persistent:

        /**
         * Hodnota vhozených mincí.
         * @persistent - proměnná se bude přenášet mezi HTTP požadavky
         */
        public $money = 0; 

A to je vše. Kompletní aplikaci si stáhněte, rozbalte a v prohlížeči otevřete soubor index.php, který se nachází v adresáři document_root. A dejte si kávu…

Nabízíme dobrou brazilskou kávu.

Nabízíme dobrou brazilskou kávu.

Presenter v Nette Frameworku má podobnou roli jako kontroler v MVC. Vybírá pohled (view) a předává mu model, nebo data z modelu. Udržuje stav persistentních proměnných. A především zpracovává reakce uživatele. Ty se dají rozdělit na tři typy:

  • změna pohledu (s tím jsme se v jednoduchém Automatu na kávu nesetkali)
  • změna stavu (po vhození mince)
  • příkaz modelu (po stisknutí tlačítka)
Architektura Model–View–Presenter v Nette Framework

Architektura Model–View–Presenter v Nette Framework

A co URL?

Ačkoliv Nette Framework nás zcela odstínil od úvah nad URL, neznamená to, že nemůžete jejich podobu ovlivnit. Ba právě naopak! Framework nám dává nad URL absolutní kontrolu. Jen prostě v tuto chvíli by úvahy nad jejich tvarem odváděly pozornost od skutečného úkolu. Podobu URL můžeme rozhodnout později. A bez nutnosti cokoliv měnit v samotné aplikaci.

Vychutnejte si voňavou kávu z Nette automatu, v příštím díle se k němu vrátíme a naučíme ho vařit AJAXově.


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.

Používáte MVC?

David Grudl školí, je autorem PHP knihoven Nette Framework, databázové vrstvy dibi a formátovače HTML kódu Texy!.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 49

Přehled komentářů

blizz.boz more
Abraxis Problem MVC
Anonym Re: Problem MVC
mike Re: Problem MVC
Milan Čermák Re: Problem MVC
Luboš Horáček Re: Problem MVC
Milan Čermák Re: Problem MVC
Aleš Roubíček Re: Problem MVC
Milan Čermák Re: Problem MVC
Borek Bernard Re: Problem MVC
Jan Havrda Re: Problem MVC
Karell Re: Problem MVC
ava Rozdeleni modelu a prezenteru
Anonym Re: Rozdeleni modelu a prezenteru
David Grudl Re: Rozdeleni modelu a prezenteru
ava Re: Rozdeleni modelu a prezenteru
Almad Volání funkce revoluční jako znovuobjevené?
michal_ revolucni myslenka?
petr Re: revolucni myslenka?
David Grudl Re: revolucni myslenka?
Anonym Re: revolucni myslenka?
Anonym Re: revolucni myslenka?
Balls McLongcock Re: revolucni myslenka?
TanisCZ Re: revolucni myslenka?
v6ak Textové popisky v controlleru?
Jakub Vrána Význam URL
David Grudl Re: Význam URL
ava Re: Význam URL
Jakub Vrána Re: Význam URL
ava Re: Význam URL
Aleš Roubíček Re: Význam URL
pubso RE: Nette Framework: MVC & MVP
MacHala Trygve Reenskaug
Balls McLongcock RE: Nette Framework: MVC & MVP
Borek Bernard RE: Nette Framework: MVC & MVP
Balls McLongcock RE: Nette Framework: MVC & MVP
Aleš Roubíček RE: Nette Framework: MVC & MVP
Borek Bernard RE: Nette Framework: MVC & MVP
none_ RE: Nette Framework: MVC & MVP
Milan Čermák RE: Nette Framework: MVC & MVP
Aleš Roubíček RE: Nette Framework: MVC & MVP
Milan Čermák RE: Nette Framework: MVC & MVP
Borek Bernard RE: Nette Framework: MVC & MVP
Balls McLongcock RE: Nette Framework: MVC & MVP
komorniA trygve
Ksl Re: trygve
Encode Kde se vzala struktura?
Lucka htmlSpecialChars?
Sopta Re: htmlSpecialChars?
Zdroj: https://www.zdrojak.cz/?p=2968