Narůstání metody („Where“) při použití NotORM – Fluent

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

Dobrý den,

V nette jsem úplně nový, takže počítám že toto bude způsobeno nejspíše nevědomostí, ale
chtěl bych se Vás dotázat na cyklování podmínky " where " při skládání dotazů SQL.

Mějme tabulku v databázi MySQL, která řeší vykreslování hodnot (jmen) do kartézké soustavy souřadnic XY, o následujících sloupcích:

id (int, 11) PrimaryKey, AutoIncrement;
x (int, 11);
y (int, 11);
name (varchar, 250);

Dále mějme v Nette Frameworku 2.0.10 (PHP: 5.3.6) model s následující metodou:

<?php
	public function getAll($table) {
            return $this->db->table("jmeno_tabulky");
	}
?>

Řekněme tedy, že existuje uživatelské rozhraní pro editaci jmen ve výše uvedené tabulce, které vykreslí do kartézké soustavy souřadnic právě tolik textových polí, kolik odpovídá uživatelem zadanému rozměru, která jsou zárověn pojmenována dle svých souřadnic ve formátu X_Y.

Pro provedení hromadné editace výše popsané tabulky jsem pomocí cyklu foreach zformátoval data přijatá z uživatelského rozhraní do následujícího pole:

<?php
	$data[$x][$y] = $name;
?>

A do dalších dvou polí jsem si vložil všechny souřadnice X a Y:

<?php
	$x_souradnice[] = array("x1", "x2", ..."xn");
	$y_souradnice[] = array("y1", "y2", ..."yn");
?>

Dále zjišťuji jejich přítomnost v tabulce složením celkem jednoduchého dotazu:

<?php
	$data_z_tabulky = $this->model->getAll("table")->where(array("x" => $x_souradnice, "y" => $y_souradnice))->fetchAll();
?>

Což později procházím cyklem foreach, neexistující vkládám, existující porovnám a případně upravím, jenže tu nastává problém. Vzhledem k tomu, že jsem v Nette nenašel implementaci MySQL „CASE“ jediné co mne napadlo byl výše zmíněný cyklus foreach, ve kterém upravuji řádek po řádku takto:

<?php
	$this->model->getAll("tabulka")->where(array("x" => $value->x, "y" => $value->y))->update(array("name" => $data[$value->x][$value->y]));
?>

A tu nastává problém. Provede-li se cyklus foreach více než 1× (tedy je-li upravena více-než jedna buňka) zůstává ve WHERE uloženo předchozí nastavení a k němu se přidává nové, např.

První provedení:
Where x=1 AND y=2

Druhé provedení:
Where x=1 AND y=2 AND x=2 AND y=3

a takto to pokračuje až do ukončení cyklu. K tomuto jsem našel jediné řešení jak to „obejít“ a to je pomocí klonování.

Naklonuji si $this->model->getAll(„tabulka“) do libovolné proměnné na začátku cyklu, poté k tomuto klonu doplním where + update a poté tento klon unsetem zničím. Workaround který funguje.

Setkal se někdo z Vás s něčím obdobným? Máte nějaké lepší řešení? Není to chyba ve frameworku?

P.S. Koukám že jsem se trochu rozepsal ale doufám že to bude pochopitelné.

Za všechny nápady / přípomínky děkuji.

Prochy20

//Edit: oprava chyb…

Editoval Prochy20 (18. 4. 2013 14:49)

Prochy20
Člen | 13
+
0
-

BUMP…

Nikdo neví ?

petr.pavel
Člen | 535
+
0
-

Ahoj Prochy,
předně: ty používáš Nette Database, aka NDB, což není NotORM. NotORM jako takové moc lidí nepoužívá, tak proto ti možná nikdo neodpověděl.

Přijde mi divné, že by takto implementovaný getAll() způsoboval problém, který popisuješ. Mě se to stává u odkazovaných tabulek v NotORM (NDB nepoužívám), ale u přímého dotazu do tabulky ne. Pochybuji, že by v tom NDB bylo jiné.

CASE použít klidně můžeš, aspoň v NotORM ano. Nevidím důvod, proč by v NDB fungovat neměl.

$data = array(
  'status' => new NotORM_Literal(
    "CASE status ".
    "WHEN '".self::STATUS_REGISTERED_AWAITING."' THEN '".self::STATUS_REGISTERED."' ".
    "WHEN '".self::STATUS_SPONSOR_AWAITING."' THEN '".self::STATUS_SPONSOR."' ".
    "ELSE status ".
    "END"
  ),
);
$this->db->tournament_player('tournament_id', $tournamentId)->update($data);

Vidím, že se na tabulku odkazuješ $this->model, což naznačuje, že data ukládáš přímo v metodě formuláře/presenteru. Čistší by to bylo šoupnout do modelu, do nějakého ulozSouradnice($x, $y), které by případně ani getAll() volat nemuselo, a když už, tak $this->getAll(). Příznivci jemnějšího členění zodpovědnosti objektů by ti ještě doporučili vyčlenit zvlášť repository, což mě ale u malých aplikací přijde jako overkill.

Prochy20
Člen | 13
+
0
-

Ahoj Petře,

jak jsem psal jsem v Nette nový a z toho důvodu se objevilo to „nedorozumění“ s NotORM a Nette Database,
za což se omlouvám :) .

Odkazované tabulky (cizí klíče) samozdřejmě používám (i v této tabulce se nachází) ale tuto tabulku volám přímo, takže ty můžeme vyloučit.

Co se týče Tvého doporučení rozumím dobře, že bych měl v modelu vytvořit metodu pro update (která by klidně mohla využít metody getAll() a dále ji parametrizovat parametry co a kde (jinými slovy $data & $where) ?

Nejsem si totiž úplně jist zda-li toto nezpůsobí znovu ten samý problém (tedy budu-li tuto metodu volat cyklicky)? Neboť po delší rozvaze zde s kolegy jsme vydedukovali že použití cyklického volání where bude vhodnější (složit jeden dotaz s CASE v tomto případě pravděpodobně asi nepůjde).

Jinak tato aplikace bude pravděpodobně větších rozměrů, a tedy se vyčlenění repository jeví jako dobrý nápad (který pravděpodobně zrealizuji).

Každopádně za tvou pomoc děkuji,

Prochy20.

petr.pavel
Člen | 535
+
0
-

metoda pro update: Moje doporučení vyčlenit kód z presenteru/formuláře do modelu neřešilo problém, se kterým jsi chtěl poradit. To jsem jen tak na okraj utrousil moudro. :-)
Máš pravdu, že když volání getAll() vy smyčce způsobuje problém, tak její volání jinde bude způsobovat ten samý problém.

Ten skutečný viník nejspíš bude v $this->db. Můžeš sem, prosím, hodit, jakým způsobem tuhle vlastnost plníš?

Prochy20
Člen | 13
+
0
-

Ahoj,

k metodě pro update: udělám to jak radíš… bude to určitě čistější :)

a co se týče $this->db, plním pomocí anotace:

	/** @var Nette\Database\Connection @inject */
	public $db;

Editoval Prochy20 (24. 4. 2013 8:35)

petr.pavel
Člen | 535
+
0
-

Člověče, tak to už netuším. To $this->db je podle mě v pohodě.
(Až na to, že bys s databází měl pracovat z repository nebo modelu, a ne z presenteru, ale to nesouvisí s tím narůstáním where.)

Prochy20
Člen | 13
+
0
-

No právě, tohle je to čemu se divím…

Každopádně, ten přesun do modelu určitě zpracuji (popř. do repository až si o tom něco načtu :) ).

Otázka je, zda-li tohle není způsobeno nějakou interní nedokonalostí Nette (protože jsme došli k závěru, že na mé straně chyba není), což nám může zodpovědět pravděpodobně jen pan Grudl.

Každopádně řešení (workaround) které jsem nastínil výše (s klonováním objeku) funguje a nebudu tedy od něj upouštět i když se mi zrovna 2× nelíbí…

Prochy20.

Editoval Prochy20 (24. 4. 2013 12:14)

David Matějka
Moderator | 6445
+
0
-

vynikem nebude $this->db ale $this->model, volani $this->model->getAll("tabulka") ti totiz urcite vraci stejnout instanci \Nette\Database\Table\Selection

Editoval matej21 (24. 4. 2013 12:26)

Prochy20
Člen | 13
+
0
-

Mohu se zeptat, jak se tomu dá přejít / jak tomu lze zamezit?

David Matějka
Moderator | 6445
+
0
-

omlouvam se, kus kodu jsem v prvnim prispevku preskocil.. model by mel snad byt ok.. ale ujisti se, ze

$this->model->getAll("tabulka")->where(array("x" => $value->x, "y" => $value->y))->update(array("name" => $data[$value->x][$value->y]));

volas v kazde iteraci foreache a ze to nedelas takhle:

$table = $this->model->getAll("tabulka");
foreach($values as $value) {
	    $table->where(array("x" => $value->x, "y" => $value->y))->update(array("name" => $data[$value->x][$value->y]));
}
Prochy20
Člen | 13
+
0
-

Žádný problém…

Pravděpodobně bude zakopaný pes tu, protože:

V konstruktoru si do privátní vlastnosti

/** @var \Nette\Database\Table\Selection */
private $table;

uložím

$this->table = $this->model->getAll("tabulka");

a to poté volám v cyklu foreach

$table->where(array("x" => $value->x, "y" => $value->y))->update(array("name" => $data[$value->x][$value->y]));

Vzhledem k předchozímu příspěvku předpokládám, že chyba nastává při ukládání privátní vlastnosti $table čehož když se vyvaruji bude vše OK.

Jediné věci ale nerozumím: Důvod proč přistupuji k databázi pomocí privátní vlastnosti $table je ulehčení práce s databází, neb v databázi mám tabulek vícero (se kterými pracuji v tomto presenteru (výběr dat, etc…)) a proto pro každou z nich mám takto nadeklarovanou vlastnost… Je toto špatně? Mělo by se to dělat jinak?

Platí obecně zásada že veštkerou komunikaci s db nechat v modelu a v presenteru zpracovávat pouze datasety poskytnuté modelem ?

Děkuji,

Prochy20.

Editoval Prochy20 (24. 4. 2013 12:47)

David Matějka
Moderator | 6445
+
0
-

Nette\Database\Table\Selection je objekt pro jeden dotaz. (max pro dva – treba u strankovani, ale to ted resit nebudu :))

nejlepsi bude, kdyz si pro kazdou tabulku (respektive pro kazdou, pro kterou to ma smysl) vytvoris repository, treba ve stylu quickstartu https://doc.nette.org/cs/quickstart

hAssassin
Člen | 293
+
0
-

@Prochy20 > uvedom si co ale delas. Tvuj model (metoda getAll($table) z prvniho prispevku) ti vraci Nette\Database\Table\Selection a to si ulozis do privatni promenny, coz je spatne, protoze kazde nove where() se vaze k dany Selection a proto (jak uz bylo receno) se ti to kumuluje a kumulovat se ti to bude, protoze mas porad ve $this->table tu stejnou Selection.

Takze za 1) doporucuji mrknout do API!

Za 2) ano, clonovani Selection je jedna moznost (ale bacha kdy klonujes, protoze podminky se uchovavaji, cili klonuj pred where)

Za 3) to udelat nejak takto (psano z hlavy):

foreach($values as $value) {
	$result = $this->model->getAll("tabulka")->where(array("x" => $value->x, "y" => $value->y);
	foreach($result as $row) {
        	$row->update(array("name" => $data[$value->x][$value->y]));
	}
}

A za 4) skutecne to neni chyba Nette, ale ficura ;-)

Prochy20
Člen | 13
+
0
-

Aha,

rozumím, udělám to tak…

Děkuji za všechny odpovědi :)

Prochy20.