Práce s entitama v Database\Context

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

Měl jsem ten nápad jak napsat správně entitu a zároveň aby sem s ní mohl pracovat. Co jsem koukal, tak asi k tomu je potřeba nějaký Mapper. Může mi tu někdo ve stručnosti poradit co a jak? popř. nakopnout správným směrem?

Co taková entita by měla správně obsahovat (atributy a metody)?

Mám nastřelenou např. tuto entitu

<?php

namespace Tirus\Entity;

class UserEntity {

    /**
     * @var int  Identificator of user
     */
    protected $id;

    /**
     * @var string User login
     */
    protected $username;

    /**
     * @var string User password
     */
    protected $password;

    public function getId() {
        return $this->id;
    }

    public function getUsername() {
        return $this->username;
    }

    public function getPassword() {
        return $this->password;
    }

    public function setUsername($username) {
        $this->username = $username;
    }

    public function setPassword($password) {
        $this->password = $password;
    }
}

Když se podívám na Model: Entity-Repository-Mapper tak z toho nejsem nějak moc moudrej :(

Editoval Tirus91 (30. 7. 2014 18:30)

hrach
Člen | 1838
+
0
-

Nette\Database nepodporuje injektovani hodnot do tvych entit. Pouzij nejake orm/nadstavbu. Nextras\Orm, YetOrm, Kdyby\Doctrine…

Tirus91
Člen | 199
+
0
-

A co bys doporučil prosím? v tomto jsem celkem lajk a nerad bych něco dělal zbytečně či složitě. Myslel jsem, že bych se vyhnul všemu co není v Nette, protože jakmile jsem do něj dal už jen Dibi, tak mi využití paměti stouplo z 8 na 15MB.

Šaman
Člen | 2666
+
0
-

Zvláštní, teď jsem zkusmo pustil pár projektů, jak na čistém Nette a NDBT, tak nad Dibi a LeanMapperem.
Čisté Nette zabírá podle Tracy okolo 5MB, Dibi + LM 6,5MB.

Jinak NDB(T) skutečné entity nezná, to co vrací je jen chytřejší pole. Není to ORM, je to databázová vrstva.
To, co potřebuješ ty, je ORM, zkus pohledat.
Za sebe doporučuji Dibi a LeanMapper, jeho hlavním kamenem úrazu je v současné době… chvilka napětí… ano, dokumentace. Na to jsme si ale asi už zvykli…

Tirus91
Člen | 199
+
0
-

Šaman: tak zkouším jak si poradil.. no ale problém je v tom, že když do konstruktoru svého repozitáře chci předat instanci \DibiConnection tak mi to zařve

Argument 1 passed to LeanMapper\Repository::__construct() must be an instance of LeanMapper\Connection, instance of DibiConnection given, called in C:\wwwroot\htdocs\nette_tirus_eu\app\FrontModule\presenters\HomepagePresenter.php on line 26 and defined

a jako druhý parametr je IMapper, to v dokumentaci taktéž chybí, takže netuším co s tím…

Jinak, v projektu mám toto:

"require": {
		"php": ">= 5.3.7",
		"nette/nette": "~2.1.0",
                "kdyby/translation": "@dev",
                "dg/texy": "2.4",
                "dg/dibi": "2.1.1",
		"o5/grido": "~2.0",
		"tharos/leanmapper": "2.0.1"
	}

Casem vyhodím Grido, ale v tuto chvíli u mne na localhostu to bere 13.76MB

Editoval Tirus91 (30. 7. 2014 22:23)

Šaman
Člen | 2666
+
+2
-

Nad aktuálním Nette, Dibi a LM mám tenhle projekt, ale už není určený ke studijnim účelům, takže si zkouším experimentovat třeba s adresářovou strukturou a umistěním config souborů. Ukázku konfigurace najdeš tady, samotné pole %database% je v lokálním configu. Mapper a ostatní třídy jsou v /app/model/data/ORM. Ty potřebuješ ale jen ten mapper (ctí stejná pravidla, jako NDB) a pokud chceš lehce magický repository, tak ten (umí vygenerovat magicky getByXxx(), findByFooAndBar() metody, pokud je uvedeš v anotaci). Entity a Collection jsou ve fázi pokusů, skoro bych ti doporučil dědit od originálních LM\Entity (v tom případě v repository zakomentuj hned první metodu createCollection, ta se váže k té mé vlastní Collection).

Druhá možnost je stáhnout LM 1.3, pro kterou je psaná dokumentace. Na základní psaní a vyzkoušení ORM stačí a pokud pak budeš chtít nově přidané (a nezdokumentované) fičury, tak budeš mít už nějakou motivaci k upgrade. Zpětná kompatibilita je poměrně silná, takže přepsat projekt ze starého na nový LM je docela v pohodě.

P.S. Kdyby se v tom projektu snažil rozkoukat, jak jediná větší změna je, že všechny šablony jsou hned u svého presenteru, layout se hledá taky tam pak u všech předků. Následně se tedy mohou přesouvat celé adresáře presenterů i se svými šablonami libovolně kamkoliv, kam vidí RobotLoader. Stará struktura ale funguje taky, takže si to klidně popřesouvej podle zvyklostí v Sandboxu. Stejně tak ty configy můžeš prostě zkopírovat do jednoho.

P.P.S. Jo a lokální config je v ignorelistu, ten si vytvoř překopírováním a přejmenováním z toho example. A v databázi chybí uživatelé, zatím neni jak je přidat. Prostě je přidej v Adminerovi a v authenticatoru zakomentuj kontrolu hesla.

Tirus91
Člen | 199
+
0
-

@Šaman
tak teď si mne dostal.. no to bude horší než sem čekal. Zda to chápu, tak jakmile toto začnu využívat, tak klasické DibiConnection ani nevyužiju?

Editoval Tirus91 (30. 7. 2014 22:58)

Šaman
Člen | 2666
+
0
-

Ne, LM si ho trochu rozšiřuje. Starý LM 1.3 nad ním ale ještě jede.
Není to složité, máš to předkousané. Vezmi ty tři řádky z configu, Mapper a Repository (zakomentuj tu první metodu) a můžeš jet. V projektu máš zároveň ukázku repozitářů a entit.

Tirus91
Člen | 199
+
0
-

@Šaman ok, díky moc, zítra na to mrknu, momentálně jsem si to aktualizoval na Nette 2.2.2 a nahrál klasické poslední Dibi a leanMapper a zbavuji se Kdyby/translate (po odebrání to má už jen 8,5MB v paměti – ach ten žrout).

Mrknu na to. Defakto ani nevadí, když klasické DibiConnection nevyužiju, ale budu už jen využívat LeanMapper\Connection?

A zda to ještě chápu.

Repository – jen obsluhuje to propojení Connection a ostatního
Entity – entita objektu
Mapper – namapuje DB objekt do entity

?

Šaman
Člen | 2666
+
+2
-

Ty to DibiConnection potrebuješ, LM na něm staví. Jenom nevytváříš přímo instanci DibiConnestion, ale mírně rozšířeného potomka. Pokud bys někam jinam potřeboval to DibiConnection, klidně tam předej to LM\Connection.

Repository – práce s db, ale i specializované vyhledávací a filtrovací dotazy. Nad obyčejnou NDBT vypadá třeba takhle.

Entita je plnohodnotná entota z ER diagramu. I ona ale může na pozadí komunikovat s db, když si donačítá vazby (mám-li už načtenou entitu autora, tak volání $author->getBooks() mi položí další dotaz).

Mapper řeší pravidla převodu objektového PHP na konvence relační databáze. Takže výše uvedená vazba je třeba realizována pomocí sloupce id_author v tabulce book. A to ví mapper. Pokud budeš používat jiné konvence, uprav jen mapper, ostatním je to většinou jedno (teda, pokud nevytváříš nějaké nízkoúrovňové hacky – i z repository i z entity teoreticky můžeš volat i čistou query).

Ta moje Collection je kolekce vrácených entit (třeba všech knih autora), originál LM to dřív vracel v poli, dnes nevím, jestli si nezavedl vlastní objektovou kolekci. Upravit se to dá snadno.

Tirus91
Člen | 199
+
0
-

@Šaman tak si troufám říct, že se mi to podařilo pochopit jen na nižší úrovni.

Zkusil jsem si to sepsat do vlastního a jedu zatím tabulky Topic a Article
Když do presenteru dám

use \Tirus\TInjectTopicRepository;
use \Tirus\TInjectArticleRepository;

tak po následném zavolání metody get na articleRepository dostanu Exception

Trait method injectUserRepository has not been applied, because there are collisions with other trait methods on App\Presenters\HomepagePresenter

také v Netbeans to nezná trait ani use v objektu. Co dělám špatně?

Našel jsem to :) špatně jsem si pojmenoval metody v těch Inject repositařích :)

@Šaman prosím jak na findBy ? zkoušel jsem ji předat pole array(‚username=%s‘,‚tirus‘) ale to nefunguje

Editoval Tirus91 (31. 7. 2014 19:09)

Šaman
Člen | 2666
+
+1
-
  1. traity – tohle není nutné, ale mě to přijde přehledné. Obsah traity můžeš překopirovat do Presenteru, nebo, pokud ti nevadí public property, můžeš repozitáře injectovat rovnou přes anotaci @inject. Nesouvisí s LM.
  2. Metodu findBy() si definuji v obecném repository, samo LM také nic takového nezná. Už ale nejsme na úrovni databáze, takže se předává přímo pole findBy(['username'=>'tirus']);. Případně si rovnou v repozitáři vytvoříš zkratku findByUsername($username), pak ti ji to bude i napovídat.

Ještě bych rád podotkl, že to moje Repository vrací u get jediný záznam, nebo NULL (a pokud jich najde víc, tak hlásí chybu) a find vrací kolekci, resp. pole entit, i kdyby byla jen jedna, nebo žádná. Jiná konvence používá třeba find() a findOne(), vyber si co ti vyhovuje. Z toho mého repozitáře stačí vzít jen Mapper a ukázku konfigurace, pak máš čistý LM a nebudou tě mást moje nezdokumentované vychytávky.

P.S. Pomocí magie stačí přidat anotaci a metodu findByUsername($username) můžeš rovnou používat. Jako klíč se bere řetězec za findBy a hodnotu předáš parametrem. Aby to ale nebyla černá magie, tak je vyžadována ta anotace, pomocí které zvládá IDE napovídat a z kódu je jasné, jaké metody ten který repozitář umí zpracovat.

Editoval Šaman (31. 7. 2014 23:01)

Tirus91
Člen | 199
+
0
-

@Šaman
super, už mi to dost frčí :)

jen se tu peru s určitýma ptákovinama, kterýma si myslím že bych to mohl zacyklit.
Mám články, témata a komentáře.
Jak mám napsat entitu aby mi to vybralo k článku i téma i všechny jeho komentáře? Téma jsem dokázal suprově, ale komentáře ne.
Nebo jak mám vyhledat všechny články k danému tématu když nevím ID ale název?

Jak mohu dát podmínku na getBy kde datum začátku je <= aktuálnímu? vždy mi to dává rovno.

Šaman
Člen | 2666
+
+1
-
  1. Podle toho, jak vypadá ER diagram. Jestli se komentáře vážou k článku, tak by to měla být vazba 1..n, tu máš třeba tady a pokud budeš chtít najít i článek podle komentáře, tak popiš i druhou stranu vazby. To už jsi ale v LeanMapperu a to je zdokumentované.
  2. Nezapomeň, že pracuješ objektově. Zapomeň na databázi, jsi na objektové straně ORM. Takže články hledáš přes objekt téma a objekt téma si už podle názvu najdeš. Proto jsou u mě repozitáře i entity prakticky prázdné. Nepotřebuju selectit články podle jména tématu, využívám vazbu články-téma a metody get a find jen pro vrácení základní entity, od které se odpíchneš. Jestli ti to připadá neefektivní, tak věz, že to je cena za možnost pracovat jen s objekty a zapomenout na SQL. A není to tak neefektivní, protože LM dokáže dotazy optimalizovat. Pokud by opravdu bylo potřeba optimalizovat na čas, nebo počet dotazů, pak je lepší psát si repository ručně a nepoužívat ORM. Ale potřeba to není, dokud nepíšeš třeba xchat.

Editoval Šaman (31. 7. 2014 23:39)

Tirus91
Člen | 199
+
0
-

@Šaman
Super :)
díky moc. postupně tomu začnu už asi celkově rozumět.

ještě bych se vrátil k té jedné otázce

Jak mohu dát podmínku na getBy kde datum začátku je <= aktuálnímu? vždy mi to dává rovno.

Editoval Tirus91 (1. 8. 2014 0:06)

Šaman
Člen | 2666
+
+2
-

Napiš si vlastní matodu v repository. Pomocí té mé to neuděláš, protože ta předává do where jen jeden parametr (klidně pole) a ty potřebuješ dva.

<?php
/**
* Vrátí kolekci entit podle …
*
* @param DateTime $date
* @return Collection
*/
public function findOlderThen($date)
{
	$entities = $this->connection->select('*')
		->from($this->getTable())
		->where('[date] <= %d', $date) # nebo jak se to napíše v Dibi fluent
		->fetchAll();

	return $this->createEntities($entities); # tohle jen pokud používáš ty moje kolekce, v čistém LM to tam nepatří
}
?>

Editoval Šaman (1. 8. 2014 0:22)

Šaman
Člen | 2666
+
+1
-

Tak jsem provedl kompletní revizi kódů modelu. Mapper bez problémů, ty ostatní třídy nefungovaly, jak měly. Výsledek je v repozitáři.

Dále jsem napsal jednoduchou extension. Na ní je důležité, že umí zapínat profiler, tedy dumpováni sql dotazů. Pokud ji použiješ, tak pozor – změnil se config.local.neon, uprav ho podle examplu.
Extension umí i nastavit mapper a entityFactory, pokud je uvedeš. tahle část je u mě v configu modelu.

Co se týče toho předávání do where více parametrů, tak to půjde zrealizovat až v PHP 5.6, takže snad brzy. Do té doby to nechám tak, jak je.

Jo a zjistil jsem, že anotace pro magické metody není potřeba, ale důrazně ji doporučuji jednak kvůli napovídání, druhak aby třída netajila své API a za třetí to asi časem dopíšu a pak by magické metody pro které není napsaná anotace přestaly fungovat.

Tirus91
Člen | 199
+
0
-

@Šaman
Nevím co přesněji jsi tam měnil, takže si to jdu porovnat a snad to nebude mít na nic u mne vliv. (co tam nefungovalo jak mělo?)

Co se týče te Extension, tak podobnou jsem si udělal i já. Zkopíroval jsem tu klasickou DIBI a upravil si ji.

Šaman
Člen | 2666
+
+1
-

Měnil jsem:

  • Collection: odstranil jsem _toArray, protože není funkční jak má, a zrušil jsem fetchPairs, kteá se ale může hodit. Nicméně není potřeba ji aktualizovat, stará funguje bez problémů.
  • Entity, Repository: zrušil jsem createCollection, tahle metoda patří do EntityFactory.
  • Vytvořil jsem EntityFactory. Tím se i na aktuálním LM skutečně vytváří arrayObjekt Collection místo obyč pole. Tohle s novým LM nefungovalo.
  • Repository: zrušil jsem simulaci toho, že dědí od Nette\Object. Takže pokud bys v repozitáři používal Nette\Object fičury, tak si je tam zase vrať. Ale pro běžnou práci nejsou nutné a to vykopirování půlky N\Object kódu a vlepení do Repository nebylo vůbec hezké. Teď nemá žádnou závislost na Nette.
  • Extension – přesně, je to vykopírovaná DibiExtension. Není potřeba ji používat, starý ruční přístup funguje v pohodě, ale extension je hezčí a hlavně umí zapnout db profiler.

P.S. Dočasně je v Entity zrušená možnost předávat místo navázané entity její $id. Takže třeba $book->author = 10; Je to proto, že v novém LM mi kód téhle fičury nefungoval. Položil jsem dotaz na DibiFórum, až mi @Tharos odepíše, tak tuhle věc vrátím. Docela ji používám.

Obecně – jestli ti fungoval kód i se včerejšími soubory, bude nejspíš fungovat i dnes, protože jsem zrušil jen věci, které stejně nefungovaly nad aktuálním LM a buď jsem je opravil, nebo prohlásil býti balastem. Ten projekt je už totiž docela starý a ty base třídy pamatují ještě LM 1.3.

P.P.S. Asi udělám LM Sandbox, abych dalším zájemcům usnadnil začátky a nemuseli se prokousávat projektem, co má vlastní strukturu.

Šaman
Člen | 2666
+
0
-

@Tirus91 Další update, tentokrát jen přidává fičury.

  1. Entity opět umí pracovat s id navázanych entit (takže je možné psát $book->author = 1; namísto $book->author = $author;).
  2. Entita i kolekce má metodu toArray() pro přehledné dumpování. Místo rekurzivního vypisování navázaných entit (které by snadno vedlo k zacyklení) se vypisují idčka entit.

To je prozatím všechno, tím považuji třídy v adresáři ORM(*) za vyzkoušené a finální. Připravuji composer balíček, další změny bych směřoval tam.

(*) Odkazuji na nový projekt, Skeleton, který používá vnořené namespace. Původní projekt má všechny své třídy v jediném NS.

P.S. Možná dojde ještě k jedné aktualizaci – teď natvrdo považuji za primární klíč 'id'. Chci to nahradit něčím, co si ten PK zjistí a pak by mohl mít i jiný tvar. API se už ale měnit nebude.

Editoval Šaman (5. 8. 2014 5:33)

Matey
Člen | 142
+
0
-

@Šaman je fajn mať niečo vzorové na ten LM, ďakujem

ale spýtam sa lebo nemám potuchy, ako sa pracuje s tou Collection?

Šaman
Člen | 2666
+
0
-

Collection je jen ArrayObject nad vrácenými daty a nyní navíc umí toArray(). Můžeš si do ni dopsat další metody, třeba podporu řazení, filtrování, nebo jsem tam měl třeba fetchPairs() pro naplnění selectboxů.
Originál LM vrací rovnou pole entit, takže se s výsledkem nedá dělat nic, než jej iterovat.

P.S. Moje toArray() nevytvoří pole entit, ale převede celou Collection i s entitama na čitelné pole vhodné k dumpování.

Kdybys Collection používat nechtěl, tak jen upravíš EntityFactory, resp. použiješ původní.

Matey
Člen | 142
+
0
-

ďakujem za vysvetlenie

pýtal som sa preto, že pri entite Article ($article->toArray()) mi to vracalo mix všetkého možného, tak som myslel že to zle používam, ale chyba bola v tom že som síce do Entity vložil tvoju metodu toArray(), ale nezmenil som v nej alias pre LeanMapper\Entity as LeanEntity

už môžem spokojne plniť editačné formuláre :)

Matey
Člen | 142
+
0
-

@Šaman ako riešiš kontrolu či metoda find* niečo vrátila?

upravil som si tvoju EntityFactory createCollection, je to na tejto úrovni správne?

<?php
/**
 * @param Entity[] $entities
 * @return Collection
 */
public function createCollection(array $entities) {
	return (empty($entities) ? NULL : Collection::from($entities));
}
?>
Šaman
Člen | 2666
+
0
-

Není. Kolekce přece může být prázdná. Jestli chceš testovat, zda v ní něco je, tak si ji countni. Ale i prázdná je iterovatelná, countnutelná, dumpnutelná (fuj, to jsou výrazy). :)

S tím NULL si jen přiděláváš problémy. U Entity je to něco jiného – buď je to entita (objekt), nebo to musí být NULL.

Jinak jestli ještě chvilku počkáš, tak právě připravuji composer balíček, odhadem za hodinu by mohl být venku, už jenom testuji.

Šaman
Člen | 2666
+
+1
-

Takže composer balíček je venku. Zdrojáky jsou na GitHubu.

Pokud používáte composer, stačí přidat řádek:

"require": {
	"saman/leanmodel": "~1.0"
}

Veškeré závislosti na Nette (samozřejmě kromě použití extension) jsou odstraněny, balíček je komentován a anotován anglicky, licence je newBSD. LeanMapper není potřeba v composer.json uvádět, automaticky se přidá nejnovější stabilní kompatibilní. Kaskádově se pak přidá i Dibi, které je kompatibilní s tím LM.

Ukázka použití je třeba v tom Todolistu, u kterého to celé začalo. BC break je nyní nutnost dědit entity a repozitáře od LeanModel\Entity, resp. LeanModel\Repository. Prakticky stačí do všeh entit a repozitářů vložit use sekci. Trochu se změnily i konfigurační soubory a přibyla možnost nastavit $defaultEntityNamespace přímo v configu.

Otázka na Tharose: Nemáš nic proti zvolenému názvu?

ping @Matey, @Tharos

// edit: změněná verze z ~1.0.0 na ~1.0 – budu držet kompatibilitu v rámci jednotek.

Editoval Šaman (11. 8. 2014 23:41)

Matey
Člen | 142
+
0
-

dobrá práca :-) niečo takéto sa bude hodiť, mať lm pripravený pre nette, už mi tam chýba asi len fetchPairs, ktorá sa hodí na select v formulároch

Šaman
Člen | 2666
+
0
-

Já jsem ho zatím zrušil s tím, že teď na tom udělám projekt a uvidím, co ještě přidat. Ale jestli ho taky používáš, tak to asi vrátím. Přes víkend zkusím zjistit, jestli je jiná jednoduchá možnost a když ne, tak to přidám.

Matey
Člen | 142
+
0
-

jasné, zistíš čo ti v tom chýba a na to spolieham, že to bude úprava LM s bonusmi pre použitie v Nette :) ja sa zatiaľ bez fetchPairs obídem, lebo som sa pustil do úpravy toho čo už mám hodne škaredo napísané..

a mám taký dotaz k LM :)
nahodil som si LeanModel a teraz postupne prepisujem to čo už mám hotové z pôvodného LeanQuery(priamo v presenteroch/komponentách)/Repository mixu, niekde som používal LeanQuery podľa Microsite a inde zas *Repository

práve som takéto niečo prehodil do repository Microsite metóda loadPages()

<?php
// v komponente som získaval stránky takto:
$pages = $this->getPages();

/**
 * @return Page[]
 */
private function getPages() {
	return $this->domainQueryFactory->createQuery()
		->select('p')
		->from('App\Domain\Page', 'p')
		->where('p.lang = %s', $this->locale)
		->getEntities();
}

// prerobil som to a presunul získavanie stránok z repository
$pages = $this->pageRepository->findByLang($this->lang);

//repository:
/**
 * @method Page[] findByLang($lang)
 */
class PageRepository extends \LeanModel\Repository {}
?>

skončí to erorom, tabuľka page nemá stĺpec lang

áno nemá, lebo má lang_id, znamená to teda že Repository nemá prístup k definícii Entity?

a tým pádom musím použiť buď niečo takéto:

/**
 * @method Page[] findByLang_id($lang)
 */
class PageRepository extends \LeanModel\Repository {}

alebo si metódú napísať normálne:

class PageRepository extends \LeanModel\Repository {
	public function findByLang($lang) {
		$entities = $this->connection->select('*')
			->from($this->getTable())
			->where('lang_id = %s', $lang)
			->fetchAll();
		return $this->createEntities($entities);
	}
}

pardón ak je to hlúpa otázka, ale akosi som si myslel že ten Repository berie na vedomie definiciu Entity (pozeral som video o Nextras\Orm a tam bolo práve niečo také spomenuté, je pravda že u videa o LeanMapper som to nezapočul)

Šaman
Člen | 2666
+
0
-
  1. LeanQuery mě teprve čeká, to bude druhý krok.
  2. Určitě ne, co je v databázi za sloupce tě na objektové straně nezajímá, od toho je mapper. Jak máš definovanou entitu Page? Ta má property lang?
Matey
Člen | 142
+
0
-

v tom prípade nemám potuchy kde robím chybu :/

áno entita Page má property lang

/**
 * @property Lang $lang m:hasOne
 */
class Page extends Entity {}

a entita Lang

/**
 * @property string $id
 */
class Lang extends Entity {}

všetky entity aj repository mám v rovnakom namespace

class PageRepository extends Repository {

	public function findByLang($lang) {
		$entities = $this->connection->select('*')
			->from($this->getTable())
			->where('lang = %s', $lang) // DibiDriverException #1054 - Unknown column 'lang' in 'where clause'
			//->where('lang_id = %s', $lang) je vporiadku
			->fetchAll();
		return $this->createEntities($entities);
	}
}

Editoval Matey (9. 8. 2014 19:24)

Šaman
Člen | 2666
+
0
-

Podle jiné entity se nedá takhle vyhledávat (@Tharos, nekecám?). Správný přístup by byl načíst si entitu Lang a pak volat $lang->pages.
@method Page[] findByLang_id($lang) taky funguje, jen tam holt musíš znát název sloupce.


Z jiného soudku: vytvořil jsem composer balíček saman/skeleton, ve kterém je použitá stejná kostra jako v Todolistu a taky obsahuje LeanModel. Takže stačí

  1. composer create-project saman/skeleton
  2. vytvořit si databázi a v ní spustit .sql skript z adresáře app/model/data
  3. zkopírovat config.local.example.neon na config.local.neon a vyplnit v něm klíčky k databázi

P.S. Pokud budete verzovat, tak config.local.neon je v .gitignore a nechte ho tam. Jen pokud budete dělat nějaké změny v tomto souboru, tak je nezapomeňte dopsat i do verzovaného config.local.example, jak se často stává mě.
P.P.S. Samozřejmě si databázi můžete udělat vlastní, pak jen upravte entitu User a případně Authenticator a přihlašovací formulář.

Editoval Šaman (10. 8. 2014 7:36)

Tirus91
Člen | 199
+
0
-

@Šaman super. už jsem to aktualizoval a rozchodit.

Más u mne pivko ;)

Editoval Tirus91 (11. 8. 2014 19:44)

Šaman
Člen | 2666
+
0
-

To jsem rád, že se to hodí. A propo, LeanModel už umí nad kolekcemi i ty fetchPairs().
Ještě chci nějak jednoduše pořešit zjištění počtu navázaných entit (aniž by se musela vytvořit kolekce a ta countnout), víc asi na tomhle už řešit nebudu a začnu studovat LeanQueryObject.

Tirus91
Člen | 199
+
0
-

@Šaman
Nastal mi menší problém.
Mám definované 2 entity
Tým
zápasy

V zápasech je tým buď domácí nebo jako host. Potřeboval bych ale vybrat entitu týmu a k tomu vypsat všechny zápasy kde tým byl buď jako host nebo domácí. Jak toto provést?

Tým je takto

/**
 * @property int $id
 * @property string $name
 * @property string $trainer
 * @property string $trainerphone
 * @property string $trainermail
 * @property Countries $country m:hasOne
 * @property int $playercount
 * @property int $accepted
 *
 * @property Ebtournament $tournament m:hasOne
 * @property Ebaccommodation $accommodation m:hasOne
 * @property Ebcategory $category m:hasOne
 *
 * @method Ebteam findBySeasonId($tournament) Vrátí tymy k dane sezone
 */

Zápas takto

/**
 * @property int $id
 * @property Ebteam $hometeam m:hasOne(hometeam)
 * @property Ebteam $visitorteam m:hasOne(visitorteam)
 * @property \DateTime|null $matchtime
 * @property int $homescore
 * @property int $visitorscore
 * @property Ebtournament $tournament m:hasOne
 *
 * @method Ebmatch findByEbtournament_id($tournament) Vrátí zapasy dle turnaje
 */
Šaman
Člen | 2666
+
0
-

Potřebuješ vazby i ze strany týmu na zápas. Viz dokumentace entita Author.

Tirus91
Člen | 199
+
0
-

@Šaman
jasně, ale dá se to nějak sloučit do jedné property?

Šaman
Člen | 2666
+
0
-

Tak to už jsou otázky na @Tharose. Já jsem rád, že zvládám běžné použití. Ale mělo by to jít tak, že si dopíšeš metodu třeba getAllMatches(), kde si vytáhneš obě ty property, spojíš je (kolekce by se měla chovat jako pole, i když nevím, jestli budou fungovat array_ funkce, třeba právě ta array_merge) a z nich vytvoříš jednu kolekci.

Tirus91
Člen | 199
+
0
-

@Šaman
Zkusil jsem to tak jak si psal předtím. No asi nejsem moudrej :(

/**
 * @property int $id
 * @property string $name
 * @property string $trainer
 * @property string $trainerphone
 * @property string $trainermail
 * @property Countries $country m:hasOne
 * @property int $playercount
 * @property int $accepted
 *
 * @property Ebmatch $visitorteam m:belongsToMany(visitorteam)
 * @property Ebmatch $hometeam m:belongsToMany(hometeam)
 * @property Ebtournament $tournament m:hasOne
 * @property Ebaccommodation $accommodation m:hasOne
 * @property Ebcategory $category m:hasOne
 *
 * @method Ebteam findBySeasonId($tournament) Vrátí tymy k dane sezone
 */

a dostanu

Property 'visitorteam' with HasMany or BelongsToMany in entity Tirus\Entity\Ebteam relationship must contain collection

Dám si rovnou facku :)

vyřešeno, má to být

/**
 * @property int $id
 * @property string $name
 * @property string $trainer
 * @property string $trainerphone
 * @property string $trainermail
 * @property Countries $country m:hasOne
 * @property int $playercount
 * @property int $accepted
 *
 * @property Ebmatch[] $visitorteam m:belongsToMany(visitorteam)
 * @property Ebmatch[] $hometeam m:belongsToMany(hometeam)
 * @property Ebtournament $tournament m:hasOne
 * @property Ebaccommodation $accommodation m:hasOne
 * @property Ebcategory $category m:hasOne
 *
 * @method Ebteam findBySeasonId($tournament) Vrátí tymy k dane sezone
 */

Editoval Tirus91 (11. 8. 2014 23:12)

Tharos
Člen | 1030
+
0
-

@Tirus91 Ahoj, povedlo se Ti to tedy nějak vyřešit? Pokud ne, rád pomohu. K entitám by se mi ještě hodilo kdyžtak schéma relevantních tabulek. Díky!

Tirus91
Člen | 199
+
0
-

@Tharos
nijak jsem si s tím nehrál, protože jsem nijak extra do toho zatím nepronikl.
Jinak

-- Adminer 4.1.0 MySQL dump

SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';

DROP TABLE IF EXISTS `countries`;
CREATE TABLE `countries` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `countrycode` varchar(2) NOT NULL DEFAULT '',
  `countryname` varchar(100) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `ebaccommodation`;
CREATE TABLE `ebaccommodation` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `description` text NOT NULL,
  `active` enum('0','1') NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `ebcategory`;
CREATE TABLE `ebcategory` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `description` text NOT NULL,
  `active` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `ebmatch`;
CREATE TABLE `ebmatch` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `hometeam` int(10) unsigned NOT NULL,
  `visitorteam` int(10) unsigned NOT NULL,
  `matchtime` datetime NOT NULL,
  `homescore` int(5) DEFAULT NULL,
  `visitorscore` int(5) DEFAULT NULL,
  `ebtournament_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `hometeam` (`hometeam`),
  KEY `visitorteam` (`visitorteam`),
  KEY `ebtournament_id` (`ebtournament_id`),
  CONSTRAINT `ebmatch_ibfk_3` FOREIGN KEY (`ebtournament_id`) REFERENCES `ebtournament` (`id`),
  CONSTRAINT `ebmatch_ibfk_1` FOREIGN KEY (`hometeam`) REFERENCES `ebteam` (`id`),
  CONSTRAINT `ebmatch_ibfk_2` FOREIGN KEY (`visitorteam`) REFERENCES `ebteam` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `ebteam`;
CREATE TABLE `ebteam` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(250) NOT NULL,
  `trainer` varchar(250) NOT NULL,
  `trainerPhone` varchar(45) DEFAULT NULL,
  `trainerMail` varchar(100) DEFAULT NULL,
  `playercount` int(11) NOT NULL,
  `countries_id` int(10) unsigned NOT NULL DEFAULT '1',
  `accepted` tinyint(4) DEFAULT NULL,
  `ebtournament_id` int(10) unsigned NOT NULL,
  `ebcategory_id` int(10) unsigned NOT NULL,
  `ebaccommodation_id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `ebteam_fk_1` (`ebtournament_id`),
  KEY `ebcategory_id` (`ebcategory_id`),
  KEY `ebaccommodation_id` (`ebaccommodation_id`),
  CONSTRAINT `ebteam_ibfk_1` FOREIGN KEY (`ebcategory_id`) REFERENCES `ebcategory` (`id`),
  CONSTRAINT `ebteam_ibfk_2` FOREIGN KEY (`ebaccommodation_id`) REFERENCES `ebaccommodation` (`id`),
  CONSTRAINT `ebteam_ibfk_3` FOREIGN KEY (`ebtournament_id`) REFERENCES `ebtournament` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `ebtournament`;
CREATE TABLE `ebtournament` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '0',
  `startdate` datetime DEFAULT NULL,
  `enddate` datetime DEFAULT NULL,
  `active` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `year` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- 2014-08-12 15:30:24

Editoval Tirus91 (12. 8. 2014 19:13)

Šaman
Člen | 2666
+
+1
-

Extense v LeanModelu v 1.0.3 umí registrovat filtry, pokud splňují jednoduché rozhraní (*) Ukázka v Todolistu.

(*) Protože nevím, jak v extension ověřit 'instanceof', tak ono rozhrani splňovat nemusí. Musí mít jen metodu registerFilters($connection), která se zavolá pro každou třídu filtrů. Pokud tam nebude, vyskočí laděnka. Ale doporučuji to rozhraní používat, protože se tam ta chybějící podmínka může později objevit, abych mohl vypsat srozumitelnou chybovou hlášku.

P.S. Pro ještě jednodušší použití filtrů existuje třída LeanModel\DefaultFilters, která obsahuje prozatím filtry limit(int $count), where(array $by), is(string $column) a not(string $column). Pokud bych si ji zaregistroval, tak nemusím psát vlastní filtr a filtrování aktivních tasků bych provedl pomocí 'm:filter(not#done)'.


Doplnění: Při registrování tříd filtrů už není potřeba uvádět název – dokonce je možné kombinovat pojmenované a nepojmenované třídy (od verze 1.0.4)

leanModel:
	defaultEntityNamespace: Todolist
	mapper: LeanModel\Mapper
	entityFactory: LeanModel\EntityFactory
	filters:
		- Todolist\TaskFilters
		- def: LeanModel\DefaultFilters

Editoval Šaman (14. 8. 2014 13:22)

Tharos
Člen | 1030
+
0
-

@Tirus91: Pokud potřebuješ spojit kolekce ze dvou vazeb, nejsnazší je udělat to ve vlastním getteru pomocí array_merge nebo podobně.

Pokud bys to chtěl spojit už na úrovni databázového dotazu, taky to lze, ale musíš si pak při tom traverzování položit vlastní dotaz. Tady se řešilo něco podobného, ale bohužel to vyžaduje určitý vhled do toho, jak LM vnitřně funguje :(. Asi napíšu ještě nějakou jednodušší ukázku…

Tharos
Člen | 1030
+
0
-

@Tirus91: Já jsem věděl, že už jsem něco podobného dříve s někým řešil… :)

https://github.com/…er/issues/53

Jak je vidět z těch řešení, nejsnazší je opravdu spojit si to (a eventuálně i seřadit) v paměti. Já osobně bych volil „databázové“ řešení až v momentě, kdy by to přinášelo nějakou zásadní výhodu.

Tirus91
Člen | 199
+
0
-

@Tharos @Šaman
Mám na vás ještě jeden dotaz. Narazil jsem na situaci kdy bych potřeboval udělat vazbu N:M

Definice pomocí anotací
Našel jsem jen M:N 1:N a N:1 a 1:1

M:N mi udělá následující query

SELECT `user`.*
FROM `user`

SELECT `user_role`.*
FROM `user_role`
WHERE `user_role`.`user_id` IN (1)

SELECT `role`.*
FROM `role`
WHERE `role`.`id` IN (1, 2, 3, 4)

Editoval Tirus91 (17. 8. 2014 19:46)

Šaman
Člen | 2666
+
0
-

N:M je totéž, jako M:N, jenom je z pohledu té druhé tabulky potřeba explicitně říct přes jakou tabulku se bude hledat. Nebo to nastavit ručně v mapperu podle toho, ze které strany hledáš.
Mapper totiž při traverzování od usera správně určí spojivací tabulku ‚user_role‘ , ale při traverzování od role určí špatně ‚role_user‘.

Tirus91
Člen | 199
+
0
-

@Šaman
díky, blbě jsem si přečetl dokumentaci. Už mi to funguje :)
pro ty co by to také potřebovali

/**
 *
 * @property int $id
 * @property string $name
 * @property string $code
 * @property User[] $users m:hasMany(role_id:user_role:user_id:user)
 * @property string $canbedeleted
 */

Editoval Tirus91 (18. 8. 2014 9:02)

Tirus91
Člen | 199
+
0
-

@Šaman @Tharos
Narazil jsem ještě na jednu věc, co není v dokumentaci.
Hodlám udělat něco jako rozpracované články a zde bych rád udělal aby když si vyberu entitu témat, která má v sobě kolekci článků, abych zde mohl vybrat jen ty, co nejsou rozpracované.

Vím že když chci získat nově kolekci článků, tak stačí si napsat vlastní findBy a nebo využít např. findByActive(1), ale jak to brát z jiné entity?

Šaman
Člen | 2666
+
0
-

Na to slouží filtry. Dokumentace někde v tom dlooooouhém vlákně :)
Ale jestli chceš jenom tohle, tak na to jsou připravené moje DefaultFilters. Pokud bys měl u článku třeba proprerty $active, tak když píšeš tu vazbu z jiné entity, přidáš nakonec definice ještě 'm:filter(is#active)'. K dispozici je is, not, where a limit. Jen si ty filtry musíš zaregistrovat v extension, viz můj skeleton, nebo todolist (tam je ukázka vlastního filtru).

Editoval Šaman (18. 8. 2014 23:01)

Tirus91
Člen | 199
+
0
-

@Šaman @Tharos

Ahoj,
tak jsem po dlouhé době zas narazil na problém :)
Jak mohu kategorii udělat vazbu na obrázek? a naopak?

Editoval Tirus91 (7. 9. 2014 19:21)