Architektura MVC
MVC je návrhový vzor pro řešení rozdělení kódu podle jeho funkce. Jak se tento vzor používá, co jednotlivé části znamenají, jak může v praxi vypadat a proč by ses tím vůbec měl zabývat? To se dozvíš v tomto článku.
Návrhový vzor MVC
MVC je návrhový vzor - pomáhá navrhnout a udržet strukturu kódu pro projekt, a tím zvýšit jeho přehlednost, ulehčit jeho psaní a umožnit snadnější udržování i rozšiřování kódu.
MVC vychází z teorie, že části kódu, které vykonávají různé úkoly, by měly být od sebe oddělené. Například výpočty průměrů a hledání v databázi by nemělo být na jedné kupě s vykreslováním meníčka nebo zkoumáním, jaký jazyk si uživatel přeje zobrazit. Toto lze dokázat i bez návrhového vzoru - bez něj je ale udržení kódu ve slušně kvalitě většinou pracnější a stejně to svádí k jednoduchým zkratkám v kódu, které ho značně znepřehledňují.
Pokud se tato separace logických celků nedodržuje nebo projekt nepoužívá žádný podobný návrhový vzor, velmi často tento přístup vede k takzvanému špagetovému kódu. Ve špagetovém kódu jsou jednotlivé části projektu propleteny do sebe tak důkladně, že je většinou jednodušší napsat celý projekt znovu, než ho zkoušet rozmotávat. Vykreslování je smícháno s přístupy do databáze, routování s výpočty velikostí a když chce kodér pozměnit malou věc, většinou musí přepsat kód na mnoha různých místech. Udržovat špagetový projekt je nákladné a rozšiřovat prakticky nemožné. Příklady použití sem ani nebudu dávat (:
Jednotlivé komponenty MVC
MVC schéma předpokládá, že existují tři hlavní funkce kódu - datová, vykreslovací a řídící. A to odráží jednotlivé součásti MVC - modely (Models), pohledy (Views) a kontrolery (Controllers). Co má každá součást na starosti?
Pohled (View)
Pohled zajišťuje vykreslení webu v HTML a JavaScriptu, aby se uživateli vše zobrazilo pěkně uhlazené a se správnými fonty. Důležité je, že do pohledu už přicházejí všechna data, co pohled může potřebovat. Nemůže je tedy sám někde shánět - to by bylo porušení MVC principu oddělení rolí. Kde se ta data vezmou, o tom se dočteš v dalších částech.
Na vykreslování se zpravidla používá vykreslovací engine (templating engine). Oblíbené jsou napříkad Latte, Moustache, Twig nebo Smarty. Jednoduchý vykreslovací engine obsahuje i samotný jazyk PHP (přesnější by bylo říci “PHP samotné je v podstatě vykreslovací engine”).
Template engine má na starosti vykreslení stránky uživateli - neměl by obsahovat žádnou složitější logiku, nejvýše jednoduché if nebo for/foreach cykly. Často se také stará o formátování dat nebo automatické escapování výsledků, což velmi napomáhá zabezpečení. Proto se vykreslovací engine používají i v jiných schématech než MVC.
Jak vypadá kód pohledu v čistém PHP? Třeba takto by mohl vypadat pohled se jménem uživatele v nadpisu a tabulka s výpisem jeho objednávek:
<h2>
<!-- Jméno uživatele -->
<?=$user['first_name']?> <?=$user['last_name']?>
</h2>
<table>
<!-- Nadpisy sloupců tabulky -->
<th>
<td>Pořadové číslo</td>
<td>Datum</td>
<td>Cena</td>
<td>Status</td>
</th>
<!-- Údaje všech objednávek uživatele -->
<?php foreach ($user['orders'] as $order): ?>
<tr>
<td><?=$order['id']?></td>
<td><?=date("j/n/y", strtotime($order['date']))?></td>
<td><?=$order['price']?>,- Kč</td>
<td><?=$order['status']?></td>
</tr>
<?php endforeach ?>
</table>
Jak je vidět, pohled se skládá hlavně z HTML kódu. PHP je tam minimum a je přehledně vidět, co jaká část dělá. Kód se velmi podobá výslednému HTML v prohlížeči, což je dobře - při změnách můžeš rovnou psát HTML a nemusíš řešit, kde uzavřít jakou závorku či uvozovky a co se vypisuje a co už ne.
Všimni si, že je potřeba mít předem připravená data v array $user
. Pohled neřeší, kde se tyto údaje vezmou, prostě s nimi počítá a použije je. Pro vykreslovací šablony v čistém PHP se pro soubory často používá přípona .phtml
, používá se i .php
či .html
(na to je ale potřeba přizpůsobit webserver).
Pro srovnání porovnej funkčně stejný kód napsaný ve vykreslovacím enginu Latte:
<h2>
<!-- Jméno uživatele -->
{$user['first_name']} {$user['last_name']}
</h2>
<table>
<!-- Nadpisy sloupců tabulky -->
<tr>
<td>Pořadové číslo</td>
<td>Datum</td>
<td>Cena</td>
<td>Status</td>
</tr>
<!-- Údaje všech objednávek uživatele -->
{foreach ($user['orders'] as $order)}
<tr>
<td>{$order['id']}</td>
<td>{$order['date']|date:'%j/%m/%y'}</td>
<td>{$order['price']},- Kč</td>
<td>{$order['status']}</td>
</tr>
{/foreach}
</table>
Kód pohledu je velmi podobný, opět se používá hlavně HTML. Změnila se jen gramatika vkládání proměnných a cyklu.
U řádku se zobrazením data si můžeš všimnout Latte filtru, který se stará o správné zobrazení formátu - tímto přístupem máš formátování řešené až při vykreslování (kam logicky patří) a uvnitř projektu můžeš použít formát, který nejvíce vyhovuje projektu.
Model (Models)
Model stojí na opačné straně než pohled - stará se o byznysovou logiku (Business Logic). Ta má na starosti získávání dat, jejich manipulaci, výpočty, výsledky, určení, zda je něco pravda či ne a další věci. Toto bývá základem firmy, na které stojí její obchodní strategie, proto se nazývá byznysová.
Pro příklad si můžeš představit banku, kde její byznys logika spočívá v získání dat o klientovi a spočtení jeho kreditibilty (jestli mu můžou půjčit peníze či ne). Jiný příklad je třeba stavební firma, kde model příjmá velikost domu a počet pater, oken a dveří a on vrátí odhadnoutou cenu (takto nejspíš odhad ceny domu neprobíhá, ale což :).
Modely se ze všech tří komponent nejvíce blíží standardním funkcím či metodám. Měly by být uspořádány do tříd a používají se jejich metody pro manipulaci s daty.
Opět pro příklad zkoukni (velmi naivní) návrh modelu pro odhad materiálu naznačeného výše:
<?php
class HouseBuild {
public function GetHousePrice ($floorCount, $windowsCount, $doorsCount) {
$price = INITIAL_HOUSE_PRICE;
$price *= $floorCount;
$price += $windowsCount * WINDOW_PRICE;
$price += $doorsCount * DOOR_PRICE;
return $price;
}
}
V kódu chybí důležíté součásti (validace, testy…). Je zde pro představu, jak by mohl kousek modelu vypadat.
Model opět neřeší, kdo vznesl požadavek na data, ani jak se dál použijí. Jeho úkol je pouze provést zadanou operaci.
Kontrolery/Presentery (Controllers/Presenters)
Máme pohled, máme data, zbývá to nějak spojit dohromady. A přesně k tomu slouží kontroler (controller), někdy nazývaný prezenter (okolo terminologie jsou dlouhé články a diskuze přesahující rozsah tohoto článku).
Kontrolery fungují jako manažeři. Jsou v nich popsané procesy, co vše se musí stát pro výsledek. Pro představu prostuduj následující příklad kontroleru, který zajišťuje poslání mailu z vyplněného formuláře:
<?php
class ContactController {
public function process($parameters) {
//vytvoříme si potřebný model
$contact = new ContactModel();
//pomocí modelu ošetříme vstupní data z formuláře, pokud nějaká máme
if (isset($_POST['send'])) {
$data = $contact->sanitize([
'email' => $_POST["email"],
'message' => $_POST["message"]
]);
$result = $contact->sendEmailToAdmin($data['email'], $data['message'], $this->language);
}
//nastavíme a vykreslíme požadovaný pohled
$view = 'contactResult';
$this->messages->addResult($result);
$this->templateEngine->render ($view, $data);
}
}
Co tento kontroler reálně dělá? Zde nejdříve vytvoří model, který bude dále potřebovat. Potom vezme data z formuláře a pomocí modelu je ošetří. Poté opět pomocí modelu odešle email s ošetřenými daty administrátorovi. A nakonec vykreslí správný pohled i se zprávou, jak celá akce dopadla.
Kontrolery mívají jednu hlavní metodu, která se provádí vždy, když je kontrolerovi svěřen daný požadavek. Kontroler může vykreslit a poslat data uživateli sám, jako v příkladu zde, nebo může výsledek odevzdat hlavnímu kontroleru, který už poslání výsledného HTML zajistí sám.
Pořadí vykonávání požadavku od uživatele (life cyklus)
Jak tedy v praxi vypadá životní cyklus MVC aplikace, tedy v jakém pořad se jednotlivé komponenty za sebou řadí? V nejjednoduším případě může vypadat pořadí takto:
- požadavek od uživatele (request) zachytí hlavní kontroler,
- zjistí, čeho se požadavek týká a předá ho patřičnému konkrétnímu kontroleru,
- konkrétní kontroler pomocí modelů získá všechna data, co bude potřebovat,
- tato data kontroler předá správnému pohledu,
- pohled vykreslí finální HTML pro uživatele a výsledek pošle uživateli.
Na první pohled to stále vypadá komplikovaně. Stačí si ale uvědomit že vše řídí kontroler - ten si pomocí modelu umí získat data a pomocí pohledu je správně vykreslí. Toto ještě většinou obaluje kód, který řeší další věci okolo projektu jako hledání patřičného konkrétního kontroleru nebo načítání nastavení - zde mu říkám “hlavní kontroler”.
Pokud by pořadí vykonávání kódu stále nebylo jasné, neboj se ozvat v komentářích - případné nepřesnosti v textu rád dovysvětlím.
Problémy a nedostatky
Největší slabinou MVC přístupu jsou mechanizmy, které nejdou přesně zaškatulkovat do jedné ze tří součástí. Týká se to zejména routování nebo autentizace s autorizaci. Tyto problémy se ale dají řešit - například router je speciální typ kontroleru a pro práci s uživateli lze použít model.
Začátečníci mají tendenci přetěžovat kontrolery a snaží se je využít pro víc úkolů, než jen řízení. Následkem toho ztrácí kontroler svojí hlavní funkci a celý návrhový vzor se rozkládá.
Osobně můžu více než doporučit MVC pro středně velké projekty, které jsou postavené na formulářích a zobrazování dat. MVC v tomto případě hodně ulehčí práci s rozmýšlením struktury projektu. Části se navíc stanou snadno zaměnitelné za jiné/novější a není problém postupně přejít i k jiným návrhovým vzorum typu separation of concerns nebo přepsat projekt do nějakého frameworku. Velké frameworky obecně MVC zgustu používají, takže i tam se s MVC setkáš.
Další zdroje a odkazy
České vysvětlení MVC najdeš třeba na itnetwork.cz. Kvalitní úvod anglicky najdeš na blogu Toma Dallinga, krátký anglický výtah najdeš na tutorialspointu a samozřejmě existuje i hezká odpověď na StackOverflow).
Too Long, Didn’t Read?
MVC vychází z potřeby separovat části kódu, které mají různé úkoly. Návrh je dělí do tří komponent - modelu, pohledu a kontroleru. Model se stará o výpočty a data, pohled o vykreslení a kontroler o řízení celého procesu. MVC přístup má svoje výhody i nevýhody - jako každý návrhový vzor je třeba zvážit vhodnost jeho použití pro každou situaci - obecně je vhodný pro formulářové aplikace.