Jak napsat fci pro count()?
- boneofsin
- Člen | 25
Zdravím,
potřebuji pomoci s definicí fce pro počet vítězví hráče v tabulce her. Tabulka ‚game‘ obsahuje sloupce: ID, date, number, dis_id, player_one, player_two, win(id hráče), round.
Takže jak napsat funkci která mi vrátí počet – kolikrát je ID určitého hráče ve sloupci WIN?
Používám Repository jako v tutorialu „první aplikace“ kde pro hry mám pak GameRepository ve kterém nejspíš tuto fci budu definovat. Následně ve výpisu všech hráčů budu u každého chtít zobrazit právě jeich počet vítězství.
- Šaman
- Člen | 2666
Jestli používáš abstraktní repository z QS, tak:
<?php
$wins = $this->findBy(array('win' => $userId))->count();
?>
Píšu to z hlavy, tak kdyby ti vyskakovala laděnka, tak to doladíme. Ale princip je jasný – vyselectíš si všechny řádky které tě zajímají a na výsledku zavoláš count().
- David Matějka
- Moderator | 6445
jo, do dokumentace by se mela napsat velka cervena cedule varujici pred
pouzitim ->count()
bez parametru :)
- Šaman
- Člen | 2666
enumag napsal(a):
@Šaman: Nebylo by to lepší takhle?
$wins = $this->findBy(array('win' => $userId))->count('*');
Proč? Oboje mi vrací správný výsledek, stejný počet dotazů. Podle API bych řekl že to není špatně a jinde jsem o tom nic v dokumentaci nenašel.
Editoval Šaman (24. 4. 2013 11:23)
- enumag
- Člen | 2118
@Šaman: Protože bez té * se zbytečně tahají
data i když je potřeba pouze jejich počet. Prostě to udělá ten select a
pak spočítá kolik řádků databáze vrátila. Když předáš * nebo třeba
id, tak to počítá databáze pomocí agregační funkce.
https://api.nette.org/…ion.php.html#…
Editoval enumag (24. 4. 2013 11:35)
- boneofsin
- Člen | 25
vyzkoušim… díky pánové =)
do gamerepository jsem to uvedl takto
GameRepository
public function countWin($userID)
{
return $this->findBy(array('win' => $userId))->count('*');
}
Presenter
public function renderDefault()
{
$this->template->wins = $this->gameRepository->countWin($userId);
}
Když v presenteru definuji $userID (např. 1) tak to funguje. Nicméňě chtěl bych tabulku – výpis tabulky hráčů (player) a vedle každého jména pak jejich počet vítězství z tabulky game.
Editoval boneofsin (24. 4. 2013 13:04)
- Šaman
- Člen | 2666
enumag napsal(a):
@Šaman: Protože bez té * se zbytečně tahají data i když je potřeba pouze jejich počet. Prostě to udělá ten select a pak spočítá kolik řádků databáze vrátila. Když předáš * nebo třeba id, tak to počítá databáze pomocí agregační funkce.
https://api.nette.org/…ion.php.html#…
U mě to vygeneruje pokaždé stejný dotaz:
<?php
dump($this->books->findByTag('historie')->count());
//dump($this->books->findByTag('historie')->count('*'));
//dump($this->books->findByTag('historie')->count('id'));
?>
<script>
SELECT COUNT(*)
FROM `book`
INNER JOIN `book_tag` ON `book`.`id` = `book_tag`.`book_id`
INNER JOIN `tag` ON `book_tag`.`tag_id` = `tag`.`id`
WHERE (`tag`.`text` = 'historie')
</script>
//edit: Koukám že používám YetORM, ještě to musím vyzkoušet na čistém QS.
//edit2: Jo a Nette je 2.1dev někdy ze začátku března.
Editoval Šaman (24. 4. 2013 13:51)
- Šaman
- Člen | 2666
boneofsin napsal(a):
Tak ted sem uplně mimo teda =o).. takže tu fci v game repository ani v presenteru potřebovat nebudu… a při výpisu hráčů v player defaul.latte akorát přidám sloupec do tabulky kde vložím related… jak jsi uvedl ty?
Použiješ, prostě si v presenteru (i když tohle je typická úloha pro
servisní vrstvu/fasádu modelu) načteš všechny uživatele, které budeš
chtít mít v tabulce a potom si pro každého (ten foreach
)
zavoláš tuhle metodu, které předáš Id
aktuálního
uživatele. To related
je interní fce mapperu
(v QS je ale mapper v repository) a do presenteru ani do šablony
nepatří.
Kapitánův deník 201304241405, dodatek :)
Budeš si muset vytvořit vlastní datovou strukturu (asociativní pole), ve které ta data předáš šabloně. Dokonce to asi bude vícerozměrné pole (budeš mít jedno pole, které pod klíčem $userId bude obsahovat druhé pole, ve kterém budeš mít všechny hodnoty co budeš chtít o uživateli vypsat.)
Ten kód od @enumag tě od tohoto odstíní za cenu, že v šabloně
používáš to related
, což je „skoro SQL“. Není to
akademicky čisté, ale v tomto případě praktické.
Další varianta, také ne úplně čistá, by bylo předat šabloně
$gameRepository
a při vypisování sloupce win
tento
repository použít. Já bych šel asi cestou tohoto kompromisu, ale oba dva
postupy se nebudou líbit kodérovi, který pak nebude šabloně rozumět.
Editoval Šaman (24. 4. 2013 14:10)
- boneofsin
- Člen | 25
ok, zkusim to dát ráno nějak dohromady..
V presenteru Player mám výpis všech hráčů z tabulky player a funguje: $playerRepository + renderDefault (findAll)… a připíšu k tomu tedy $gameRepository a do renderDefault pod findAll také připíšu ten (countWin), který je definován v GameRepository. A následně pak v šabloně použiju related.
už to chápu správně?
Editoval boneofsin (24. 4. 2013 20:06)
- Šaman
- Člen | 2666
No, když použiješ related
, nepotřebuješ
$gameRepository
. Ten count se ti spočítá přímo z objektu
$player
.
Tento způsob připomíná ORM. Klidně ho používej, dokud nezjistíš proč
to není dobře. Protože tohle zjistíš až pokud bys měl hráče třeba
nejen v db, ale i v memcache, nebo v jiném nerelačním úložišti. A tou
dobou budeš už i vědět, jak se s tím porvat.
- boneofsin
- Člen | 25
Věřím, že spousta lidí má určitě jasno co tím chtěl básník říci, ale nechápu to jak získá PlayerPresenter, který šabloně zajišťuje data pro výpis všech hráčů, přístup k databázové tabulce „game“ a sloupci „win“ (kde je id uživatele) kde spočítá počet vítězsví každého hráče.
Já mám za to že přístup k určité tabulce je definován právě názvem repositáře – tedy PlayerRepository (je tabulka player) atd.. V tom případě může sosat data pouze z tabulky player, ne? Takže si myslím že v PlayerPresenter musí být přeci zmínka o $gameRepository a jeho renderu a v GameRepository pak funkce pro count..???
- Šaman
- Člen | 2666
Právě že activeRow si umí natahat navázané objekty (data z tabulek)
pomocí cizích klíčů sama. Tím se obchází potřeba všechno řešit přes
repository. Je to praktické, jednoduché, ale maličko nečisté (nemůžeš si
vytvořit entitu, která nemá vazbu na tabulku). A této vlastnosti právě
využil @enumag, když ti ukázal konstrukci
$player->related('wins')->count('*');
.
Pokud bys chtěl všechno řešit přes repository, tak bys to měl daleko
pracnější a vycházel bys z toho, co jsem popsal já. Nataháš si pole
uživatelů, ke každému dohledáš pomocí jiného repository jeho výhry a
pak to nějak rozumně předáš šabloně.
Jinak to traverzování mezi tabulkama je základní vlastnost NDb –
třeba pokud každý hráč bude mít vazbu na team, tak můžeš použít
intuitivní $player->team->name
, aniž bys řešil
TeamRepository
.
Editoval Šaman (26. 4. 2013 16:58)
- boneofsin
- Člen | 25
Přez cizí klíč se z tabulky Player do tabulky Game nedostanu, protože klíče mám pouze v tabulce Game odkazující na Player (nevím jestli to jde i zpětně)… Nicméňe zkouším to tedy přez repository a vypadá to takto:
GameRepository
public function countWin()
{
$wins = $this->findBy(array('win'))->count('*');
}
PlayerPresenter
class PlayerPresenter extends BasePresenter {
/**@var Ball\PlayerRepository */
/**@var Ball\GameRepository */
private $playerRepository;
private $gameRepository;
protected function startup()
{
parent::startup();
$this->playerRepository = $this->context->playerRepository;
$this->gameRepository = $this->context->gameRepository;
}
public function renderDefault()
{
$this->template->players = $this->playerRepository->findAllPlayers();
$this->template->wins = $this->gameRepository->countWin();
}
}
Player – default.latte
{block content}
<table>
<thead>
<tr>
<th>Jméno</th>
<th>Příjmení</th>
<th>Vítězství</th>
</tr>
</thead>
<tbody>
{foreach $players as $player}
<tr>
<td>{$player->fname}</td>
<td>{$player->lname}</td>
<td>{$player->related('wins')->count('*')}</td>
</tr>
{/foreach}
</tbody>
</table>
{/block}
A ve výsledku to pak hodí chybu:
Nette\Database\Reflection\MissingReferenceException
No reference found for $player->related(wins).
Když se podívám do arguments:
3. …\temp\cache\_Nette.FileTemplate\_Player.default.latte-c470a58fc1b824fab26dcabed8b3278c.php:27
source ► Nette\Database\Table\ActiveRow→ related (arguments ▼)
$key „wins“ (4)
4 – by odpovídalo počtu vítězství právě vypisovaného hráče…
Aha koukám že ta funkce v Presenteru na to vliv vůbec nemá, když jí zakomentuju.. To related teda projede všechny tabulky, když mrknu do laděnky Nette\Database
Editoval boneofsin (27. 4. 2013 14:17)
- Šaman
- Člen | 2666
Ach jo. Motáš dvě věci. Funkci countWin()
z repository,
kterou máš špatně napsanou, ale je tam úplně zbytečně. A počítání
pomocí related, které máš v šabloně.
Pro začátek zakomentuj/vymaž countWin()
a její volání
v Presenteru.
Pokud ti pak bude chyba vyskakovat dál, tak sem postni strukturu db.
Zajímá nás pouze provázanost tabulek Player
a
Game
.
Editoval Šaman (27. 4. 2013 14:28)
- boneofsin
- Člen | 25
Zkusil jsem ještě
{foreach $players as $player}
<tr>
<td>{$player->fname}</td>
<td>{$player->lname}</td>
{foreach $player->related('game', 'win')->count('*') as $win}
<td>{$win}</td>
{/foreach}
</tr>
{/foreach}
ale to je zas chyba: Invalid argument supplied for foreach()
No nic tady je SQL
CREATE TABLE IF NOT EXISTS `player` (
`id` int(4) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(4) unsigned NOT NULL,
`fname` varchar(20) NOT NULL,
`lname` varchar(20) NOT NULL,
`birth` year(4) NOT NULL,
`foto` varchar(100) NOT NULL DEFAULT 'http://www.seznam.cz',
PRIMARY KEY (`id`),
KEY `fk_user` (`user_id`),
CONSTRAINT `fk_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `game` (
`id` int(4) unsigned NOT NULL AUTO_INCREMENT,
`date` date NOT NULL,
`number` int(2) unsigned NOT NULL,
`dis_id` int(4) unsigned NOT NULL,
`player_one` int(4) unsigned NOT NULL,
`player_two` int(4) unsigned NOT NULL,
`win` int(4) unsigned NOT NULL,
`round` int(3) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `players` (`player_one`,`player_two`,`win`),
KEY `discipline` (`dis_id`),
CONSTRAINT `player_one` FOREIGN KEY (`player_one`) REFERENCES `player` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `player_two` FOREIGN KEY (`player_two`) REFERENCES `player` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `player_win` FOREIGN KEY (`win`) REFERENCES `player` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `discipline` FOREIGN KEY (`dis_id`) REFERENCES `discipline` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- boneofsin
- Člen | 25
už to frčí… yeahhhhh
{foreach $players as $player}
<tr>
<td>{$player->fname}</td>
<td>{$player->lname}</td>
<td>{$player->related('game','win')->count('*')}</td>
</tr>
{/foreach}
Díky pánové za trpělivost.. Stejně se budu muste někdy pokusit o tento výsledek pomocí repository =o/, protože až tento výpis budu chtít seřadit podle počtu výtězství budu muset někam zadat ->order(…), a to asi za použití tohoto způsobu nepude… Nebo se opět mýlim?
Editoval boneofsin (27. 4. 2013 15:38)
- Šaman
- Člen | 2666
Ještě si můžeš to počítání vítězství přesunout do Entity
<?php
public function getWins()
{
return $this->row->related('game','win')->count('*'); // možná nebude potřeba to row, teď nevím
}
?>
a pak v šabloně psát jen $player->wins
– budeš mít
čitelnější šablonu a to počítání jen na jediném místě. Tomu ORDER BY
to ale nepomůže, leda že bys řadil již instancované entity – pak bys je
mohl podle této funkce sortovat.
- boneofsin
- Člen | 25
nene mám základ Nette, bez doplňků… takže to nepude teda.. neva.. Jinak díky za podporu..
Ještě otázka mimo count().
Je toto stejné:
{foreach $games as $game}
<td>blabla</td>
<td>blabla</td>
<td><a href="{plink Score:detail $game->related('score','id')}">detail</a></td>
</foreach>
jako toto?
{foreach $games as $game}
<td>blabla</td>
<td>blabla</td>
{foreach $game->related('score', 'id') as $score}
<td><a href="{plink Score:detail $score->id}">detail</a></td>
{/foreach}
{/foreach}
Ten druhý příklad jsem upravil na podobu uvedenou v prvním kodu, abych nemusel {foreach} používat 2×.. Bude mít odkaz stejnou podobu?