Autor Zpráva
Numero1990
Profil
Zdravím,
mám další dotaz ohledně MVC.
Kdyz koukam na netu na příklady modelů, vždycky tam dělají instanci.
Co to ma za výhody? Mně přijde lepší statická metoda, neboť můžu hned přiřadit hodnotu.

Diky
Ugo
Profil
Nevím co myslíš tím že můžeš ihned přiřadit hodnotu, faktem je, že model nemusí být většinou instance, v aplikaci se používá většinou 1x, má jedno svoje globální nastavení a statické metody jsou rychlejší, výhodu vydím v syntaxi "->" je mnohem hezčí a rychlejší na psaní než "::" :-D Instance tě navíc neomezují kdybys potřeboval použít 2 stejné modely s jiným nastavením a dají se dobře nastavit v konstruktoru případně v jiné init metodě při vytvoření, je tam toho víc, ale bez většiny se nechá obejít na úkor vyšší rychlosti.
Numero1990
Profil
V tom případě jsem asi nepochopil, jak se má napsat správný model. Moje představa byla asi taková:
Mám třídu User, kde budou všechny metody pro práci s daty v DB.
Př:
class User extends ...
{
  public static function getUserInfo($id)
  {
    // SQL a PHP, které mi vrátí array('name' => '..', ...) nebo array(), pokud uživatel nebyl nalezen.
  }
  
  ...
}

V kontrolleru, když potřebuju třeba data o uživateli, abych je předal do šablony, tak:
....
$args['template']['userInfo'] = User::getUserInfo($_SESSION['id_user']);
....

Nedojde mi ke kolizi, pokud budu chtít info o více uživatelích, protože ID předám jako parametr. Příjde mi to třeba lepší, než kvůli týhle funkci volat:
....
$user1 = new User($_SESSION['id_user']);
$args['template']['userInfo'] = $user1->getUserInfo();
....
Joker
Profil
Numero1990:
Přijde mi to divné. Proč proboha metoda GetUserInfo vrací „array('name' => '..', ...)“? K čemu ta třída User vůbec je, když informace o uživateli se předávají jako pole?
A odkud je ta metoda vezme, když je statická a jediný vstup je id uživatele? To používá globální proměnné (fuj), nebo někde uvnitř má natvrdo definované odkud má číst (taky fuj)?

Lehce odlišné, ale doufám související téma:
Když vezmu třídu User, načítání z datového zdroje by tam nemělo být, protože to vytváří závislost třídy User na konkrétním datovém zdroji.
Takovou metodu by asi měla mít servisní vrstva, resp. by ji zahrnovalo rozhraní (interface), které má servisní vrstva implementovat.
Potíž je, že při důsledné aplikaci téhle úvahy pak část tříd nemá vůbec žádné metody, což připomíná anemický doménový model.
Kde si tedy myslíte, že má vést hranice mezi modelem a servisní vrstvou?
Nox
Profil
Numero1990:
Není to flexibilní, testovatelné, nejde použít DI, vše je jen jednou, vše je globální...
Numero1990
Profil
Joker:
Třída User mi zajišťuje načítání dat z databáze (to jsem myslel, že je přesně záležitost modelu).

Pro úplnost nějak takhle.
class User extends ...
{
  public static function getUserInfo($id)
  {
    $sql = mysql_query("SELECT ... FROM `users` WHERE `id` = '".intval($id)."'");
    
    if(mysql_num_rows($sql) === 1)
      return mysql_fetch_assoc($sql);
      
    return array();
  }
  
  ...
}

Data mi předává jako pole, neboť je statická. Možná už vidím, proč dělají instanci.
Protože tu instanci pak předají do šablony, a tam volají $user->getName()?

Abych tedy odpověděl na otázku, k čemu je třída User. Třída User obsahuje metody, které mi vrátí data z databáze ve formě pole tak, abych nemusel míchat sql v kontrolleru.
Tzn pokud budu někdy v celém projektu potřebovat seznam všech úkolů uživatele, třída User bude obsahovat metodu getTasksOfUser($id) (v případě instantního třídy bych si $id předal v konstuktoru a jako parametr bych ho nemusel předávat).

Př (statický model):
class User extends ...
{
  public static function getUserInfo($id) { ... }
  public static function getTasksOfUser($id) { ... }
  public static function logInUser($username, $password) { ... }
  ...
}
Nox
Profil
Model implementuje nějakou logickou funkcionalitu projektu, kde "model" je velmi často skupina tříd, ne jediná.
V aplikaci databáze vůbec být nemusí

Protože tu instanci pak předají do šablony, a tam volají $user->getName()?
No to ani ne ... v šabloně bychom klidně mohli použít $user['name'] ...

Psaní pomocí statických modelů defakto není OOP, nebo jen velmi málo ... což nemusí být nutně špatně, každopádně to tak je.

Třeba spolu s nemožností DI v podstatě také zaniká polymorphismus

Takže statický model poskytuje jakési prvotní pohodlí, platí se za něj neúměrně velkými nevýhodami, které často nezhoršují, ale úplně blokují spoustu možností.
Numero1990
Profil
Nox:
Děkuji, těch tříd bude samozřejmě více.

Já model používám tak (tak jsem to pochopil, budu si muset najít něco, kde budou i konkrétní příklady (od jednodušších, až po složitější)), že ho využívám po vzoru ER modelu (případně UML). Co entita, co třída. Jsou tam základní settery, gettery (příše tedy insert, update, delete) a pak zbylé metody, které mi popisují vztahy mezi entitami. Tzn, pokud mám vztah mezi User a Task, tak obě entity budou obsahovat metody (User například getTasksOfUser() a Task zase getOwner()).

Vím, že sem se to asi moc nehodí, ale mohl by sem někdo uvést jednoduchý příklad modelu (řekněme na těchto 2 třídách - čili User a Task).

Děkuji

Edit:// Teď jsem si uvědomil, že si model asi pletu s proxy třídami nad DB.
Joker
Profil
Numero1990:
Třída User mi zajišťuje načítání dat z databáze (to jsem myslel, že je přesně záležitost modelu).
Právěže model by na zdroji dat závislý být neměl, kvůli znovupoužitelnosti.
Ten příklad výše je přesně ukázka: $sql = mysql_query("SELECT ... - a co s tím dál? Teď to musí ten dotaz někde provést.
Jelikož je to statická metoda, nejspíš musí spoléhat na to, že nějaký kód předtím otevřel připojení k (té správné) databázi. Neboli ta třída (nejspíš) spoléhá na to, že nějaký okolní kód jí připraví prostředí.
Což je chyba, třída by měla být uzavřená entita, odstíněná od okolního prostředí.

Navíc když celý model bude udělaný takhle a já budu mít (z pohledu teorie OOP triviální) požadavek, aby příští verze podporovala přepínání mezi datovými úložišti (třeba čtení volitelně z XML a z databáze), znamená to přepsat celou aplikaci.
Alphard
Profil
Joker:
Tak XML nebo jíné typy databází jsou v běžné praxi asi jen vzpomínky na teorii. Nemyslím, že by to Numero1990 musel teď řešit :-)

Numero1990:
Kolegové vás vedou správným směrem, ale já bych prioritně doporučil začít používat třeba dibi z důvodů bezpečnosti, ošetření chyb, pohodlnosti skládání dotazů.

Statické modelové metody si na jednodušší výběry z databáze umím funkčně představit, ale moc mi pak nezapadají do dalších představ o kódu :-) Radši bych vytvářel instance i když teď nemusíte vidět přidanou hodnotu.
Ugo
Profil
když budu chtít vyměnit mysql za xml, tak prostě musím přepsat aplikaci, nemusím přepisovat pokud vyměním mysql za sqlite atp. ale jinak se přepisu neubráníš. DI statické metody nevylučují. Proč by to nebylo testovatelné také nevím :-/

Numero1990:
ano model chápeš dobře, když pohledáš pořádně tak najdeš příklady jenž se přesně shodují s tvým pohledem. viz. příklad z nette: return $this->database->table('albums');
nic by nebránilo použít self::$database->table();

nebo příklad odemě jenž dělá to co ty
class users_model extends m {
    function __init__() {
        $this->db->table('users');
    }
    function getInfo($id) {
        return $this->db->where('id',$id)->select()->fetch_assoc();
    }
}
Joker
Profil
Alphard:
Tak XML nebo jíné typy databází jsou v běžné praxi asi jen vzpomínky na teorii.
XML je asi teorie, ale jiné typy databází než MySQL jsou řekl bych podporované daleko víc než dříve.

Byla to ale spíš ukázka, uvedená třída bude závislá na jakékoliv změně ve zdroji dat, včetně třeba přejmenování tabulky. Dokonce bude závislá i na tom, že jí předchozí kód připraví připojení k databázi, což mi přijde velmi záludné a z pohledu teorie OOP poměrně zásadně špatně (třída používá de facto globální proměnnou definovanou někde venku)

já bych prioritně doporučil začít používat třeba dibi z důvodů bezpečnosti, ošetření chyb, pohodlnosti skládání dotazů.
Já to vzhledem k příspěvku [#1] beru spíš obecně a teoreticky.

Ugo:
Proč by to nebylo testovatelné také nevím :-/
Jak v takovémhle kódu otestuji funkčnost třídy User pokud ještě nemám připravenou databázi s daty?

když budu chtít vyměnit mysql za xml, tak prostě musím přepsat aplikaci, nemusím přepisovat pokud vyměním mysql za sqlite atp.
I při výměně za sqlite bude nutné přepsat celou aplikaci, protože ta třída uvnitř používá mysql funkce.
Navíc když tu aplikaci budu pak chtít použít jako součást projektu s jinak pojmenovanými tabulkami (třeba i „prvniprojekt_User“ vs. „druhyprojekt_User“), bude opět nutné přepsat celou aplikaci.

Kdyby byl návrh takový, že budu mít třeba interface IUser s metodou GetUserInfo, která převezme id uživatele a vrací objekt User, přičemž kontroler bude mít datový zdroj (dále třeba $zdrojdat) implementující IUser, místo třeba:
$uzivatel = User::GetUserInfo($id);
by bylo:
$uzivatel = $zdrojdat.GetUserInfo($id);
Přitom aplikaci je úplně jedno, jak $zdrojdat vlastně ta data získá, takže když budu chtít data nově číst třeba z XML, musím napsat jen třídu třeba XMLUserData implements IUser, která zajistí to samotné načtení dat. Když budu chtít zkusit funkčnost a nebudu ještě mít databázi, předhodím kontroleru třeba DummyUserData (implements IUser), která vrátí nějaké předvyplněné testovací hodnoty.
Ugo
Profil
Joker:
přečetl jsem 15x a stejně jsem nepochopil ani slovo, tudíž vzdávám :-) Ale protože bych se i přes vnitřní bloky chtěl naučit tomuhle uvažování tak budu pokračovat jak jsem to pochopil.

Jak v takovémhle kódu otestuji funkčnost třídy User pokud ještě nemám připravenou databázi s daty?
Nijak, stejně tak jak si já představuji tvojí třídu vracející data, tak také neotestuje nic (já to vidím takto ten test...a toto skutečně není potřeba testovat), ale jak říkám nechápu tento psaný text.
function getData() {
  return "a";
}
if(getData()=="a") echo "OK";
else echo "KO";

Co se týká $zdrojdat, tka to jsem pochopil tak, že $this->database nahradím za $this->xml .. opět jsem si nepomohl (asi přejmenuju svoje db na storage abych pak nemusel měnit názvy :D ), přepisuji celou aplikaci. Ohledně názvů tabulek už vůbec nemám tušení, ten název musím znát, čili
A. nakonfiguruju si ho v configu včetně struktury, předám jej jako DI či konvencí (glob. úložiště ... výsledek je stejnej, ale u DI se upíšu když budu předávat každou blbost)
B. napíšu jej na ta místa kam patří - zrychlím aplikaci, při změně mě čeká úprava na více místech
C. vytvořím konvenci jako má třeba notorm a při změně se nevyhnu opět přepsání všeho

já preferuji metodu B, model zastupuje zdroj dat z urřitého umístění které mu řeknu při inicializaci, tedy chci-li XML bude zastupovat soubor, v databázi tabulku a její vazby. Samozřejmě aplikace je hodně provázána takže tady to naráží na to co do kterýho modelu patří a tak vznikají kombinace mezi zdrojem dat a oním GetUserInfo(). Model by taky neměl vědět nicmoc o aplikaci a aplikace o datech, jestliže předávám z controlleru název tabulky, trošku to porušuji (viz možnost A) a zbavuji se právě tý lehký výměny a přejmenování.

Kdybys to vysvětlil ještě jednou česky, byl bych rád :-P.
Joker
Profil
Ugo:
jak si já představuji tvojí třídu vracející data, tak také neotestuje nic
Testováním samozřejmě nemyslím to, že tam naplním data a pak se podívám jestli tam jsou.
Spíš budu volat metody nějak měnící stav objektu a pak koukat, jestli nový stav objektu odpovídá očekávání. Často se do dělá automatizovaně, například:
$user = $zdrojdat->GetUserInfo(1);
$user->GrantPrivilege(PRIV_ADMIN);
$test = $user->HasPrivilege(PRIV_ADMIN);
assert('$test == false');
$user->RemovePrivilege(PRIV_ADMIN);
$test = $user->HasPrivilege(PRIV_ADMIN);
assert('$test == true');
Dál můžu testovat i tak, že prostě otevřu třeba profil uživatele a koukám, jestli jsou tam vyplněné správné informace.

S tím původním návrhem bych si musel založit testovací databázi, vyplnit nějaká testovací data, nasměrovat na ni aplikaci, zjistím, že je potřeba poupravit databázi, pak hledám všechny výskyty dané tabuky v kódu, někdy mnohem později zjistím, z 15 výskytů jsem změnil jenom 12… To všechno znám :-)

Ugo:
Co se týká $zdrojdat, tka to jsem pochopil tak, že $this->database nahradím za $this->xml .. opět jsem si nepomohl
Jakto? Když aplikace bude nějak takhle:
$zdroj = new DBUserData();
$kontroler = new UserController($zdroj);
$kontroler->OpenPage($url);

Při změně zdroje dat třeba na XML je třeba v aplikaci udělat jedinou změnu: Vyměnit objekt servisní vrstvy. Samotný model, ani view, ani kontroler, se vůbec nezmění.
$zdroj = new XMLUserData();
$kontroler = new UserController($zdroj);
$kontroler->OpenPage($url);

U toho původního návrhu stejná změna znamená překopat model a kontroler (protože model spoléhá na to, že se mu venku připraví podmínky)

Poznámka, ale to že to tak vidím já neznamená, že to je dogma, docela bych uvítal případnou oponenturu.
Ugo
Profil
ty testy jdou stále mimo mě, bude to tím že nevidím smysl automatických testů když jim stejně musim zadat co z nich má vypadnout :-P spíš bych viděl jako dobrý automatický test jenž by zkoušel nesmyslné hodnoty zda nevyhodí error :) ale to nevadí i tak si myslím že není problém použít třídu jako objekt.. šak to je vlastně jedna instance, jen místo $this je self a člověk se nedostane ke konstruktoru, což u modelu až tak nevadí, jelikož konstruktor dělává nastavení vůči aplikaci a tak je lepší se mu vyhnout (blbě se to zapisuje a psát ještě parent::__construct() je už moc.)

ten zdroj už chápu, není to vlastně zdroj, ale zdroj nad zdrojem, protože třeba v databázové vrstvě nebudu mít stejný metody jako ve třídě pro XML, takže pro obě musim udělat ovladač kterej bude shodnej, což je problém protože dělat v XML hledání a řazení jako má databáze nebude čajíček. Pořád si teda myslim, že i když to takto změníš, tak to prostě nepude :) Další problém je, co když chceš polovinu z DB a polovinu z XML? To je něco kde ti přepisování nezabrání asi žádná implementace a bude to i častější - např. neměný data si vemu z rychlejšího úložiště.

s tím zdrojem mě to celkem dostalo a opravdu přemýšlím že bych přejmenoval svojí proměnnou $db na něco universálního (mít soubory v $db by se mi příčilo)

Takže jestli rozumím, tak pod našimi modely by bylo dobré mít ještě jednu vrstvu která až volá onen "zdroj" - třída database, xml, csv ...

Asi se nad tím ve volné chvíli zamyslím, jestli chceš oponenturu, tak já sem blázen do rychlosti, projekt co teď delám například píšu dotazy ručně, protože skládání skriptem je moc pomalé a přesto jsem se téměř bez obsahu dostal na hodnotu kolem 10ms. Ono řešení které probublává přes 3 třídy bude rozhodně pomalé, špatně dohledatelné pro nově příchozího ke kódu a většinou naprosto zbytečné, navíc obchází to co se snaží nabídnout frameworky kde jsou modely implementované. (není to podle nich, tudíž to nevyužívá jejich funkcionalitu) A závislost na konkrétní implementaci bude stále i když tedy nižší.

Jak nahradit třeba dibi v modelu? Myslím že psaní mezivrstvy která umí ovládat více nesourodých médií je horší neš přepsat modely (které mají mít právě tu funkci že je mám vyměnit za jiné a nic se nerozbije)

btw. jako původní návrh o kterém se bavím je, když v modelu voláš dotazy na třídě - většinou databázové (viz. příklady nette, můj příklad, dibi)

PS. díky za námět k přemýšlení a vysvětlení s trpělivostí :)
Nox
Profil
Ugo:
1) U testů testuješ jak správná data, tak i špatná (má hodit výjimku) a krajní případy. Jasně, napoprvé ti to třeba všechno jede, jenže pak za měsíc něco změníš a aplikace ti záhadně přestane fungovat - a to se se statickým přístupem jak je tu prezentován stane MNOHEM pravděpodobněji
Bez testů pak budeš složitě dohledávat, co se stalo ... s nimi odhalíš, že se tak vůbec děje, třeba hned po té změně. Ne až týden po nasazení do produkce

Až budeš dělat nějaký vážnější projekt, tak tam to bez nich prostě nepůjde

2) "což u modelu až tak nevadí, jelikož konstruktor dělává nastavení vůči aplikaci a tak je lepší se mu vyhnout" - wtf?

3) XML: driver třeba máš v knihovně co používáš (třeba, to je jedno). A i kdyby sis ho napsal - pokud při změně musíš měnit i něco jiného, tak byly ty ovladače špatně napsaný

... doporučuju se kouknout na LINQ - funguje např. v C# a umožňuje to SQL hledání i v objektovém modelu! Takže ne že to nejde

4) Nejsi blázen do rychlosti, jinak bys nepoužíval PHP :-P ale např. Javu (jen na backend), C++, C# nebo C ... a rázem bys jen tím měl třeba 100x rychlejší aplikaci.
Vím že benchmarky by se měly brát opatrně ale... u většiny jsem viděl čísla jako C++ 2ms, PHP 550ms ... to je prostě neskutečný rozdíl

5) Bublání není o moc pomalejší, navíc když všechno naplácáš do jedné třídy, tak to přehlednější vůbec nebude. Kdo má rozumný editor, tak prostě ctrl+klikne a je hned tam.
Naopak rozčlenění do tříd je mnohem přehlednější a flexibilnější

6) "a většinou naprosto zbytečné, navíc obchází to co se snaží nabídnout frameworky kde jsou modely implementované" ???
model implementuje obsah aplikace, to znamená že je to přesně to, co ve frameworku být nemůže

7) "A závislost na konkrétní implementaci bude stále i když tedy nižší." - nebude, existuje totiž něco jako interface, dohledej si

8) "DI statické metody nevylučují." nesmysl - jak předáš statickou třídu? chceš předávat název a vše volat přes $nazev()? Jak předáš dvě různě nakonfigurované závislosti?

9) "Další problém je, co když chceš polovinu z DB a polovinu z XML? To je něco kde ti přepisování nezabrání asi žádná implementace a bude to i častější - např. neměný data si vemu z rychlejšího úložiště."
právě tady použiješ instance a DI a pošleš někam DB a někam XML, zároveň díky společnému interfacu to můžeš prohodit a pokud budou všude dostupná příslušná data, tak se nic nestane
a žádné přepisování nebude třeba

To co propaguješ v podstatě není OOP. Je to procedurální způsob používající globální prvky.

Zkus si někdy udělat něco fakt OOP, mrknout se na testování (teorie na http://misko.hevery.com, základy najdeš něco česky), DI a OOP vůbec
Joker
Profil
Ugo:
ten zdroj už chápu, není to vlastně zdroj, ale zdroj nad zdrojem
Správné slovo je rozhraní. Třída implementuje rozhraní pro načítání uživatele z databáze.
Důležitá věc je, že to neznamená dva způsoby pro přístup k datům! Kontroler je úplně odstíněný od připojení k databázi, používá jen to rozhraní. Jediné co ví (a co potřebuje vědět), že datový zdroj má metodu GetUserInfo($id), která vrátí informace o daném uživateli. Tomuhle se právě říká zapouzdření.

pro obě musim udělat ovladač kterej bude shodnej, což je problém protože dělat v XML hledání a řazení jako má databáze nebude čajíček
Nemusí mít veškerou funkčnost, musí umět jen to, co říká rozhraní. Čili pokud tady máme na rozhraní metodu GetUserInfo($id), musí vrátit uživatele podle id, což XML zvládne (resp. při rozumné struktuře dat, ale totéž platí i pro databázi).
Samozřejmě jednu nevýhodu to má, je tam další mezivrstva a u nějaké technologie se může stát, že „by to bylo mnohem výkonnější, kdyby to rozhraní bylo postavené maličko jinak“.
Jinak ale vybírání a třídění jde implementovat opravdu hodně obecně, viz Noxem zmíněný LINQ.

Asi se nad tím ve volné chvíli zamyslím, jestli chceš oponenturu, tak já sem blázen do rychlosti
Samozřejmě všechny připomínky vítány, ale myslel jsem hlavně z pohledu návrhu a MVC, třeba něco jako: „Máš zbytečně velkou tu servisní vrstvu, já bych XY dal radši do modelu, protože…“ (Mimochodem tenhle problém jsem zmínil výše, že důsledná aplikace tohohle myšlení přesouvá spoustu funkčnosti z modelu do servisní vrstvy a obávám se, že to někdy vede k tomu antivzoru „anemický doménový model“).
Numero1990
Profil
Děkuji, jsem rád, že se tu rozproudila taková diskuze, ale bohužel tomu moc nerozumím, je to sice česky, ale na mě je to až moc akademické a abstraktní.

Dokázal by mi někdo ukázat, jak by například měl vypadat model pro třídu User, která bude mít (pro jednoduchost) pouze jednu veřejnou metodu a to getUserInfo($id). Bude to využívat mysql databázi, konkrétně tabulku users, která obsahuje sloupečky id, name, dateOfBirth a privilege.

Pokud by to šlo, tak ještě kousek kódu, jak by vypadalo volání v controlleru, který obsluhuje požadavek od uživatele viewUser s id 1?

Děkuji.
Ugo
Profil
Nox:
Zkoušel jsem python, výsledek je takový že PHP je mnohem rychlejší, s javou mám špatné zkušenosti, nemám jí rád a taky mi přijde pomalá (pomalejší jak python), ohledně webu je PHP rychlostí vynikající, třeba přístupy do databáze v něm oproti javě nebo pythonu frčí jako drak a to je to co zdržuje většinou asi nejvíc, něco jako hledání prvočísel (to je v pythonu skutečně rychlejší) se asi často nepoužije :)

wtf?
Vypůjčím si dokumentaci CI (i když tohle je v mojí možná roepsaný víc) http://codeigniter.com/user_guide/general/models.html někdo s emusí postarat o to aby existovala proměnná $db, ty říkáš že by se měla předávat přes DI, já myslím že ne, že by si jí mohl framework zajistit sám když se používá pořád, jistě takový model bude nepoužitelný v aplikaci které nepoužívá FW, ale změna bude taková že jí instanci můžu předat v konstruktoru, čili bude tam změna, ale triviální a pokud se vyhnu tomu abych používal předtím v modelu __construct(), dovedu si představit řešení nahrazením v několika souborech najednou, čili úprava na 5 minut. (mohl bych si vypůjčit i něco z diskuse nebo snad i dokumentace nette - konfigurace "DI" v configu)

nebude, existuje totiž něco jako interface
interface znám a jestli jsem někde nepřehlíd větu která by měla být tučná h1, čili že má nějakou funkci, tak nemá žádnou funkci, jen definuje minimální strukturu třídy

Joker:
Nemusí mít veškerou funkčnost
Nevím, mám-li se spolehnout, tak musím to chování alespoň nějak napodobit. koukl jsem na LINQ - pěkná věc, ovšem neřekl jsem že to nejde, ale že to nebude vůbec snadný a za tím si stojím :)

Numero1990:
Za sebe bych to shrnul obecně takto, controller se nestará kde veme model data a model by se zas neměl starat o to jak veme data. Realita je taková že při DI někdo musí vědět odkud model bere data (controller nebo kdo?) jelikož mu musí předat onen zdroj. A model zas musí vědět jak vybrat data - čili na jaké metody se může ptát. Chceš-li vyměnit zdroj z notorm na dibi, musíš přepisovat, minimálně tu vrstvu která se stará o spojení zdroje a modelu.

Řekl bych že tou vrstvou mezi zdrojem by měl být právě model, v praxi u jedné aplikace nepotřebuješ často měnit mezi typově odlišnými zdroji a při nové aplikaci stejně model napíšeš znova

moje představa tvého modelu (nikoliv třídy reprezentující uživatele, to je něco trochu jiného) je tedy

<?php
class user_model {
  function __construct(database $db) {
    $this->database=$db;
  }
  function getUserInfoById($id) {
    /* tady mám problém s kterým si nikdy nevím rady, jak escapovat v query názvy sloupců a tabulek? :-/ */
    $res=$this->database->query("SELECT * FROM users WHERE id=".(int)$id);
    return $res->fetch_assoc();
  }
}

Jednoznačné problémy - nemohu použít XML, název proměnný mi to nedovoluje zrovna tak ono query() - i když tedy existuje způsob jak toho využít, má ale zas malinko jinou syntaxi, tam by bylo dobré tedy použít něco co query teprve sestaví, to platí i pro jiné typy databází - zmíněné escapování je odlišné a i některý zápis SQL je jiný, sqlite třeba neumí vícenásobný insert (btw. nevíte jak se s tím popere DIBI? - budu muset zkusit :))

a hlavně model přímo ví, že z query vyleze objekt kterej má metodu fetch_assoc(), ale už opravdu nevím jak se tomuhle dá vyhnout, když vrátím $res, tak to musí vědět controller, a to já zase vidím raději když controller dostane rovnou hotový data než aby dostával třídu která data teprve reprezentuje.

Z toho jasně plyne, že závislosti na třídách se nezbavíš, i když si uděláš mezivrstvu mezi Database a Model, tak jen přesuneš onen problém s nahrazením zdroje o úroveň jinam, co když budeš chtít použít jinou spojovací vrstvu protože tamta nevyhovuje? - Přepis celé aplikace, stejně tak jako když místo vlastní database použiješ jinou. (bavím se o třídách jenž nemají stejné metody - čili abych použil zajímavější terminologii, neimplemetují stejné interface :) )

V aplikaci bych si pak udělal asi službu která se postará o správné vytvoření modelu a tu volal v controlleru... a sme u CodeIgniteru ;) (či jiného FW) - já totiž nechci v každym controlleru řešit spojení s databází, chci si ho jen nakonfigurovat a používat, když chci použít jiné, nic mi nebrání jej modelu podstrčit, ale defaultní by mu mohl zajistit někdo jiný a nebo on sám ne?


PS. díky Noxi za odstavec o testování, k testům jsem se nepřiklonil, ale konečně jsem pochopil k čemu jsou vyjímky jako takové (praktické využití)
Joker
Profil
Numero1990:
Dokázal by mi někdo ukázat, jak by například měl vypadat model pro třídu User, která bude mít (pro jednoduchost) pouze jednu veřejnou metodu a to getUserInfo($id)
To se demonstruje dost blbě, pokud ta třída má jedinou metodu, kterou bych navíc já osobně dal jinam.
Nebo teda možná si nerozumíme, ale moje představa je, že když mám v modelu třídu User, měla by zajišťovat logiku týkající se uživatele.
Třeba tu kontrolu oprávnění co jsem dal výše. Nebo by tam patřily validace jako „Uživatelské jméno musí obsahovat jen písmena a čísla“, atp.
Metoda GetUserInfo by měla být získání toho objektu User a nejsem si jistý, že patří zrovna k tomu objektu.

Ugo:
Řekl bych, že v 99% případů na tom nezáleží a když už je rychlost tak kritický faktor, že se kvůli ní vyplatí vybrat jazyk, bude stejně nejrychlejší Céčko (resp. Assembler, ale nepředpokládám, že by v něm někdo programoval webovou aplikaci).

K tomu příkladu:
No, přijde mi to spíš jako strukturovaný přístup, čekal bych, že údaje o uživateli budou objekt.
Další problém toho je, že návratová hodnota té funkce metody závisí na datovém modelu databáze- neboli nelze jistě říct, co ta funkce vlastně vrátí. Pokud to použiju způsobem naznačeným v [#3], tzn. výsledek té metody nacpu do view, stává se view přímo závislá na struktuře databáze (přejmenování sloupce v databázi způsobí chybu ve view), což je podle mě špatně; Smyslem MVC je právě to, aby se tohle nedělo.

edit: Špatné názvosloví
Alphard
Profil
Joker:
ale jiné typy databází než MySQL jsou řekl bych podporované daleko víc než dříve.
Obecně s tím nesouhlasím, mám v db třeba pohledy, uložené procedury, indexy (i fulltext)..., v dotazech jsou složité joiny, poddotazy. Systém je pomocí explainů a kontroly logů docela pracně vyladěn pro MySQL, s daným rozvržením co je InnoDB, MyISAM, jsou (snad) vyřešeny problémy se zamykáním tabulek, deadlocky... Představa, že bych měl změnit databázi jen tím, že změním konfigurák je pro mě utopie. Musel bych přepsat celý model.

Tabulkám mohu změnit prefix, jiné přejmenování je pro mě v zásadě nepřijatelné. Je to cena za jednodušší vývoj, ale rozhodl jsem se pro desítky tabulek nevytvářet nějaké struktury, kde je transkripční tabulka mého aliasu pro skutečný název sloupce. Jednou vytvořenou strukturu prostě pokud možno nepředělávám. Pokud už by to jinak nešlo, musel bych asi na úrovni sql dotazu dát aliasy.

docela bych uvítal případnou oponenturu
Mně se zdá, že vše směřuješ na ORM, které mě nechytlo, takže to radši nebudu rozvádět. Přiznám se, že nevím, jaké jsou současné možnosti, ale před několika lety např. Doctrine pokládalo u složitějších věcí tolik dotazů, že by to dělalo asi problémy. Docela souhlasím s kritikou, kterou publikoval Jakub Vrána a ostřeji pak další odpůrci tohoto přístupu. Mapovat relační databáze na objekty je v obecném případě blbost.

Ugo:
blbě se to zapisuje a psát ještě parent::__construct() je už moc
Mně to IDE (NetBeans) generuje v podstatě automaticky. Dědím vždy, když nic detailnějšího, tak Nette\Object.

Takže jestli rozumím, tak pod našimi modely by bylo dobré mít ještě jednu vrstvu která až volá onen "zdroj" - třída database, xml, csv ...
Ano, třeba v Nette je velká část obecných tříd již napsaná, např. Nette\Security\User. Podívejte se na ten odkaz, jsou tam metody pro práci s oprávněním, přihlášením apod. K tomu si už sám implementujete jen tu vrstvu, která vytahá data z vaší db.

tak já sem blázen do rychlosti, projekt co teď delám například píšu dotazy ručně
Je to přehledné a vše správně escapované? Ono je to často docela pracné :-)

a přesto jsem se téměř bez obsahu dostal na hodnotu kolem 10ms
To je dobré, ale otázka je, jak to bude fungovat, až tam bude ten obsah.

Jak nahradit třeba dibi v modelu? Myslím že psaní mezivrstvy která umí ovládat více nesourodých médií je horší neš přepsat modely
Já jsem to určitě nepsal kvůli jiným typům uložišť, ale právě pro eleganci skládání dotazů. Ono je to fakt návykové:
$s = $db->select($cols)->from('users');
if ($role)
{
  $s->where('find_in_set(%s, role)', $role);
}
if ($skilled)
{
  $s->where('date_insert < %d', $skilled);
}
$s->orderBy('name');
return $s;

A teď mohou (pokud jde o podmíny) nastat 4 situace. Ve třech z nich tam musí být právě jednou uvedené where, ve čtvrté být nesmí. Skládat to ručně je docela pracné. O escapování vstupních hodnot nebo o tom, že v až v controlleru přidím $s->limit() ani nemluvím.

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: