Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

MVC paradox a jak jej rešit

Pokud se rozhodnete tvořit aplikace podle architektury Model-View-Controller, dříve nebo později zjistíte, že postupy, které tak krásně vypadají na papíře, při praktické realizaci pokulhávají. Narazit se dá už u nejtriviálnějších úkolů – třeba obyčejný výpis článků na blogu.

Teorie hovoří takto:

  • model má metodu getArticles(), která vrátí všechny články (například z databáze jako result-set)
  • controller / presenter tuto metodu zavolá a získaný seznam předá do view
  • view se postará o vykreslení

V praxi se ale ukáže, že budeme chtít navíc:

  1. výpis stránkovat
  2. mít možnost řadit i podle jiných sloupců
  3. omezit výběr podle vlastní podmínky
  4. určit, které sloupce přenášet

Přitom půjde vždy o požadavky prezentační logiky. Model nemusí zajímat, že výpis článků stránkujeme (bod 1). Nemusí ho zajímat, že jsme se rozhodli na web přidat boxík s deseti nejčtenějšími články (body 2, 3, 4). Nemusí ho nic z toho zajímat, avšak postup, kdy získáme z databáze tabulku všech článků, ty setřídíme a vyfiltrujeme samotnou aplikací a nakonec zahodíme nepotřebná data, by v praxi představoval šílený overhead. Proto se zdá vhodnější rezignovat na MVC a zamořit model spoustou metod alá getArticles(), které vracejí články různě seřazené a filtrované, vždy na míru konkrétním požadavkům view.

…a nebo lze použít dibi :-)

Řešení podle DibiDataSource

Dibi nabízí více způsobů, jak MVC paradox řešit, nicméně DibiDataSource je pro tento typ úkolu vyloženě navržen. Zkusím tedy nastínit koncept řešení.

Nejprve implementujeme model a jeho metodu getArticles(). Ta se pomocí SQL dotazu (libovolně složitého) dotáže na všechny články a jejich atributy.

class Model
{
    function getArticles()
    {
        return dibi::dataSource('SELECT ... FROM table1 INNER JOIN table 2 ... WHERE ...');
    }
}

Pokud dibi znáte, všimněte si, že místo obvyklého dibi::query() nebo dibi::fetchAll() zde volám dibi::dataSource(). Jinak se nic neliší.

Controller/presenter aplikuje stránkovací logiku:

class AnyPresenter extends Presenter
{
    ...

    public function renderDefault($page)
    {
        $articles = $this->model->getArticles(...);

        // přidáme nějakou dodatečnou podmínku
        $articles->where('category=%i', $this->category);

        // inicializujeme stránkovač
        $paginator = new Paginator; // Paginator je třída z Nette Framework
        $paginator->itemsPerPage = 40;
        $paginator->itemCount = count($articles); // vrací celkový počet článků; ekvivalent k $articles->count()
        $paginator->page = $page;

        // a omezíme datasource na LIMIT a OFFSET
        $articles->applyLimit($paginator->length, $paginator->offset);

        // seznam článků předáme do šablony
        $this->template->articles = $articles;
    }

    ...
}

Nakonec si šablona může říci, o které sloupce má zájem a může také nastavit vlastní řazení (kvůli čitelnosti jsem vynechal volání htmlSpecialChars):

<h1>Stručný přehled článků</h1>

<?php foreach ($articles->select(array('url', 'title', 'perex'))->orderBy('title') as $article):?>
    <h2><?= $article->title ?></h2>
    <p><a href="<?= $article->url ?>"><?= $article->perex ?></a></p>
<?php endforeach ?>

Klíčové je, že se provedou jen dva SQL dotazy, které si vyžádají jen skutečně vypisovaná data. V tomto případě se třeba nemusí přenášet celé texty článků, protože šablona vypisuje pouze perexy. Výsledkem je přehledný kód, optimalizované využítí databáze a neposkvrněná architektura MVC. Za zmínku také stojí, že dotaz na databázi se provádí až při volání count v presenteru a foreach v šabloně.

Doplnění: bohužel, pro MySQL to je nepoužitelné :-(

Komentáře

  1. Shabbi. http://shabbi.cz #1

    avatar

    Paráda, zrovna před pár dny jsem tohle řešil při rozmýšlení nad architekturou jedné menší aplikace využívající krásu a sílu Nette a Dibi :-)

    A ještě k tomu voňavou kávu z Nette automatu, díky :-)

    před 8 lety
  2. Honza M. http://www.janmarek.net #2

    avatar

    DibiDataSource je moc pěkná věc.

    Jenom se mi špatně píše dataSource->select(...), když je těch sloupečků hodně. Líbilo by se mi nějaké zjednoudušení, že by šlo místo $articles->select(array('url', 'title', 'perex')) psát třeba $articles->select('url,title,perex').

    před 8 lety
  3. Washo #3

    avatar

    A co na to DGXova britva dobreho mvc konkretne bod 1?

    • když změním názvy sloupců v databázové tabulce, bude nutné editovat kód controlleru či view?

    Nepremyslis nad tim, jak dostat mezi sablonu a db jeste nejakou nepovinnou mezivrstvu?

    Napadl me nasledujici priklad. Mam multijazycny web a chci vypsat titulek produktu v pozadovanem jazyce, nebo pokud neni prelozen tak v defaultnim jazyce.
    V sablone potom jenom {$product->getTranslatedTitle()}. Super by bylo, kdyby datasource umel prijmout i argument, ve kterem specifikuju tridu ktera se ma vracet (jinou tridu nez DibiRow) (asi implementaci IDibiRow). ? To by bylo si myslim uz potom docela fer.

    před 8 lety | reagoval [12] David Grudl [22] PHX
  4. Daniel http://www.danielkvasnicka.net #4

    avatar

    Hm, vidim to presne naopak. Radek typu

    $articles->select(array('url', 'title', 'perex'))->orderBy('title')

    nema podle me ve view vubec co delat. Co ma view co vedet o tom, ze se to nejak radi a podle ceho? View ma iterovat pres data a zobrazit je.
    Az budete chtit stejna data poslat treba pres webservisu v XML nebo JSONu, budete tuhle logiku vybirani a razeni psat znova a az to budete menit, budete to menit na 2 nebo 3 mistech? Misto toho, abyste jen napsal nove sablony, ktere iteruji a vypisuji dodana data v jinem formatu…

    Nehlede na to, ze z toho jeste rovnou cisi, ze pouzitym datastorem je relacni databaze, coz uz je uplne blbe… (i kdyz uznavam, ze to neni jednoznacne)

    Stejne tak nechapu to o tom „zamorovani“. Model je vrstva, ktera ma dodat data takova a v takove podobe, jak chce controller (nebo treba view helper). Na metode getArticles(orderBy, page) nevidim nic spatneho.

    před 8 lety | reagoval [12] David Grudl
  5. Koubas #5

    avatar

    Řeším to stejně, jenom používám jinej stavební materiál (ehm… ZF, ehm… Doctrine), akorát si pořád nadávám do různých hospodářských zviřátek, že tím vlastně stěhuju logiku z modelu do controlleru. Ono totiž ve většině případů není nutné mít ani žádnou metodu modelu getArticles(), která by vracela Query object k dalšímu doupřesnění podmínek, ale je kratší si celou query sestavit přímo v controlleru :)

    před 8 lety | reagoval [11] Daniel
  6. Aleš Roubíček http://rarous.net/ #6

    avatar

    Dibi začíná pomalu LINQovatět :) Dobrá práce.

    před 8 lety | reagoval [13] David Grudl
  7. Almad #7

    avatar

    Asi jsem tu MVC pochopil špatně, ale dokonce i v mé wikipedii je:

    As an architectural pattern
    (…) and the controller is the code that gathers dynamic data and generates the content within the HTML or XHTML (…)

    Podle mě to docela zapadá do toho co je na papíře: Controller není jenom vrstva navíc (ona by jinak nedávala smysl), ale prostře vrstva která reaguje na akce uživatele, „čistí“ je a podle nich komunikuje s modelem.

    Model nezajímá, že jsme se rozhodli stránkovat. To zajímá controller, který si taky podle toho řekne o patřičné články (v Djangu by to třeba bylo Articles.objects.all().ordered_by(‚sloupec‘)[od:do], což by se přeložilo do patřičného querysetu, ale třeba to může být Articles.get(order, from, skip), whatever).

    Každopádně v tom pořád nějak nevidím MVC problém (čimž nepolemziju s tim data sourcem ,))

    před 8 lety
  8. v6ak http://v6ak.profitux.cz #8

    Možná to je trochu OT, ale když tu chválíš ten jeden dotaz, tak řekněme, že chceš je každému článku i jméno autora. V tabulce je pochopitelně jen id, …
    O to by měl požádat view nebo controller (oboje má své výhody a nevýhody).
    Samozřejmě by to mělo jít přes model, třeba pomocí getUsers().
    Dál, hodilo by se umožnit trošku omezit nebo abstrahovat názvy sloupců.
    Názvy sloupců by ve viewu být mohly, ale orderBy mi tam moc nevoní.

    před 8 lety | reagoval [13] David Grudl
  9. Almad #9

    avatar

    @v6bak: Pokud to bylo na mne, tak:

    – Nechvalim ten jeden dotaz, to byla jen demonstrace. Pointa byla v tom, ze si na to proste vytvorim pristup k modelu.
     – V tabulce ano, ve vystupu v modelu ne, pokud si ho tak naprogramuju. To je pointa: model nejsou jen prelozene tabulky na objekty, model je logika, ktera se chova tak jak chci
     – Ano, abstrahovat by se to melo, na tom co jsem popisoval to nic nemeni
     – To co pisu se dava do controlleru, ne view

    před 8 lety | reagoval [15] v6ak
  10. Borek http://www.borber.com/ #10

    avatar

    Vida, něco jako LINQ v PHP :) Chvályhodný počin, i když teda souhlasím s Danielem, že složitější metody getArticles() v modelu nejsou nutně špatné (nazval bych je jen méně elegantními). A už vůbec bych nešel tak daleko, abych použil výraz „paradox MVC“; s tím marketingem se to zas nesmí tak přehánět :)

    před 8 lety | reagoval [13] David Grudl
  11. Daniel http://www.danielkvasnicka.net #11

    avatar

    #5 Koubasi, To jako ze primo v controlleru mastis SQL?..... :-)

    Jednou ze zakladnich myslenek MVC je, ze M, V i C musi byt „pluggable“ (nechce se mi premyslet nad ceskou alternativou :-) ). Tedy v praxi primarne M a V. Model by mel byt v idealnim pripade naprosto nepruhledny, pokud jde o typ backendu. Tzn. vracet asoc. pole, objekty z domenovych trid, cokoliv… ale tak, aby pri zmene zpusobu ulozeni dat nebylo nutne zmenit v C a V ani carku. To taky znamena zadnou vazbu controlleru na konkretni ORM a uz vubec ne psani SQL dotazu v C vrstve… :)

    Tim nechci rict, ze se mi nikdy toto nepodarilo porusit nebo ze snad je mozne to vzdy na 100 % dodrzet, jen se snazim toto mit jako svuj cil u kazde aplikace – i kdyz treba u XRX aplikaci je prakticky nemozne toho dosahnout – to je proste koherentni architektura, u niz vymena jedne casti znamena ztratu smyslu celku

    před 8 lety | reagoval [24] Koubas
  12. David Grudl http://davidgrudl.com #12

    avatar

    #3 Washo, Když změním názvy sloupců v databázové tabulce, nebo dokonce z nějakého sloupce udělám číselník do nové tabulky, tak nemusím měnit API modelu. Jen napíšu SQL příkaz tak, aby generoval stejný výsledek, třeba pomocí aliasů.

    Zrovna ten multijazyčný web je nádherný příklad. Model by měl metodu setLang() a SQL by poskládal tak, aby ve sloupci title byl titulek ve správném jazyce, ať už je multijazyčnost v databázi řešena jakkoliv.

    Super by bylo, kdyby datasource umel prijmout i argument, ve kterem specifikuju tridu ktera se ma vracet (jinou tridu nez DibiRow)

    Ano, to je v plánu. Zatím to jde jen kostrbatě.

    #4 Danieli,

    Co ma view co vedet o tom, ze se to nejak radi a podle ceho? View ma iterovat pres data a zobrazit je.

    A kdo jiný, než právě view, to má vědět? Možná teď jen mícháš pojmy view a šablona. Tenhle článek je o view.

    Nehlede na to, ze z toho jeste rovnou cisi, ze pouzitym datastorem je relacni databaze.

    Pouze u metody where() (proto také není v IDataSource). Jiný datasource ji nemusí implementovat.

    před 8 lety | reagoval [14] Daniel [16] Washo [22] PHX
  13. David Grudl http://davidgrudl.com #13

    avatar

    #8 v6aku,

    Dál, hodilo by se umožnit trošku omezit nebo abstrahovat názvy sloupců. Názvy sloupců by ve viewu být mohly, ale orderBy mi tam moc nevoní.

    Abstrahuj ve své mysli. Názvy sloupců určuje API modelu, někde v dokumentaci metody getArticles() bude jejich výčet. Nikde přitom není řečeno, že stejné názvy sloupců má databáze (ani nemusí).

    A pokud už máme názvy sloupců dané, je přeci jedno, jestli je použiju v echo $row->title nebo $articles->orderBy('title')

    #6 Aleši Roubíčku, #10 Borek hehe, koukám že Microsoft se mnou stále drží krok :-))) (což je marketingově ještě přehnanější nadpis než ten „paradox“!)

    před 8 lety | reagoval [15] v6ak
  14. Daniel http://www.danielkvasnicka.net #14

    avatar

    #12 Davide Grudle,

    A kdo jiný, než právě view, to má vědět? Možná teď jen mícháš pojmy
    view a šablona. Tenhle článek je o view.

    No vzdyt ty to ale mas prave v te sablone… Jasne, treba v GUI aplikacich, kde je ve view nejaka tabulkova komponenta zobrazujici data, tam vi View o razeni. Ovsem v klasickem pripadu webove aplikace bez RIA natury ci AJAXu nevidim duvod. View pres HTTP preda controlleru a potazmo modelu pozadavek na razeni a stranku a controller mu zpet preda data z modelu, ne? Proto je dobre pouzivat sablonovaci systemy, kde nelze jen tak jednoduse mastit rovnou PHP (dosadit vlastni oblibeny jazyk)… nehlede na to, ze pak lze pouzit stejnou sablonu na nekolika mistech. Tu sablonu, co tu mas, pouzijes tam, kde chces url, title a perex razene podle title a konec.

    před 8 lety
  15. v6ak http://v6ak.profitux.cz/ #15

    #13 Davide Grudle, Možná jsem se na to špatně podíval. TEď to vapadá ještě zvláštněji. Cože se stane? Dibi zparsuje SQL a pak dělá fluent-like úpravy?
    #9 Almad „V tabulce ano, ve vystupu v modelu ne, pokud si ho tak naprogramuju. To je pointa: model nejsou jen prelozene tabulky na objekty, model je logika, ktera se chova tak jak chci“
    E? Na co vlastně odpovídáš? S tou „pointou“ souhlasím.

    před 8 lety
  16. Washo #16

    avatar

    #12 Davide Grudle, No tim padem multijazycny web neni zas az tak dobry priklad pro to co jsem jim chtel ilustrovat. :) Slo mi hlavne o to ze pomoci SQL se prave obcas neda uplne vsechno dost pekne vyjadrit a co by slo jednoduse v PHP musi se v SQL delat slozite. Kazdopadne v tom pripade me tesi, ze se pocita s tim, ze Datasource bude umet vracet i jinou tridu nez Row. Super a uz se tesim.

    před 8 lety
  17. Aichi http://www.czechdesign.cz/blogs/aichi #17

    avatar

    Nezdá se mi, že tento návrh obcházení smyslu MVC je nejlepší řešení virtuálního problému. MVC má mimo jiné za úkol oddělit prezentační vrstvu od aplikační. Zde se tyto vrstvy snažíme promíchat, tedy View musí znát jak jsou do něj data předávána z aplikační vrstvy pomocí DibiDataSource. To je ale popření principu MVC.

    před 8 lety | reagoval [18] David Grudl
  18. David Grudl http://davidgrudl.com #18

    avatar

    #17 Aichi, to přece není návrh na obcházení MVC, právě naopak. Ukázka, jak psát aplikace šikovně rozdělené do tří vrstev (tři ukázky kódu) a vyhnout se promíchání.

    Něco víc k MVC.

    před 8 lety | reagoval [19] Aichi
  19. Aichi http://www.czechdesign.cz/blogs/aichi #19

    avatar

    #18 Davide Grudle, myslím, že si nerozumíme. Ano MVC rozděluje aplikaci na tři vrstvy. Nicméně v aplikaci máme také vrstvy jiné (aplikační a prezenční). A tyto dvě vrstvy v tomto nešťastném příkladu mícháš a porušuješ Single Responsibility princip.

    Proč má View vědět o tom jak se získávají data do modelu? Proč mají umět pracovat s DibiDataSource? Všechna relevantní data mají dostat od modelu, který vizualizují.

    před 8 lety | reagoval [20] Washo [21] David Grudl
  20. Washo #20

    avatar

    #19 Aichi, Myslim ze te matou ty nazvy metod select a orderBy, ktere jsou takove SQLacke. Ja to beru tak, ze v sablone jimi pouze definuji data, ktere budu potrebovat pro vypis. Je to proste jenom chytrejsi volani $article->title, nebo spis dopredu dam vedet co budu potrebovat a vytvoril optimalnejsi iterator. Dalo by se to presunout do modelu ale tim bys snizil pouzitelnost metody Model::getArticles(). A docela zbytecne.

    před 8 lety
  21. David Grudl http://davidgrudl.com #21

    avatar

    #19 Aichi, IDataSource je API modelu. Model má metodu, která vrací IDataSource (stejně jako kdyby vracela řetězec, pole, nebo cokoliv jiného, je to jedno).

    View neví, „jak se získávají data do modelu“, nezajímá ho to. On dostane IDataSource, konkrétně jeho implementaci DibiDataSource a s tou pracuje. Nad IDataSource se dá iterovat a vypsat jeho obsah. Iterování lze omezit na určitý rozsah. Dokonce lze data pro iterování nechat setřídit podle nějakého sloupce. Ale rozhodně ho nezajímá, jak je to řešeno uvnitř, jestli se data tahají z SQL databáze a kdy se tahají. Model klidně může vrátit ArrayDataSource, což bude zapouzdření nad statickou tabulkou. Na view to nebude mít dopad.

    před 8 lety | reagoval [28] Aleš Roubíček
  22. PHX http://phx.gipix.net #22

    #3 Washo,

    Nepremyslis nad tim, jak dostat mezi sablonu a db jeste nejakou nepovinnou mezivrstvu?
    Super by bylo, kdyby datasource umel prijmout i argument, ve kterem specifikuju tridu ktera se ma vracet (jinou tridu nez DibiRow)

    #12 Davide Grudle,

    Ano, to je v plánu. Zatím to jde jen kostrbatě.

    Neco takoveho jsem uz resilt.

    // vycuc
    class MyDataSource extends DibiDataSource {
    
            private $rowClass = null;
    
            public function __construct($sql, $rowClass = null, DibiConnection $connection = null) {
                $this->rowClass = $rowClass;
                if ($connection === null) {
                    $connection = dibi::getConnection();
                }
                $sql = $connection->sql($sql);
                parent::__construct($sql, $connection);
            }
    
    public function getResult($debug=0) {
                try {
                    $result = parent::getResult();
                }
                catch (DibiException $ex) {
                    throw new ModelException(Model::ERROR_DB, Model::ERROR_DB_NUMBER, $ex);
                }
                self::printDebug($result, $debug);
                if ($this->rowClass && $result instanceof DibiResult) {
                    $result->setRowClass($this->rowClass);
                }
                return $result;
            }
        }

    Takze nakonec kdyz zavolam getReuslt() dostanu DibiResult, ktery bude vracet mnou specifikovany objekt, kde si mohu s daty delat co chci (zprehazet ci prejmenovat sloupecky dle nejakeho ORM)

    Aby bylo jeste psani SQL dotazu prijemnejsi vyuzivam:

    dibi::setSubstFallBack('dibi_substFallBack');
    
    function dibi_substFallBack($subst) {
            if(!$subst || strpos($subst, '.')===false) return '';
    
            list($model, $item) = explode('.', $subst);
            switch($item) {
                case 'table':
                    return dibi::$substs[''] . call_user_func(array($model, 'getTable'));
                case 'pk':
                    return call_user_func(array($model, 'getDBPK'));
                default:
                    return call_user_func(array($model, 'getColumn'), $item);
            }
        }

    kde se napr dotaz: SELECT * FROM :Clanky.table: prevede na prislusny nazev v DB.

    PS: lze nejak pri psani komentare upravit nahled? Mam tu jen moznost Pridat komentar

    před 8 lety
  23. zadluzen http://www.zadluzeni.cz #23

    avatar

    Výsledkem je ..... neposkvrněná architektura MVC.

    Tak o tomto tvrzení silně pochybuji (diskuzi jsem četl).
    Čuňáren, kdy volám nějaké funkce ze šablony, se snažím odnaučit. Toto je IMHO krok zpět. A zcela zbytečný, protože totéž mohl zařídit controller a view měl dostat již předchroustaná data.

    před 8 lety | reagoval [26] Gimli2
  24. Koubas #24

    #11 Danieli, „To jako ze primo v controlleru mastis SQL?..... :-)
    Ne, jelikož použivam Doctrine tak žádné SQL v pravém slova smyslu nemám.

    Zezačátku mi to přišlo být podobné, jako je uvedeno v článku – jelikož s Dibi ani Nette jsem ještě nedělal, tak jsem měl pocit, že DataSource je jakýsi náznak ORM. Pokud je ale IDataSource dostatečně univerzální a ne napsaný na míru pouze relačním databázím – jako v případě ORM, tak to beru v rámci PHP jako docela revoluční počin, protože s podobnou implementací jsem se v něm ještě nesetkal, pokud si ale dobře vzpomínám, něco podobného jsem kdysi zahlédl v Delphi.

    Každopádně se tímto pro mě stal článek prudce inspirativním :)

    před 8 lety
  25. v6ak http://v6ak.profitux.cz/ #25

    @kritici: Tak především si uvědomte, že to je nezávislé na implementaci zdroje dat. Minimálně pro výběr sloupců to je čisté.
    Nějaký join by taky měl být čistý, i když nevím, jak na něj.
    Co se týče řazení a filtrování, to bych už ale do viewu fakt necpal.

    před 8 lety
  26. Gimli2 http://gimli2.gipix.net #26

    #23 zadluzene, Tak podle mě jde v první řadě o to, jaký koncept (pull/push) šablon používáš. Zda si šablona o data žádá sama, nebo zda jsou do ní data nacpaná…
    Žádá-li si sama, proč by mělo být prasárnou zavolat si nějakou modifikační funkci navíc?

    před 8 lety
  27. Tomáš Fejfar http://blog.red-pill.cz #27

    avatar

    Tohle se příčí ne snad přímo MVC, to určit ne, ale spíše mému osobnímu chápání VIEW.

    Když bych to měl popsat nějak tak polopaticky – třeba na všem programátorům blízké analogii s pečením – VIEW je jako formička (template) do které se naplní těsto (data), aby to nějak ve výsledku vypadalo. Pokud chci v těstě oříšky nebo kakao, tak je musím přidat na recept (controller) nebo je přihodit když těsto míchám (model).

    před 8 lety
  28. Aleš Roubíček http://rarous.net/ #28

    avatar

    #21 Davide Grudle, Zkus IDataSource přejmenovat na IEnumerable nebo IQueryable a přestane to pálit. :)

    před 8 lety
  29. m4tt #29

    avatar

    OT: moc pěkné písmo na nadpisy :p (generuje se nějak „zvláštně“ tím přiloženým js?)

    před 8 lety | reagoval [30] David Grudl
  30. David Grudl http://davidgrudl.com #30

    avatar
    před 8 lety
  31. nightfish http://hrazdil.info/ #31

    avatar
    před 7 lety
  32. blizzy #32

    avatar

    Neměl by být dotaz count a výběr článků v transakci?

    Co když zatímco tvořím paginátor (v těch několika milisekundách) někdo půlku článků smaže?
    Vyrendruje se nekonzistentní stránka, paginator neodpovídá počtu dat.

    Mýlím se, nebo mám pravdu a mělo by se to nějak řešit?

    před 6 lety | reagoval [33] David Grudl
  33. David Grudl http://davidgrudl.com #33

    avatar

    #32 blizzy, v transakci by to být mohlo, záleží na tom, jak je ta konzistence klíčová. Když někdo půlku článků vymaže ihned po vygenerování stránky, stejně nebude paginátor odpovídat počtu.

    před 6 lety
  34. jahudka http://reggaeautumn.cz #34

    avatar

    Mno, tak abych přispěl svou troškou do mlýna :o) když jsem ten článek četl, dělalo se mi špatně, když jsem četl komentáře a začal jsem tušit, co vlastně chtěl básník David říct, dělalo se mi ještě hůř – protože mi trvalo tak dlouho, než mi to došlo. :o)

    Myslím, že celej problém je tady v názvosloví. Ono je to logický, že dibi::dataSource() bude vracet objekt DibiDataSource, ale ten zavání spojitostí s dibi a tím pádem s SQL. Je logický, že metoda, která řadí data, se bude jmenovat order(), orderBy() nebo nějak podobně, což už ze zvyku člověku taky zavání SQL. Totéž platí i o limitu. Kdyby se ty věci jmenovaly třeba DibiValueFiller, sort() a slice(), asi to bude zavánět něčím jiným, žejo :o)

    Ale důležitý je, co se děje na pozadí. Pokud to správně chápu, stane se to, že když DibiDataSource vytvoříme, zapamatuje si svůj SQL dotaz a nic jiného nedělá. Pak v šabloně na něm zavoláme orderBy() a limit(), čímž on jen upraví svůj SQL dotaz a pořád nic nedělá. A až teprve v momentě, kdy mu dojde, že už od něj chceme data, usrkne si trochu z databáze.

    Zkusím příklad s úplně jinými jmény :o) :

    Model při volání funkce např. getArticles() vrátí pouze prázdnou schránku, dejme tomu instanci třídy ArticleCollection, a uloží do ní místo dvaceti nejčerstvějších článku jenom definici toho, jak mají ty články být vybrány (například, ale skutečně jen například, SQL dotaz nebo SQL fluent). ArticleCollection se tak dostane do rukou presenteru, ten jej předá view; tady je myslím neodargumentovatelné, zda má stránkovat presenter nebo view či zda má řadit presenter nebo view, protože to jsou operace takového nepříliš jasného charakteru – je to sice především věc zobrazení, ale zahrnuje práci s netriviální logikou (jakoukoliv jinou než if..else a foreach). Myslím, že je to věc vkusu či zvyku. Zkrátka v některém momentu některá naše vrstva zavolá na ArticleCollection metodu sortBy($what), v nejlepší víře, že se články seřadí podle zadání. Pak si ještě vybereme jen část všech článku prostřednictvím třeba metody subset($start, $length) a chceme se pustit do jejich výpisu.

    A tady přijde ten vtip: saháme pořád ještě do zcela prázdného kontejneru, který jenom ví, co by v něm mělo být, ale taky ví, že to v něm není :o) a náš model zaregistruje, že jeden z kontejnerů volá mejdej a na poslední chvíli do něj nasype pořádnou dávku těch nejšťavnatějších článků… :o)

    Je to perfektní, je to maximálně lazy, jsme skutečně odfiltrování od toho, jak model data získá a předžvýká, máme unifikované API na všechny kontejnerky atd
    atp. :o)

    Díky za perfektní článek jako vždy, Davide :o)

    před 6 lety
  35. Tomas #35

    avatar

    Lze query definovane v dataSource nejak vice modifikovat na zaklade definice z view? Co kdyz napr. to ze v pohledu napisi $articles->select(array(‚necodalsiho‘)) by vyzadoval JOIN do jine tabulky? Psat ten join z view by nebylo asi optimalni.
    Musim ten join napsat uz v modelu (getArticles()) a smirit se s tim ze nakonec treba pro pozadovana data vubec nebude potreba, nebo lze dataSource doplnit o nejake podminky ktere se vyhodnoti az na zaklade konkretniho pozadavku, rozsirit o nejaky mapper apod? Mozna se ptam kostrbate tak se predem omlouvam :o)

    před 6 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.