Internet Info, s.r.o. Lupa Root Měšec Podnikatel DigiZone Slunečnice Vitalianew Bomba Navrcholu Weblogy Jagg Woko Dobrý web Computer.cz SK: MojeLinky

Hlavní navigace

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é.

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 Nette\Application\Presenter;

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.

Anketa

Používáte MVC?

       

David Grudl

David Grudl

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

Přehled názorů

more
blizz.boz 24. 3. 2009 03:00
Problem MVC
Abraxis 24. 3. 2009 04:54
├ 
Re: Problem MVC
anonymní uživatel 24. 3. 2009 07:39
│
└ 
Re: Problem MVC
Michal Krause 24. 3. 2009 09:34
│
 
└ 
Re: Problem MVC
Milan Čermák 24. 3. 2009 11:35
│
 
 
└ 
Re: Problem MVC
Luboš Horáček 24. 3. 2009 16:05
│
 
 
 
└ 
Re: Problem MVC
Milan Čermák 25. 3. 2009 08:58
│
 
 
 
 
└ 
Re: Problem MVC
Aleš Roubíček 25. 3. 2009 10:02
│
 
 
 
 
 
└ 
Re: Problem MVC
Milan Čermák 25. 3. 2009 10:22
│
 
 
 
 
 
 
└ 
Re: Problem MVC
Borek Bernard 25. 3. 2009 10:53
├ 
Re: Problem MVC
Jan Havrda 24. 3. 2009 09:40
└ 
Re: Problem MVC
Karell 24. 3. 2009 11:37
Rozdeleni modelu a prezenteru
ava 24. 3. 2009 10:18
├ 
Re: Rozdeleni modelu a prezenteru
anonymní uživatel 24. 3. 2009 10:58
└ 
Re: Rozdeleni modelu a prezenteru
David Grudl 24. 3. 2009 13:48
 
└ 
Re: Rozdeleni modelu a prezenteru
ava 24. 3. 2009 15:48
Volání funkce revoluční jako znovuobjevené?
Almad 24. 3. 2009 11:23
revolucni myslenka?
michal_ 24. 3. 2009 12:17
├ 
Re: revolucni myslenka?
petr 24. 3. 2009 13:02
└ 
Re: revolucni myslenka?
David Grudl 24. 3. 2009 13:11
 
├ 
Re: revolucni myslenka?
anonymní uživatel 24. 3. 2009 21:01
 
│
└ 
Re: revolucni myslenka?
anonymní uživatel 24. 3. 2009 21:55
 
│
 
└ 
Re: revolucni myslenka?
Balls McLongcock 25. 3. 2009 02:47
 
└ 
Re: revolucni myslenka?
TanisCZ 24. 3. 2009 21:55
Textové popisky v controlleru?
v6ak 24. 3. 2009 14:38
Význam URL
Jakub Vrána 24. 3. 2009 14:53
├ 
Re: Význam URL
David Grudl 24. 3. 2009 14:57
└ 
Re: Význam URL
ava 24. 3. 2009 15:41
 
├ 
Re: Význam URL
Jakub Vrána 24. 3. 2009 15:50
 
│
└ 
Re: Význam URL
ava 24. 3. 2009 16:52
 
└ 
Re: Význam URL
Aleš Roubíček 25. 3. 2009 10:11
RE: Nette Framework: MVC & MVP
pubso 24. 3. 2009 15:53
Trygve Reenskaug
MacHala 24. 3. 2009 20:36
RE: Nette Framework: MVC & MVP
Balls McLongcock 24. 3. 2009 21:19
├ 
RE: Nette Framework: MVC & MVP
Borek Bernard 24. 3. 2009 23:18
│
└ 
RE: Nette Framework: MVC & MVP
Balls McLongcock 25. 3. 2009 02:59
│
 
├ 
RE: Nette Framework: MVC & MVP
Aleš Roubíček 25. 3. 2009 10:14
│
 
└ 
RE: Nette Framework: MVC & MVP
Borek Bernard 25. 3. 2009 10:43
├ 
RE: Nette Framework: MVC & MVP
none_ 24. 3. 2009 23:43
└ 
RE: Nette Framework: MVC & MVP
Milan Čermák 25. 3. 2009 09:13
 
├ 
RE: Nette Framework: MVC & MVP
Aleš Roubíček 25. 3. 2009 10:17
 
│
└ 
RE: Nette Framework: MVC & MVP
Milan Čermák 25. 3. 2009 10:23
 
├ 
RE: Nette Framework: MVC & MVP
Borek Bernard 25. 3. 2009 10:39
 
└ 
RE: Nette Framework: MVC & MVP
Balls McLongcock 25. 3. 2009 13:23
trygve
komorniA 29. 3. 2009 00:05
└ 
Re: trygve
Ksl 7. 5. 2009 00:27
       
Zasílat nově přidané příspěvky e-mailem

Zasílání upozornění na nové příspěvky je dostupné jen registrovaným uživatelům. Proto budete před aktivací zasílání názorů přesměrováni na přihlašovací stránku, ze které se můžete případně také zaregistrovat.