Přeskočit na hlavní obsah

Model není pouze databáze

Jedním z nejrozšířenějších omylů mezi pokročilejšími PHP programátory je představa, že model v MVC je vlastně jenom databáze plus nějaké ORM, které v ní umožňuje číst, zapisovat a mazat. To je ale velké nepochopení. Model není jen datový, ale zejména funkční základ celé aplikace.

Modelujeme aplikaci

Mnoho PHP programátorů začíná v poslední době používat některý z MVC frameworků prodělávajících nyní velký rozmach. Přitom jim ale chybí jakýkoliv teoretický základ ohledně MVC architektury. Představu o tom, co vlastně MVC je, si vytvářejí teprve zpětně podle zkušeností se svým frameworkem, povětšinou špatně či neúplně. Chybí jim totiž rozhled za hranice daného frameworku, neřkuli za hranice celého PHP.

Jedním z nejrozšířenějších omylů je představa, že model v MVC je vlastně jenom databáze plus nějaké ORM, které v ní umožňuje číst, zapisovat a mazat. To je ale velké nepochopení. Model není jen datový, ale zejména funkční základ celé aplikace. V modelu je schovaná celá aplikační business logika.

Trocha suché teorie

Model si lze představit jako stavový prostor, jako množinu různých stavů, do kterých se aplikace může dostat. Jakákoliv událost, jakákoliv akce uživatele (přihlášení, odhlášení, vložení zboží do košíku, změna hodnoty v databázi) představuje jen přechod modelu z jednoho stavu do jiného.

Model je černá skříňka, která si uvnitř udržuje aktuální stav a ven nabízí pevně dané rozhraní. Voláním funkcí tohoto rozhraní můžeme zjišťovat či měnit vnitřní stav modelu. Přitom k rozhraní může přistupovat kdokoliv a kdykoliv.

Důležitým požadavkem je, že model musí být ve všech svých stavech konzistentní. Žádná událost jej nesmí dostat do nepovoleného stavu. Jelikož může jeho rozhraní volat kdokoliv a kdykoliv, nelze se spoléhat na jakékoliv vnější předpoklady. Za svou vnitřní konzistenci je model odpovědný sám.

Model jako celek proto musí být důsledně zapouzdřený, poskytovat ven jen své pevně dané rozhraní a nesmí dopustit, aby jakékoliv vnější volání narušilo jeho vnitřní integritu.

Vyměňte view a controller!

Pro lepší představu, co všechno patří do modelu, co by měl model zajišťovat a jak by se měl chovat, pomáhá představit si, že ponecháte stále stejný model, ale kompletně vyměníte controller a view, například:

  • místo přijímání dotazů od prohlížeče a odesílání webových stránek budete přijímat a odesílat nějakou SOAP komunikaci,
  • budete zpracovávat dávkový soubor, kde budou jednotlivé události prováděné nad aplikací všechny pěkně za sebou,
  • k modelu bude přistupovat nějaký vzdálený standalone klient,
  • někdo bude jednotlivé funkce rozhraní volat napřímo ručně přes nějaký terminál.

Poslední dva body se sice týkají spíš jiných prostředí, než přímo PHP, ale teď jde úlohu modelu v MVC obecně.

Ve všech těchto případech musí model zajišťovat plnou funkčnost aplikace. Zároveň ho žádné volání nesmí rozhodit, i kdyby třeba v posledním příkladu psal uživatel do terminálu úplné blbostě.

Co patří a co nepatří do modelu

Co tedy u webových aplikací všechno zahrnout do modelu? Co je všechno součástí aktuálního stavu aplikace? Nelze samozřejmě sestavit úplný seznam, tak spíš jenom pár typických příkladů:

  • Aplikační data – jejich čtení, zápis, aktualizace, mazání, zpracování, ověřování apod.
  • Aktuální uživatel – zda je přihlášený, kdo je přihlášený, ke kterým operacím má či nemá oprávnění apod.
  • Obsah košíku – jaké zboží má daný uživatel přihozené do košíku v rámci otevřené relace.

Naopak do modelu rozhodně nepatří věci, které jsou závislé na konkrétním controlleru a view. Například u aktuálního uživatele je to kontrola jeho SID, User-Agenta či IP adresy. Ty jsou dané konkrétním typem přístupu k modelu, v jiném případě je třeba vůbec mít nemusím.

Validace vstupních dat

Vezměme si jako jednoduchý příklad nějakou položku, která může mít maximálně 30 znaků. To je základní vlastnost dané položky, i samotné databázové pole bude nejspíš definováno jako VARCHAR(30).

Volitelně mohu samozřejmě kontrolu provádět například v prohlížeči JavaScriptem nebo na straně serveru v controlleru. Ale jenom jako redundantní kontrolu, například pro zvýšení komfortu. Když ale vyměním svůj controller a view za nějaké jiné, tak v těch už najednou žádná kontrola na 30 znaků třeba vůbec nebude.

Ovšem i v takovém případě musí být aplikace odolná proti situaci, že do ní někdo pošle znaků padesát. Hlavní místo, kde se musí omezení kontrolovat vždy a bez výjimky, je proto právě model.

Nejde jen o databázi

Jak jsem již zmínil, model vůbec není jenom pro práci s jednotlivými databázovými tabulkami a záznamy. Rozhraní modelu by naopak v ideálním případě mělo zbytek aplikace odstínit od konkrétní databázové implementace, od konkrétních databázových tabulek a atributů.

Pokud tedy budu chtít založit nového uživatele, tak databázovou implementaci odkrývající přístup může vypadat takto:

$user = new User;
$user->setUsername('pepa');
$user->setPassword('xxx');
$user->store();

Zatímco model, který odstíní zbytek aplikace od implementačních detailů, bude vypadat třeba takto:

$user = User::create('pepa', 'xxx');

Extrémní sporty v přírodě

V článku jsem se snažil popsat čisté systémové pojetí modelu tak, jak je zamýšlen v MVC architektuře. Zmiňovaný přístup pak doceníte u některých pokročilých aplikací, které drtivou většinu své business logiky přesouvají přímo do databáze.

Interface takového modelu je nabízen typicky v podobě úložných procedur. K modelu pak lze přistupovat skutečně odkudkoliv, kde je konektor do dané databáze, od PHP kódu přes aplikace v C++ až po terminálový klient. To už je ale na jiné povídání.

Komentáře

  1. Ačkoliv jsem se sám k teoretickým základům dostal zpětně od podle zkušeností, školy nemaje, tak naprosto souhlasím ;-)

  2. (nechtěl jsem se pouštět do delší reakce, protože mě bolí levý ukazováček, ale čert ho ven. ehm vem.)

    S článkem naprosto souhlasím. Někdy v praxi, když je model triviální, tak může pragmatické hledisko převážit a dovolím si jej ošidit. Místo důsledně zapouzdřeného rozhraní prostě zavolám přes dibi SQL příkaz přímo v kontroleru.

    Spousta programátorů tohle jako porušení také vnímá, což je v pořádku.

    Problém nastává v okamžiku, kdy místo SQL příkazu použiji jiný jazyk. Například jazyk PHP a Zend_Db_Table. Syntaxe se sice liší, ale rozdíl je kosmetický, stále jde o mapování 1:1 mezi aplikací a databází.

    A tady už programátoři nevidí, že používáním těchto objektů v kontroleru se dopouštějí téhož prohřešku.

    Na vině jsou především Rails a vlna tutoriálů, jak vytvořit administrační rozhraní za 15 minut a přitom ještě stihnout obalit a usmažit hermelín i s hranolkama.

    Člověk nemusí studovat MVC, stačí respektovat pravidlo OOP, že objekt má skrýt své implementační detaily. Kontroleru může být jedno, jestli model používá databázi nebo data ukládá v XML do souborů.

    Takže dgx-ova břitva dobrého MVC:

    – když změním názvy sloupců v databázové tabulce, bude nutné editovat kód controlleru či view?
    – když změním HTML rozhraní za Flashové, bude nutné editovat kód modelu? (zmíněno i v článku)
    – když přehodím rozložení prvku na stránce, bude nutné editovat controller nebo model?

  3. výbornej článek :) takovej na českým internetu hodně dlouho chyběl

  4. Tak ještě jednou já…

    Teprve po přečtení tohoto komentáře href=„http://­weblog.ronnie­web.net/?p=138#­comment-8092“ rel=„nofollow“>http:/­/weblog.ronni­eweb.net/?p=138#­comment-8092 mi docvaklo, že článek má být reakcí na „mé“ nepochopení modelu. Tomu nerozumím – jak jsem psal v předchozích komentářích, s tvým pohledem zcela souhlasím.

    Webové formuláře považuju jako součást VIEW vrsty. I knihovna NForm není ničím jiným, než prezentační logikou, bez přesahu do jiných vrstev. Databázi a ORM zase považuji za nízkoúrovňovou součást modelu. Vlastně mezi databází a view je největší vzdálenost – proto jsem u Ronnieho kritizoval, jak úzce se snaží tyto dvě nesourodé části propojit.

  5. Díky za dobře napsaný článek a díky dgx, který je za to odpovědný také :D

  6. [4] Ahoj Davide, ještě jednou jsem pročítal tu diskuzi u Ronnieho. Původně jsem Tvoje reakce pochopil úplně jinak, než jak jsi je asi zamýšlel – tedy jako argumentaci proti Ronnieho prolínání view a modelu. Takže se omlouvám.

  7. Pri citani clanku ma napadla jedna otazka, kam by ste v MVC webovej aplikacii (napr. v ZF) zaradili cachovanie ? Do model, view alebo do controller?

  8. [7] Kamkoliv :). Vždyť cachovat můžu různé věci, různé části aplikace a z různých důvodů. Od výsledků databázových dotazů (tedy nízkoúrovňová část modelu) až po kompletně vygenerované stránky na výstupu (tedy view). Vždy záleží na konkrétním případě a konkrétní potřebě. Každá část se pak samozřejmě může řešit úplně jiným způsobem – zatímco některé db výsledky si uložím třeba do sdílené paměti, výstupní stránky například do statických přímo adresovaných souborů.

  9. „- když přehodím rozložení prvku na stránce, bude nutné editovat controller nebo model?“
    Správně ani jedno, že? To by měla být věc viewu.

  10. Hrachova břitva na DGXovo NForm:

    Když chci změnit label elementu, edituji controller, nebo view? :))

  11. [10] samozřejmě view

  12. Jak to tady tak ctu, tak by to chtelo podobny clanek na tema: „View neni pouze phtml sablona.“. Ne ze bych se sklebil nad tim co si lide mysli, ale sam mam v tom trochu gulas.
    Jak by melo vypadat rozhrani mezi View a Controlerem?
    Co vsechno je vec view?
    Jakym zpusobem specifikovat data pro view?

  13. Skvely clanek. Nepochopeni MVC je casto zpusobeno architekturou. Jinymi slovy, PHP neni od zakladu navrzeno podle MVC patternu. U ASP.NET ci JSP-Servlet, je Vam predlozena architektura, ktera MVC podporuje a da vice prace obejit tento vzor (viz. deprekovana vlastnost injectnuti SQL do JSP stranky), nez ho nasledovat.
    Ja osobne za hlavni cast povazuji domenovy model, coz jsou objekty reprezentujici data v DB. Samotny kontroler muze byt postaven nad DAO vrstvou nebo primo nad danym modelem. Zde je trosku problem, protoze ne vzdy musim mit DAO a ne vzdy ho musim vyuzit. Kdyz si to predstavim zkracene:
    Model – data (struktura, obsah)
    Controller – akce (akce nad daty, komunikace mezi UI a modelem)
    View – zobrazeni (bud pres ciste HTML, pres vlastni komponenty, ci jine technologie umoznujici zobrazit UI)

    Tyhle patterny jsou navrzeny jednoduse. Vzdy jde o pohled na dany kus aplikace, architektury. Stejne to muze byt ve smyslu trivsrtve architektury. Pro PHP bude klientem web browser. Pro enterprise aplikace muze byt klientem jiny app. server, web. kontejner, jina desktop aplikace, atd. Takze ani MVC neni jen pro web. aplikace. Kontrolerem je jiz samotne DAO. Ale nikde jeste neni psano, ze stejne DAO je kontrolerem dane webove komponenty.

  14. ale ale, Honzíku, malá hnidopišská poznámka k tomu příkladu se zakládáním uživatele:

    databázovou implementaci neodkrývá ani jeden z uvedených příkladů, tak, jak je to uvedeno, za tím může být nejen jakákoliv DB, ale i jakékoliv jiné úložiště (xml, acl, ..), nebo dokonce něco úplně jiného (/dev/null :o)

    zatímco v druhém případě jsi naopak odstíněn od možnosti s objektem jakkoliv rozumně pracovat, např. nastavovat vlastnosti jednotlivě, když nemáš všechny informace v jeden okamžik pohromadě, rozhodovat o persistenci, atd.. uvedená „factory“ metoda je dobrá leda jako „convenient shortcut“ a měla by se správně jmenovat „createAndStore“, „createPersistent“ nebo tak nějak..

    hezký den Ti přeje ovlčený gulzák

    p.s.: grrr.. root-doménová tečka na konci e-mailové adresy je validní formát, což si takhle doladit ten validační regexp?? předem děkuji ;oE

  15. v jake vrstve bych mel tedy kontrolovat sessions a cookies ?? v modelu nebo controlleru? (zatim to delam v modelu protoze prepsani do controlleru je zbytecne slozite)

  16. Zdravim. Dobry clanek. Ako zaciatocnika by ma zaujimalo kde najdu nejake PHP ukazky, classy alebo nejake case study. Ze specifikaci se toho moc nenaucim. Dekuji za napady ;-)

  17. Výborný článek!
    V případě ASP.NET MVC je situace obdobná – lidi serou SQL do controllerů nebo nedejbože do view ;-)

  18. No s větou:

    „Aktuální uživatel – zda je přihlášený, kdo je přihlášený, ke kterým operacím má či nemá oprávnění apod.“

    buď nesouhlasim nebo je napsána trochu zavádějícícm způsoben.

    Podle mě to kdo je přihlášenej a obecně přihlášení do modelu nepatří … aspoň já to používám tak, že kontroler řekne modelu „jsem user_id=xxx a chci udělat akci yyy“ To znamená, že model to kdo je aktuální uživatel ví jen v okamžiku kdy se provádí nějaká akce. Ale že by model měl dlouhodobě vědět něco o tom, že v aplikaci brouzdá zrovna těhle těhle pět přihlášených uživetelů … to si fakt (vynecham-li nějaký logy) nemyslim.

  19. [18] S příspěvkem zcela souhlasím, stav přihlášení aktuálního uživatele opravdu do modelu rozhodně nepatří. Díky za připomínku!

  20. Já možná půjdu trochu proti proudu, ale rád bych znal váš názor. Když jsem začal používat MVC framework tak jsem udělal klasickou zde zmíněnou chybu, že jako vrstvu modelu jsem použil (zneužil) třídy ORMka a aplikační logiku jsem měl ve větší míře v kontrolerech. Později jsem si chybu uvědomil, a kontrolery „očistil“. Tohle řešení se mi ale taky nezdálo ideální – v kontrolerech jsem neměl téměř nic na úkor modelových tříd (které jsou zároveň ORM třídami s definicemi vazeb a validačními pravidly). Nakonec jsem do konceptu MVC trochu na radu kolegy ještě zařadil „servisní“ vrstvu, ve které mám velkou část aplikační logiky. Tzn. z kontroleru zpravidla volám nějakou metodu servisní třídy (třeba getUserById, createOrder apod..) a tato servisní třída si sama sáhne pro potřebný model(y), sestaví výsledná data a pošle je zpět kontroleru, který inicializuje (případně poskládá) příslušné šablony a data jim předá. Navíc, protože některé „moduly“ mé aplikace fungují prakticky stejně jen s jinými daty a trochu jinou šablonou (novinky, články, třeba reference), některé servisy jsou „generické“ tzn. umožňují dynamicky změnu modelu se kterým pracují – což považuji za výhodu. Ve třídách modelu (ORM) mám nyní pouze definice vazeb, validační pravidla a metody pro získání kolekce dat z příslušných tabulek DB. Možná to není úplně správný postup, ale nikde jsem se nedočetl jak vlastně ORMko na úrovni modelu v konceptu MVC použít. Díky za případné odpovědi.

  21. [20] Nastíněný postup s vyhozením aplikační logiky z controllerů do servis je z mého pohledu best-practice, je to přesně to, co jsem chtěl článkem říct, i když v něm servisy explicitně nezmiňuji. Jenom si pak ve své hlavě změň názvosloví – pochopil jsem, že modelem jsou pro Tebe stále jen entity/orm, zatímco servisy jsou něco, co „na pozadí volá model“. Ve skutečnosti je to tak, že v Tvém případě pak model = databáze + entity/orm + servisy. Pokud tedy z controllerů voláš nějakou metodu některé servisy, tak už voláš model.

  22. Opožděně díky za odpověď, nevím proč jsem si předtím neprohlédl celý Tvůj web. To bych totiž zjistil, že jsem jeden z těch co objevují kolo! Poslední články o modelech (a zvlášť „pět vrstev modelu“) odpovídají na moji otázku [20] a v podstatě je v nich popsán i můj myšlenkový postup a problémy s otázkami, na které jsem narazil. Díky za perfektně podané informace o praktické pokročilé práci s MVC, kterých se člověk na webu jinak moc nedohledá.

  23. Souhlasím s tím, že tenhle článek je na webu stále vzácnost a díky za něj. Postupně prozkoumám i ostatní. :-)

  24. Ahoj Honzo, zdravím a děkuji za článek. Vedu teď prvním rokem cvičení PHP na UPCE a zajímalo mě, jestli se v PHP používá třívrstvá architektura (MVC) (mám víc zkušeností ). Chystám se studentům něco doporučit a na tenhle článek je odkážu :)

    Ještě bych se rád zeptal na vaše názory ohledně DTO objektů, které tečou mezi mezi modelem a kontrolerem (např. UserDTO). Kterou variantu preferujete?

    1. Varianta 1 – (v naprosté většině projektů se kterými jsem se setkal) to bylo tak, že UserDTO neobsahoval metody, šlo jen o kontejner na data a ty metody byly v UserService.
    2. Varianta 2 – UserDTO ani UserService neexistují, pouze User, který je zároveň kontejnerem na data ($firstname, $surname atp) a zároveň obsahuje metody, které jsou normálně v service – create, delete, update
    • Myslíte, že by mohl tento objekt obsahovat i metody, které nesouvisejí s tou konkrétní instancí?

    Můj názor je ten, že první varianta je praktičtější (stejně potřebujeme dát někam metody jako findUsers, které nepracují s instancí), ale druhá varianta je víc v duchu OOP, protože „object is usually taken to mean an ephemeral compilation of attributes (object elements) and behaviors (methods or subroutines) encapsulating an entity“

  25. [24] V jazyce PHP se MVC (nebo některé podobné architektury, jako třeba MVP) rozhodně používá a je na tom postavena celá řada používaných framworků, jako je Zend nebo Nette.

    Ohledně dotazu na DTO, osobně už dneska nepovažuji Variantu 1, tedy anemický model, za dobrý a dost silně preferuji komplexní entity v duchu Varianty 2. Naproti tomu si myslím, že by entita měla obsahovat jen a pouze metody, které souvisí přímo s její instancí, a to ještě navíc jen ty, které operují s jejími daty jako takovými, ale ne s entitou jako celkem. Více viz články http://www.phpguru.cz/…etody-modelu a http://www.phpguru.cz/…rstev-modelu včetně navazující diskuze.