Jak napsat fci pro count()?

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

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

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

enumag
Člen | 2118
+
0
-

@Šaman: Nebylo by to lepší takhle?

$wins = $this->findBy(array('win' => $userId))->count('*');
David Matějka
Moderator | 6445
+
0
-

jo, do dokumentace by se mela napsat velka cervena cedule varujici pred pouzitim ->count() bez parametru :)

Šaman
Člen | 2666
+
0
-

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

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

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)

enumag
Člen | 2118
+
0
-

Zjednodušeně takto (musíš si to rozkouskovat co patří do šablony, co do modelu etc.):

$players = $connection->table('player');
foreach ($players as $player) {
	echo $player->name;
	echo $player->related('wins')->count('*');
}
boneofsin
Člen | 25
+
0
-

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?

Šaman
Člen | 2666
+
0
-

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

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)

enumag
Člen | 2118
+
0
-

@Šaman: Samozřejmě, YetORM tohle řeší.

Šaman
Člen | 2666
+
0
-

enumag napsal(a):

@Šaman: Samozřejmě, YetORM tohle řeší.

Jj, máš pravdu, že v čistém QS to načítá data. Nikdy jsem si toho nevšiml. Díky.

boneofsin
Člen | 25
+
0
-

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

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

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

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

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

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

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

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

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

… a kam takovou entitu správně zapsat?

Šaman
Člen | 2666
+
0
-

Máš někde entitu Player, ne? Tak do ní. Pokud teda používáš YetORM. Jestli máš Ndb, tak tam pravé entity nejsou, tam to není kam napsat.

boneofsin
Člen | 25
+
0
-

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?

Šaman
Člen | 2666
+
0
-

Zdá se mi, že v tom hodně lítáš. Foreach je pro iterování nad nějakým polem nebo skupinou záznamů. Pokud má $game jen jedno score, tak ten druhý příklad nedává smysl. Naopak pokud by hra měla více score, tak není možné použít ten první. To ale není Nette, ale základy PHP.

boneofsin
Člen | 25
+
0
-

máš pravdu nefunguje to… ke každé hře náleží dva řádky z tabulky score… Jo začínám s PHP, tak se neni čemu divit… =o/