konstruktor v modelu
- o5
- Člen | 416
Ahoj, potřebuji vytvořit model, který budou využívat dva moduly. Chtěl bych to nějak takto:
class MyModel extends DibiTableX
{
public function __construct($type)
{
if($type == "typeA")
{
$this->primary = "id_tableA";
$this->name = "tableA";
$this->primaryModifier = "%i";
}
else
//atd
}
}
Ovšem tohle neprojde (chyba níže), protože je to: špatná logika OOP / nette neumožňuje ?
Fatal error: Call to a member function query() on a non-object in /libs/dibi/libs/DibiTableX.php on line 259
Napadlo mě dát do modelu setter ovšem pak by v presenteru muselo být něco jako:
$model = new MyModel;
$model->set("typeA");
místo kratšího :D
$model = new MyModel("typeA");
Editoval o5 (4. 2. 2009 22:01)
- kravčo
- Člen | 721
o5 napsal(a):
Ovšem tohle neprojde (chyba níže), protože je to: špatná logika OOP / nette neumožňuje ?
Fatal error: Call to a member function query() on a non-object in /libs/dibi/libs/DibiTableX.php on line 259
To, či je to zlá logika OOP nechám na teba, Nette to samozrejme umožňuje.
Chyba zjavne popisuje, že voláš query()
na niečom, čo nie
je objekt.
Typicky sa toto môže stať napríklad v prezenteri:
class MyCoolPresenter extends MyBasePresenter
{
protected $model;
public function actionWhatever()
{
$model = new MyCoolModel('typeA');
} # $model sa zahodí
public function renderWhatever()
{ # tu je $model nová premenná
$this->template->result = $model->getResult();
}
}
keď namiesto $this->model
píšeš len $model
.
Chyba samozrejme môže byť i inde, no zjavne ak model vytvoríš a potom na
ňom voláš query()
, v tom momente to objekt nie je. V takýchto
prípadoch často pomôžu Notice:-ky.
- _Martin_
- Generous Backer | 679
Ahoj, potřebuji vytvořit model, který budou využívat dva moduly.
A co kdybys vytvořil dva modely? Třeba ModelTypeA
a
ModelTypeB
a následně volal
$model = new ModelTypeA;
Pokud jsi se ovšem pro nepoužití dvou modelů nerozhodl záměrně…?
Ovšem tohle neprojde (chyba níže), protože je to: špatná logika OOP / nette neumožňuje ?
Ani jedno, ani druhé, chyba pravděpodobně vyskakuje proto, že jsi přepsal původní konstruktor, který (mimo jiné) zajišťuje získání připojení k databázi (viz. API reference).
- kravčo
- Člen | 721
_Martin_ napsal(a):
o5 napsal(a):
Ovšem tohle neprojde (chyba níže), protože je to: špatná logika OOP / nette neumožňuje ?
Ani jedno, ani druhé, chyba pravděpodobně vyskakuje proto, že jsi přepsal původní konstruktor, který (mimo jiné) zajišťuje získání připojení k databázi (viz. API reference).
Presne tak. Tiež treba čítať celé chybové hlášky, nie len prvú časť ako ja :-)
A samozrejme vždy volať rodičovský konštruktor:
class MyModel extends DibiTableX
{
public function __construct($type)
{
parent::__construct();
if($type == "typeA")
{
$this->primary = "id_tableA";
$this->name = "tableA";
$this->primaryModifier = "%i";
}
else
//atd
}
}
- PetrP
- Člen | 587
kravco napsal(a):
Presne tak. Tiež treba čítať celé chybové hlášky, nie len prvú časť ako ja :-)
A samozrejme vždy volať rodičovský konštruktor:
class MyModel extends DibiTableX { public function __construct($type) { parent::__construct(); if($type == "typeA") { $this->primary = "id_tableA"; $this->name = "tableA"; $this->primaryModifier = "%i"; } else //atd } }
Spíš bych to dělal obraceně, tedy nejdříve nastavoval ->name a pak volal rodičovský konstructor, protože se v něm vola DibiTableX::setup(), které ->name a další defaultně nastavuje (když nejsou):
class MyModel extends DibiTableX
{
public function __construct($type)
{
if($type == "typeA")
{
$this->primary = "id_tableA";
$this->name = "tableA";
$this->primaryModifier = "%i";
}
else
//atd
parent::__construct();
}
}
- David Grudl
- Nette Core | 8218
No, raději jsem z příkladů třídu DibiTable odstranil, ať to nesvádí k jejímu používání.
Totiž příklad Akrabat měl být co nejvíce 1:1 transformací Zendovského příkladu do Nette, jenže to asi nebyl úplně dobrý nápad, protože jsem použil techniky, které nepovažuju za nejlepší. Pro začátek jsem se tedy zbavil DibiTable a nejspíš asi dám pryč i variantu „old“, nebo ji alespoň přesunu mimo distribuci. Samozřejmě ještě musím znovuzprovoznit verzi „forms“, která háže chybu kvůli chybějícímu parametru v šabloně.
- Patrik Votoček
- Člen | 2221
David Grudl napsal(a):
No, raději jsem z příkladů třídu DibiTable odstranil, ať to nesvádí k jejímu používání.
Totiž příklad Akrabat měl být co nejvíce 1:1 transformací Zendovského příkladu do Nette, jenže to asi nebyl úplně dobrý nápad, protože jsem použil techniky, které nepovažuju za nejlepší. Pro začátek jsem se tedy zbavil DibiTable a nejspíš asi dám pryč i variantu „old“, nebo ji alespoň přesunu mimo distribuci. Samozřejmě ještě musím znovuzprovoznit verzi „forms“, která háže chybu kvůli chybějícímu parametru v šabloně.
Můžu se tě zeptat proč je nepovažuješ za dobrý nápad? Osobně taky používám DibiTable ale ne přímo. Jak to používám já bude asi jasnější z příkladu.
<?php
class Test extends DibiTableX
{
protected static $all;
public static function getTestByName($name)
{
self::loadAll();
foreach (self::$all as $value)
{
if ($value->name == $name)
return $value;
}
return FALSE;
}
public static function getTestById($id)
{
self::loadAll();
if (empty(self::$all[$id]))
return FALSE;
return self::$all[$id];
}
public static function getAll()
{
self::loadAll();
return self::$all;
}
protected static function loadAll()
{
if (empty(self::$all))
{
$cache = Environment::getCache();
if (isset($cache['Test']))
self::$all = $cache['Test'];
else
{
$test = new Test();
$res = $test->findAll();
if ($res->count() > 0)
{
foreach ($res->fetchAll() as $value)
self::addTest($value->id, $value->name);
}
$cache->save('Test', self::$all, array('expire' => time() + 60 * 60, ));
}
}
}
protected static function addRole($id, $name)
{
return self::$all[$id] = new TestItem($id, $name);
}
public function insert($data)
{
$this->connection->query('INSERT INTO %n', $this->name, '%v', $this->prepare($data));
self::reloadCache();
return $this->primaryAutoIncrement ? $this->connection->insertId() : NULL;
}
public static function reloadCache()
{
self::$all = NULL;
$cache = Environment::getCache();
unset($cache['Test']);
self::loadAll();
}
}
class TestItem extends Object
{
public $id;
public $name;
public function __construct($id, $name)
{
$this->id = (int)$id;
$this->name = (string)$name;
}
public function delete()
{
$test = new Test();
$test->delete(array('id' => $this->id));
Test::reloadCache();
}
}
?>
To že načítam všecho je dobře protože předem vím že budu potřebovat všechny položky… A pak někde dál můžu potřebovat zobrazi třeba jenom jednu a tak si uchovam v paměti všechny… A to že mám další svoje statické metody uvnitř modelu dohromady s dibi table vim neni to nejidealnejsi mohl bych dibi table mit ve jako class Text a metody ktery nad ni volam mit třeba v classu TestFilter… Ale takhle mě to příde pohodlnější… A tak se ptám je použití DibiTable (DibiTableX) špatné řešení? Pokud ano tak proč?
- PetrP
- Člen | 587
David Grudl napsal(a):
No, raději jsem z příkladů třídu DibiTable odstranil, ať to nesvádí k jejímu používání.
vrtak-cz napsal(a):
A tak se ptám je použití DibiTable (DibiTableX) špatné řešení? Pokud ano tak proč?
Mě by spíše zajímalo které řešení je správné, tedy jestli se už něco začalo připravovat, nebo jestli se rodí alespoň nějaký nápad a kdy nás s ním Davide seznámíš.
Předpokládám že řešení typu: přejít na jinou sql knihovnu nebude záhodno.
Téma nepatří do Nette, ale spíš do Dibi fóra.
- nAS
- Člen | 277
Jenom bych vypíchnul jeden Davidův příspěvek na dibifóru: https://forum.dibiphp.com/…iewtopic.php?…
- PetrP
- Člen | 587
nAS napsal(a):
Jenom bych vypíchnul jeden Davidův příspěvek na dibifóru: https://forum.dibiphp.com/…iewtopic.php?…
O tom právě vím, jen mě zajímalo jestli se to už nějak hnulo.
OT: předpokládám nASi že se uvidíme na trpasliconu ;], jestli to není jen záměna nicku (pochybuji). Zajímalo by mě zda se tam nechystá více příznivců Nette. Mohli by sme si třeba chvilku sednout na pivo či vínečko a pomlouvat Davida. ;]
- nAS
- Člen | 277
OT: předpokládám nASi že se uvidíme na trpasliconu ;], jestli to není jen záměna nicku (pochybuji). Zajímalo by mě zda se tam nechystá více příznivců Nette. Mohli by sme si třeba chvilku sednout na pivo či vínečko a pomlouvat Davida. ;]
Sejít se nad kávou či pivem, tedy nad jedem dle chuti, a pomlouvat Davida zní jako dobrý plán, ale já se tou dobou zrovna rekreuju na horách, takže letos se bude trpasliconit beze mě.
- David Grudl
- Nette Core | 8218
vrtak-cz napsal(a):
Můžu se tě zeptat proč je nepovažuješ za dobrý nápad? Osobně taky používám DibiTable ale ne přímo. Jak to používám já bude asi jasnější z příkladu.
Přesněji řečeno, nepovažuju za dobrý nápad to používat v examples, které může někdo brát dogmaticky. Může snadno vzniknout dojem model = tabulka, ačkoliv ve skutečnosti model > tabulka.
PetrP napsal(a):
Mě by spíše zajímalo které řešení je správné, tedy jestli se už něco začalo připravovat, nebo jestli se rodí alespoň nějaký nápad a kdy nás s ním Davide seznámíš.
Správných řešení je rovnou celá řada. I DibiTable pro potřeby malého příkladu je dostačující a legitimní řešení. Pro větší aplikace experimentuju s DibiDataSource, případně lze použít i DibiFluent.
Shrňme si, jak by to mělo fungovat:
- model vrátí (pomocí libovolně složitého SQL) nějakou tabulku dat
- view rozhodne, které sloupce, v jakém řazení a výřezu vlastně chce
Tuhle činnost lze krásně rozdělit mezi oba právě zmíněnou třídou DibiDataSource.
Příklad použití (vyžaduje poslední verzi dibi). Mějme v modelu metodu, která vrátí všechny články, které může aktuální uživatel vidět.
class Model
{
function getArticles(...)
{
// 1) $this->connection je DibiConnection, např. z dibi::getConnection()
// 2) místo $this->connection->query(...) použijeme stejným způsobem dataSource(...)
return $this->connection->dataSource('SELECT * FROM table1 INNER JOIN table 2 ... WHERE ... GROUP');
}
}
Presenter přidá stránkování:
class MyPresenter
{
public function renderDefault($page)
{
$articles = $this->model->getArticles(...);
// přidáme nějakou dodatečnou podmínku
$articles->where('date < %d', time());
$paginator = new Paginator;
$paginator->itemsPerPage = 40;
$paginator->itemCount = count($articles); // vrací celkový počet článků - pozn. ekvivalent k $articles->count()
$paginator->page = $page;
// omezíme datasource na LIMIT a OFFSET
$articles->applyLimit($paginator->length, $paginator->offset);
// chyba, smazano: $articles->where('date < %d', time());
$this->template->articles = $articles;
}
}
Šablona si řekne, o které sloupce má vlastně zájem a může nastavit ještě řazení:
<h1>Articles</h1>
{? $articles->select(array('id', 'title', 'subtitle')) }
{? $articles->orderBy('title') }
{foreach $articles as $article}
<h2>{$article->title}</h2>
<p><a href="{link read $article->id">{$article->subtitle}</a></p>
{/foreach}
Klíčové je, že se provede jen jeden dva SQL
dotazy, které žádají jen ta data, která se budou
potřebovat. Zároveň model není nijak zatížen prezentační logikou,
nezajímá ho stránkování.
Dobrý, ne?
- Jan Tvrdík
- Nette guru | 2595
David Grudl napsal(a):
Klíčové je, že se provede jen jeden (!) SQL dotaz, který žádá přesně jen ta data, která se budou potřebovat. Zároveň model není nijak zatížen prezentační logikou, nezajímá ho stránkování.
Dobrý, ne?
Awesome! :)
- Villem
- Člen | 19
Dávám si za domácí ukol prozkoumat DibiDataSource a DibiFluent. Já jsem
podobné problémy řešil založením pohledu v DB s dotazem
SELECT * FROM table1 INNER JOIN table 2 ... WHERE ... GROUP
ale
toto vypada ještě mocněji.
Jenom by mě zajímalo jak dokážeš získat celkový počet záznamů a zárověn výsledek se všemi limity v jednom dotazu?
- LuKo
- Člen | 116
Davide, buď umíte kouzlit, nebo se mám ještě hodně co učit. Dost
možná obojí ;-) Mozek se mi stále cyklí na poněkud převráceném pořadí
řádků kódu. Nejdříve spočítáte počet všech záznamů
count($articles)
(z tohoto chápu, že musíte naplnit celé pole,
abyste mohl spočítat počet řádků). A až následně omezíte vrácené
záznamy přes where a limit. Z toho mi vyplývá, že by muselo být pole
naplněné celou tabulkou a pak přes cykly a podmínky se zahazovaly
nepotřebné řádky. Ale takhle by to zase dělala lama největší, nikoli
David Grudl. Tuším správně, že v nevinném count($articles)
je skryt nějaký fígl, který skočí do budoucnosti a mrkne na
sql_calc_found_rows později omezeného dotazu? Protože jinak to asi jedním
dotazem do databáze udělat nepůjde.
- Villem
- Člen | 19
LuKo napsal(a):
Davide, buď umíte kouzlit, nebo se mám ještě hodně co učit. Dost možná obojí ;-) Mozek se mi stále cyklí na poněkud převráceném pořadí řádků kódu. Nejdříve spočítáte počet všech záznamů
count($articles)
(z tohoto chápu, že musíte naplnit celé pole, abyste mohl spočítat počet řádků). A až následně omezíte vrácené záznamy přes where a limit. Z toho mi vyplývá, že by muselo být pole naplněné celou tabulkou a pak přes cykly a podmínky se zahazovaly nepotřebné řádky. Ale takhle by to zase dělala lama největší, nikoli David Grudl. Tuším správně, že v nevinnémcount($articles)
je skryt nějaký fígl, který skočí do budoucnosti a mrkne na sql_calc_found_rows později omezeného dotazu? Protože jinak to asi jedním dotazem do databáze udělat nepůjde.
Nene. Právě zkoumám DibiDataSource a vidím, že to jsou v ukázce dotazy
dva (jak podotkl Jod). První SELECT count(*) ...
spočítá
všechny řádky původního dotazu (toho kterým je inicializován DataSource)
a druhý potom vybere data z DB pomocí všech limitů a řazení až
v okamžiku volání cyklu v šabloně (což je ona vychvalovaná
vlastnost).
Editoval Villem (6. 2. 2009 15:35)
- ViliamKopecky
- Nette hipster | 230
Legen… wait for it …dary!
Je to luxus ať už tam SELECT count(*)
je nebo neni :)
- LuKo
- Člen | 116
Villem napsal(a):
Nene. Právě zkoumám DibiDataSource a vidím, že to jsou v ukázce dotazy dva (jak podotkl Jod). První
SELECT count(*) ...
spočítá všechny řádky původního dotazu (toho kterým je inicializován DataSource) a druhý potom vybere data z DB pomocí všech limitů a řazení až v okamžiku volání cyklu v šabloně (což je ona vychvalovaná vlastnost).
Jedna záhada zdá se být odhalena. Pak jsou tu ale další dvě. Jakou
technikou se count($articles)
převede na
$articles->count();
? Druhá je pak dodatečná podmínka
uvedená v ukázce
$articles->where('date < %d', time());
– tímto získám
jinou množinu výsledků, než jsem spočítal. Tím pádem se mi vygeneruje
špatný počet odkazů paginatoru. Neměla by tedy být tato dodatečná
podmínka spíš nad count($articles)
. Toto na tom celém nemohu
rozlousknout.
- Jan Tvrdík
- Nette guru | 2595
LuKo napsal(a):
Jakou technikou se
count($articles)
převede na$articles->count();
?
Pomocí rozhraní Countable, které DibiDataSource implementuje.
- brablc
- Člen | 6
Jan Tvrdík napsal(a):
LuKo napsal(a):
Jakou technikou se
count($articles)
převede na$articles->count();
?Pomocí rozhraní Countable, které DibiDataSource implementuje.
Na prvni pohled musi byt volani count($articles)
nejen pomalejsi
nez $articles->count()
(nutnost detekce rozhrani) ale musi nutne
vest ke zmateni prumerneho ctenare. Objektove volani metody by bylo jasne vsem
i bez toho, abychom museli vedet, zda je interface Countable implementovan.
- Jan Tvrdík
- Nette guru | 2595
brablc napsal(a):
Na prvni pohled musi byt volani
count($articles)
nejen pomalejsi nez$articles->count()
(nutnost detekce rozhrani) ale musi nutne vest ke zmateni prumerneho ctenare. Objektove volani metody by bylo jasne vsem i bez toho, abychom museli vedet, zda je interface Countable implementovan.
Používej to jak chceš. David jen ukázal jednu z cest.
- Villem
- Člen | 19
LuKo napsal(a):
(…) Druhá je pak dodatečná podmínka uvedená v ukázce
$articles->where('date < %d', time());
– tímto získám jinou množinu výsledků, než jsem spočítal. Tím pádem se mi vygeneruje špatný počet odkazů paginatoru. Neměla by tedy být tato dodatečná podmínka spíš nadcount($articles)
. (…)
To by stejně nepomohlo. DibiDataSource je napsaný tak, že
count()
vrací vždy počet řádků dotazu, kterým byl DS
inicializován. V uvedené ukázce bude v opravdu paginátor vracet
nesprávné výsledky. Na myšlence to ale nic nemění. Jenom je třeba počet
řádků do paginátoru dostat jinak (_toString()
vrací aktuálně
platný dotaz; zavolat na něj SELECT count(*)
je až směšně
jednoduché).
Editoval Villem (6. 2. 2009 17:50)
- LuKo
- Člen | 116
Villem napsal(a):
To by stejně nepomohlo. DibiDataSource je napsaný tak, že
count()
vrací vždy počet řádků dotazu, kterým byl DS inicializován. V uvedené ukázce bude v opravdu paginátor vracet nesprávné výsledky. Na myšlence to ale nic nemění. Jenom je třeba počet řádků do paginátoru dostat jinak (_toString()
vrací aktuálně platný dotaz; zavolat na nějSELECT count(*)
je až směšně jednoduché).
Mně spíš šlo o to, zda tak, jak je to v příkladě napsané, paginator vypíše správný počet příspěvků podle až později definované podmínky. Zda tam není nějaký skrytý fígl.
- ViliamKopecky
- Nette hipster | 230
LuKo napsal(a):
Mně spíš šlo o to, zda tak, jak je to v příkladě napsané, paginator vypíše správný počet příspěvků podle až později definované podmínky. Zda tam není nějaký skrytý fígl.
Tak to ale ani nemá fungovat, první sql query má vybrat všechny zobrazitelné záznamy (pokud uživatel některé nemá mít možnost vidět, tak tam nemají být) – z těch se vypočítá počet. Ty pozdější podmínky už určují spíše co se zobrazí a neovlivňují původní počet pro paginaci
- _Martin_
- Generous Backer | 679
enoice napsal(a):
Tak to ale ani nemá fungovat, první sql query má vybrat všechny zobrazitelné záznamy (pokud uživatel některé nemá mít možnost vidět, tak tam nemají být) – z těch se vypočítá počet. Ty pozdější podmínky už určují spíše co se zobrazí a neovlivňují původní počet pro paginaci
Je to přesně tak, jak říkáš – a právě proto v tom (logicky) jiní hledali nějaký fígl, neboť takovýto postup rozhodí stránkování. Správně by se stránkování muselo provádět až nad konečnou (všemi podmínkami ořezanou) sadou záznamů.
- ViliamKopecky
- Nette hipster | 230
_Martin_ napsal(a):
enoice napsal(a):
Tak to ale ani nemá fungovat, první sql query má vybrat všechny zobrazitelné záznamy (pokud uživatel některé nemá mít možnost vidět, tak tam nemají být) – z těch se vypočítá počet. Ty pozdější podmínky už určují spíše co se zobrazí a neovlivňují původní počet pro paginaci
Je to přesně tak, jak říkáš – a právě proto v tom (logicky) jiní hledali nějaký fígl, neboť takovýto postup rozhodí stránkování. Správně by se stránkování muselo provádět až nad konečnou (všemi podmínkami ořezanou) sadou záznamů.
Nevím, zda-li si dobře rozumíme. Já obhajuji Davidův postup, myslím, že je správný. Nejsem si jistý jestli se opravdu shodujeme :)
- LuKo
- Člen | 116
enoice napsal(a):
Nevím, zda-li si dobře rozumíme. Já obhajuji Davidův postup, myslím, že je správný. Nejsem si jistý jestli se opravdu shodujeme :)
Davidova myšlenka DataSource je geniální, o tom není sporu. Pouze hledám skrytý fígl v podle mě nevhodně umístěné dodatečné podmínce. Zkusím doplnit komenty:
class MyPresenter
{
public function renderDefault($page)
{
/** vybere určitou množinu článků, celkem jich je 45 **/
$articles = $this->model->getArticles(...);
$paginator = new Paginator;
$paginator->itemsPerPage = 40;
$paginator->itemCount = count($articles); // vrací celkový počet článků (45) -> paginator vygeneruje odkazy na stránky 1 a 2
$paginator->page = $page;
// omezíme datasource na LIMIT a OFFSET -> omezíme na prvních 40 článků
$articles->applyLimit($paginator->length, $paginator->offset);
// a ještě přidáme nějakou dodatečnou podmínku -> této podmínce však vyhoví jen 38 článků
$articles->where('date < %d', time());
/** v šabloně se zobrazí 38 článků a paginator vygeneruje slepý odkaz na stránku 2 **/
$this->template->articles = $articles;
}
}
Už si trochu rozumíme, s čím vás tu celou dobu prudím? :-) Ikdyž mi to připadá jako chybně umístěná dodatečná podmínka (měla by být před definicí paginatoru), hledám v tom skrytý Davidův úmysl, jehož genialitu stále nejsem s to pobrat ;-)
- _Martin_
- Generous Backer | 679
enoice napsal(a):
_Martin_ napsal(a):
enoice napsal(a):
Tak to ale ani nemá fungovat, první sql query má vybrat všechny zobrazitelné záznamy (pokud uživatel některé nemá mít možnost vidět, tak tam nemají být) – z těch se vypočítá počet. Ty pozdější podmínky už určují spíše co se zobrazí a neovlivňují původní počet pro paginaci
Je to přesně tak, jak říkáš – a právě proto v tom (logicky) jiní hledali nějaký fígl, neboť takovýto postup rozhodí stránkování. Správně by se stránkování muselo provádět až nad konečnou (všemi podmínkami ořezanou) sadou záznamů.
Nevím, zda-li si dobře rozumíme. Já obhajuji Davidův postup, myslím, že je správný. Nejsem si jistý jestli se opravdu shodujeme :)
Aha, tak to se zřejmě neshodneme =) Můžeš víc rozebrat, proč si myslíš, že je onen postup správný? Mě osobně nepřijde logické, pokud mám tabulku s výpisem záznamů, nad ní odkazy Strana 1 2 a ten druhý vypíše to samé (resp. nic, nezkoumal jsem detailně API), protože všech záznamů je 20, stránkování je po 15ti a následná podmínka to ořeže na 10.
- _Martin_
- Generous Backer | 679
LuKo napsal(a):
Už si trochu rozumíme, s čím vás tu celou dobu prudím? :-) Ikdyž mi to připadá jako chybně umístěná dodatečná podmínka (měla by být před definicí paginatoru), hledám v tom skrytý Davidův úmysl, jehož genialitu stále nejsem s to pobrat ;-)
Aj, byl jsi rychlejší =)
- ViliamKopecky
- Nette hipster | 230
Ale tomuhle problému se samozřejmě vyhnete, když přidáte podmínku před count, myslím že spíš chtěl David ukázat že samotný SELECT se provede až při iteraci.
Podmínka po COUNT(*) asi nenajde časté využití, ale někomu se může hodit.
Nevím jestli to bude dobrý příklad, ale v soutěži bude třeba prvních deset míst a budeme chtít zobrazit pouze ženy, které se umístily v první desítce – a deset žen umístěných na prvních místech.
DibiDataSource není pouze na ulehčení paginace, ale i na jiné věci, příklad jen ukazoval jak je efektivní, že (nepočetní) SELECT vykoná pouze jednou.
- Villem
- Člen | 19
LuKo napsal(a):
Už si trochu rozumíme, s čím vás tu celou dobu prudím? :-) Ikdyž mi to připadá jako chybně umístěná dodatečná podmínka (měla by být před definicí paginatoru), hledám v tom skrytý Davidův úmysl, jehož genialitu stále nejsem s to pobrat ;-)
Je v tom jeden fígl. A ten že count($datasource)
vrátí
stejný výsledek at už před tím zavoláš
$datasource->where(...)
s jakým koliv dotazem. Proto je uplně
jedno v které části si necháš spočítat počet řádků. A proto možná
ostatní nechápou co na tom nechápeš ty :).
- ViliamKopecky
- Nette hipster | 230
Villem napsal(a):
enoice napsal(a):
Ale tomuhle problému se samozřejmě vyhnete, když přidáte podmínku před count, (…)
Právě že ne. Podívej se na můj předchozí příspěvek, nebo do kodu DibiDataSource. Count se počítá z původního doatazu, kterým byl DS inicializován.
Áha, tak nad tím by se mělo zauvažovat.
- _Martin_
- Generous Backer | 679
Villem napsal(a):
Právě že ne. Podívej se na můj předchozí příspěvek, nebo do kodu DibiDataSource. Count se počítá z původního doatazu, kterým byl DS inicializován.
Vida, to z toho uvedeného příkladu není moc patrné. Příklad se stránkováním v tomhle případě asi nebyl nejlepší praktickou ukázkou. A ani mě nenapadá, k čemu by se dala dodatečná podmínka využít, když nelze zjistit počet řádků – touto podmínkou omezeného – dotazu.
enoice napsal(a):
Nevím jestli to bude dobrý příklad, ale v soutěži bude třeba prvních deset míst a budeme chtít zobrazit pouze ženy, které se umístily v první desítce – a deset žen umístěných na prvních místech.
Tomu moc nerozumím… můžeš to trochu rozvést?
Editoval _Martin_ (7. 2. 2009 14:50)
- ViliamKopecky
- Nette hipster | 230
_Martin_ napsal(a):
enoice napsal(a):
Nevím jestli to bude dobrý příklad, ale v soutěži bude třeba prvních deset míst a budeme chtít zobrazit pouze ženy, které se umístily v první desítce – a deset žen umístěných na prvních místech.
Tomu moc nerozumím… můžeš to trochu rozvést?
Pro paginaci je to asi logicky nevyužitelné. Ale třeba je taková situace, že budu mít celkový počet soutěžících třeba 45, v první desítce se umístilo 6 mužů a 4 ženy. V logice aplikace bude určen výpis jen oné první desítky, a pak bude šablona, která si bude sama určovat, jestli chce zobrazit všech deset, nebo jen muže (6), nebo jen ženy (4). Kdyby se tato podmínka zahrnula do countu a až později do limitu zobrazení, pak by se zobrazovalo bud 10 lidí, nebo 10 mužů, nebo 10 žen.
Asi to je ale opravdu poněkud špatný příklad. Metafory a ukázkové příklady nejsou mou silnou stránkou ;)
- LuKo
- Člen | 116
Villem napsal(a):
Je v tom jeden fígl. A ten že
count($datasource)
vrátí stejný výsledek at už před tím zavoláš$datasource->where(...)
s jakým koliv dotazem. Proto je uplně jedno v které části si necháš spočítat počet řádků. A proto možná ostatní nechápou co na tom nechápeš ty :).Právě že ne. Podívej se na můj předchozí příspěvek, nebo do kodu DibiDataSource. Count se počítá z původního doatazu, kterým byl DS inicializován.
Ano, toto se tu již objevilo a rozumím tomu. Mně prostě nešlo do hlavy, proč se v šabloně pracuje s jiným počtem (dodatečně omezenou množinou) článků, než na jaký je vypočítané stránkování (podle inicializovaného DS bez dodatečné podmínky). O to samé šlo asi i Martinovi. Enoice uvedl příklad, který je, dle mého názoru, srozumitelnější.
- Villem
- Člen | 19
DibiDataSource je jak už název napovídá rozhraní ke zdroji dat. V této konkrétní implementaci se snaží vytvořit vrstvu k tabulce jako výsledku databázového dotazu. Svým určením je koncipován jako výstup z modelu.
Proto není jeho ukolem zvladat presentační logiku, pouze pro ni získává data. Navíc nemůže být na tuto logiku nijak vázán. Představte si jednoduchý CMS, kde přijde požadavek na zobrazení seznamu užvatelů. Výstupem z modelu je DataSource, kde každý řádek obsahuje informace o uživately jako jméno, přezdívku, počet článků, jestli je on-line, prostě výsledek posbíraný z mnoha tabulek v DB. O tom, které kontrétní uživatele je nutné vypsat ale rozhoduje presenter.
A tady se dostávám k tomu where. Model vůbec nemusí vědět, že uživatel chtěl zobrazit pouze uživatele daným počtem, příspěvků a navíc ho zajímá pouze jeho stav. A nebo si nechal zobrazit informace o nějakém konktrétním uživately.
Navíc takto koncipovaný model je unverzálnější, protože nad jedním datasource je možné postavit spoustu ruzných tiskových sestav.
Možná, že použitý příklad je lehce zavádějící, ale to co kolem toho David napsal je dle mého dostatečně vysvětlující.
- David Grudl
- Nette Core | 8218
Villem napsal(a):
Jenom by mě zajímalo jak dokážeš získat celkový počet záznamů a zárověn výsledek se všemi limity v jednom dotazu?
Jasně, napsal jsem to špatně, dotazy jsou ve skutečnosti dva.
- David Grudl
- Nette Core | 8218
LuKo napsal(a):
Jedna záhada zdá se být odhalena. Pak jsou tu ale další dvě. Jakou technikou se
count($articles)
převede na$articles->count();
? Druhá je pak dodatečná podmínka uvedená v ukázce$articles->where('date < %d', time());
– tímto získám jinou množinu výsledků, než jsem spočítal.
Správná připomínka, v DibiDataSource je bug, metodu count upravím. Na spočítání samotného zdroje přidám asi getTotalCount().
- Tomik
- Nette Evangelist | 485
pmg napsal(a):
Mocné a přitom jednoduché řešení, gratuluji. Také koukám, že už se dají dělat zanořené transakce a DibiFluent je integrováno do DibiTable. Děkuji za tato příjemná vylepšení. Dovoluje mi to zapomenout na Doctrine, ze které se mi dnes udělalo zle.
OT: Zdravím! :) Tak vidíš, vše je již vyřešeno. :)
- pmg
- Člen | 372
Když už OT, tak pořádně. Jak jsem mluvil o těch čtyřech dotazech pro
uživatelské řazení, zapomněl jsem, že už jsem se to pokoušel řešit
právě přes DibiTable
, ale kvůli několika problémům jsem to
řešení nepoužil. Nešla mi např. zanořit transakce.
Vytvořil jsem si pro tento účel abstraktní SortableTable
,
která měla volitelně zohledňovat kategorii, ve které položka byla. Jedna
metoda pro ukázku.
/**
* Inserts new row at the end of its category.
*/
public function insert($data)
{
dibi::begin();
try {
$query = dibi::select('IFNULL(MAX([sort_id]), 0) + 1')
->from($this->name);
if ($this->category) {
$query->where("[$this->category] = %u", $data[$this->category]);
}
$data['sort_id'] = $query->forUpdate()->execute()->fetchSingle();
$result = parent::insert($data);
} catch (Exception $e) {
dibi::rollback();
throw $e;
}
dibi::commit();
return $result;
}
Feature Requests
Doctrine třeba umí relace, takže se dá napsat
$category->Articles
a tabulka se sama připojí podle
definovaného vztahu. K tomu je potřeba poznamenat jednu věc: ve vztahu M : N
je málokdy potřeba spojovat všechny tři tabulky, takže ten join nebývá
tak složitý.
Dibi by se ale mohlo naučit vztah rozpoznávat a doplňovat automaticky
podle výčtu položek.
$category->select('name, count(Articles)')
by podle nastavených
konvencí přidalo
join('articles_categories ON category.id = articles_categories.category')
,
a pokud bych v dotazu chtěl jiné než souhrnné informace, tak ještě
join('articles ON articles_categories.article = articles.id')
. Typ
vztahu by se dal detekovat automaticky podle existence spojovací tabulky (její
třídy) nebo by šlo vztahy vyčíst přímo z databáze podle indexů a
restrikcí.
Bylo by potom ještě dobré zařídit, aby se v dotazu vytvořeném přes
DibiTable
doplnily prefixy položek podle názvu výchozí tabulky.
Takže by pak fungovalo
CategoryTable->select('name, Articles.name')
, aniž by
docházelo ke kolizím. Stačilo by si při vytvoření dotazu původní tabulku
pamatovat.
Ještě mě napadlo, že stejně jako
$model->fetchByCategoryIdAndVisibility()
by mohlo jít magicky
volat $articles->selectInfoAndExtra()
, což by zkombinovalo
výstup metod selectInfo
a selectExtra
, které by
vrátily nějaký dotaz, nebo dosadilo přímo název sloupce. Takto by šlo
snadno zvolit určitou skupinu sloupců nebo kombinovat přednastavené
podmínky. Co vy na to?
- Villem
- Člen | 19
pmg napsal(a):
Doctrine třeba umí relace, takže se dá napsat
$category->Articles
a tabulka se sama připojí podle definovaného vztahu. K tomu je potřeba poznamenat jednu věc: ve vztahu M : N je málokdy potřeba spojovat všechny tři tabulky, takže ten join nebývá tak složitý.Dibi by se ale mohlo naučit vztah rozpoznávat a doplňovat automaticky podle výčtu položek.
$category->select('name, count(Articles)')
by podle nastavených konvencí přidalojoin('articles_categories ON category.id = articles_categories.category')
, a pokud bych v dotazu chtěl jiné než souhrnné informace, tak ještějoin('articles ON articles_categories.article = articles.id')
. Typ vztahu by se dal detekovat automaticky podle existence spojovací tabulky (její třídy) nebo by šlo vztahy vyčíst přímo z databáze podle indexů a restrikcí.
Dělat tohle za běhu aplikace není v 99 případech ze sta nutné. Proto se domnívám, že režie s tím spojená by byla obrovská. Může se to hodit při vývoji aplikace, abys nemusel přepisovat dotazy, ale na to se hodí nejaký externí prográmek, který projde tvoje zdrojáky dotazy doplní před tím, než se do toho pustí php.