konstruktor v modelu

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
o5
Člen | 416
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

_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
  }
}
o5
Člen | 416
+
0
-

diky kravco :-)

Editoval o5 (13. 2. 2009 11:17)

PetrP
Člen | 587
+
0
-

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 | 8082
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

Jenom bych vypíchnul jeden Davidův příspěvek na dibifóru: https://forum.dibiphp.com/…iewtopic.php?…

PetrP
Člen | 587
+
0
-

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
+
0
-

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 | 8082
+
0
-

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?

jasir
Člen | 746
+
0
-

Tak to je teda pecka! Nádhera! Díky moc!

romansklenar
Člen | 655
+
0
-

To mi úplně uniklo :) vypadá to mocně!

Jan Tvrdík
Nette guru | 2595
+
0
-

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
+
0
-

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?

Jod
Člen | 701
+
0
-

To budú asi selecty dva :) , asi tým myslel, že to selectovanie obsahu je len jeden select a dá sa s tým pekna pracovať.

nAS
Člen | 277
+
0
-

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í.

Moc pěkné!

LuKo
Člen | 116
+
0
-

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
+
0
-

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é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.

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
+
0
-

Legen… wait for it …dary!

Je to luxus ať už tam SELECT count(*) je nebo neni :)

LuKo
Člen | 116
+
0
-

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
+
0
-

LuKo napsal(a):

Jakou technikou se count($articles) převede na $articles->count();?

Pomocí rozhraní Countable, které DibiDataSource implementuje.

brablc
Člen | 6
+
0
-

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
+
0
-

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.

sairon
Člen | 32
+
0
-

Tak koukám, že můžu můj vlastní datasource zahodit a předělat modely. Ale stejně díky, je to geniální ;)

Villem
Člen | 19
+
0
-

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íš nad count($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)

Tomik
Nette Evangelist | 485
+
0
-

Davide, díky! :)

LuKo
Člen | 116
+
0
-

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ěj SELECT 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
+
0
-

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
+
0
-

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
+
0
-

_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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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 :).

Villem
Člen | 19
+
0
-

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.

ViliamKopecky
Nette hipster | 230
+
0
-

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
+
0
-

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
+
0
-

_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
+
0
-

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
+
0
-

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 | 8082
+
0
-

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 | 8082
+
0
-

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().

pmg
Člen | 372
+
0
-

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.

Tomik
Nette Evangelist | 485
+
0
-

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
+
0
-

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
+
0
-

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ř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í.

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.