YetORM – kvaziORM nad Nette\Database
- uestla
- Backer | 799
Jak tabulka, tak entita se dají zapsat buď anotací, nebo třídní proměnnou:
/**
* @entity My\Super\Entity
* @table my_glorious_article_table
*/
class ArticleRepository extends YetORM\Repository
{}
nebo
class ArticleRepository extends YetORM\Repository
{
protected $entity = 'My\Super\Entity';
protected $table = 'my_glorious_article_table';
}
Editoval uestla (15. 4. 2013 17:56)
- K0nias
- Člen | 10
Zdravim,
premyslim ted jestli metoda YetORM\Entity::toArray funguje presne podle ocekavani. Uvedu priklad:
Repository
<?php
class Repository exnteds
{
public function insert(Entity\Item $item)
{
$this->getTable()->insert($item->toArray());
}
}
?>
Item:
<?php
/**
* @property-read int $id
* @property-read string $createDate -> create_date
*/
class Item extends YetORM\Entity
{}
?>
V ukazce se neulozi spravne datum pokud budu mit sloupec pojmenovany „create_date“.
<?php
$item = new Item($row);
$item->setCreateDate('2013-04-18');
$repository->insert($item);
?>
V tuhle chvili totiz metoda toArray bude vracet pole:
<?php
array(
'id' => ...,
'createDate' => '2013-04-18'
);
?>
coz neni podle meho nazoru spravne. Podle me by mela vracet nazev sloupce a ne pseudo nazev se kterym pracuje jen object entity:
<?php
array(
'id' => ...,
'create_date' => '2013-04-18'
);
?>
- Jan Tvrdík
- Nette guru | 2595
@K0nias: Proč si to myslíš? Dle mého je
současné chování v pořádku. To na jaký sloupec se mapuje
createDate
je interní detail té entity.
- K0nias
- Člen | 10
no praveze kdyz budes mit tabulku:
item:
id
create_date
a budes k tomu chtit udelat Entitu, ktera by mela mit klasicky camel nazev metod. Tak ti potom toArray nebude vracet ty same nazvy jako mas v DB, ale bude vracet ty interni nazvy sloupcu z entity. To je ta chyba podle me.
- Tharos
- Člen | 1030
K0nias napsal(a):
$this->getTable()->insert($item->toArray());
Tohle je IMHO ne moc hezká praktika. Je to skoro jako někde udělat:
$connection->table('neco')->insert($form->getValues(true));
Pokud je entita úplně triviální, tak možná budiž… Ale pokud obsahuje něco speciálního (speciální validace, specifické mapování), repository by se s tím mělo vypořádat jinak …
Editoval Tharos (18. 4. 2013 17:25)
- castamir
- Člen | 629
Já to ve svém orm řeším tak, že data entity držím ve formě, ve které se s nima dá krásně pracovat (např. databázový sloupeček typu SET udržuju jako pole, datum a čas jako DateTime apod.). V případě uložení entity tam mám metodu, která mi data připraví tj. doplní nevyplněné položky defaultními hodnotami, odkazy na jiné entity nahradí jejich primárním klíčem, naformátuje datum a čas a na závěr ověří, zda jsou všechny údaje vyplněny. Takto to vypadá složitě, ale implementace je poměrně jednoduchá.
Editoval castamir (18. 4. 2013 17:32)
- hAssassin
- Člen | 293
@castamir > mam to uplne stejne, taky to resim o vrstvu jinde, ktera provadi mapovani properties z entity na sloupecky v DB. Mival jsem to automaticky ale byla to moc velka magie a kdyz by se zmenil sloupecek v DB musel bych prepsat vse, takhle zmenit jen dva radky (entity->array, array->entity) + upravil primo mapper ktery poklada dotazy a je to.
@Tharos > +1
@Jan Tvrdík > +1
- Šaman
- Člen | 2661
KOnias: A jak bys zpracoval virtuální property – metodu
getFullName()
, která si načte firstName
a
surName
a spojí je dohromady? Obdobně getAddress()
spojená z několika sloupců, apod. Anebo getPerimeter()
který
se vypočítá ze základních rozměrů? Používám to poměrně často.
Mapování entity do databáze a opačně by měl zajišťovat mapper. Mě se
sjednotil s repository (stejně jak to popisuje QS) a nemám se stávajícím
chováním problém. Metoda toArray()
je součástí entity a
entita by neměla vědět o svém úložišti, takže je určitě správně,
že vrací ‚objektové‘ názvy, nikoliv ‚relační‘.
Editoval Šaman (18. 4. 2013 20:57)
- uestla
- Backer | 799
KOnias: Entita by navenek neměla být závislá na pojmenování sloupců databáze. Ale díky, žes nakousl tenhle problém, protože současný stav „odstíněnosti“ od názvu sloupců mi ne úplně vyhovuje a přemýšlím o zlepšení.
Mám konkrétně na mysli řazení a podobné radosti prováděné na
EntityCollection
. Už na téhle úrovni by měl člověk řadit
podle jmen propert a nikoli podle jmen sloupců, jak je tomu teď.
Problém je, jak zapsat řazení podle property spřažené entity – teď třeba seřadím pohodlně díky
$books->orderBy('author.name');
Ale toto jsou jména sloupců…
- Teyras
- Člen | 81
Předně bych rád pochválil za super práci, něco takovýho už měl někdo dávno napsat. Pokouším se to teď používat na jednom novým projektu, tak bych rád vypíchl věci, na který jsem narazil.
- Nemám problém napsat si skoro všechno sám, ale kdybych měl opisovat obyčejnej CRUD, find($id) atd v každým repozitáři, připadal bych si jako blbec. Dědičností to rozumně řešit neumím, getEntityClass je private… Co takhle udělat něco jako metodu createEntity? A nějaký základní CRUD operace v Repository by možná taky neškodily…
- Nevím, kolik ORM featur plánuješ přidat, ale určitě by neškodila třeba identityMap
- Jack06
- Člen | 168
EDIT: Tak nevím proč, ale po x refreshích už to jede v pohodě. Díky
No dobrá, to jsem zkusil a přišel jsem na to, že když toto udělám, tak mi nette panel hlásí 2 naprosto stejné dotazy na databázi. To by se asi stát nemělo ne?
Dokud tam nedám to fetch, tak mám dotaz na db jeden, jakmile přidám fetch, tak mi hlásí dotazy 2.
uestla napsal(a):
@Jack06 Protože
$this->getTable()->where('user_id', $key)
nevrací ActiveRow, ale Selection, čili použij
$this->getTable()->where('user_id', $key)->fetch()
Editoval Jack06 (26. 4. 2013 19:00)
- Šaman
- Člen | 2661
Ty dotazy nejsou stejné. Jednou načítáš jen user_id
, je to
požadavek z repozitory. Asi si zjišťuje vazbu na usera.
Potom máš požadavek na *
z entity, asi přitom
fetch()
. A tak se musí provést dotaz znovu, protože načítáš
dalši data.
NDb si ale pamatuje jaké dotazy která stránka požaduje a proto po
dalším refreshi se už hned napoprvé ptá na *
, aby se pak
nemusel pokládat dalši dotaz.
- Jack06
- Člen | 168
Dědičnost asi až tak uplně nefunguje nebo ano?
UserEntity: http://pastebin.com/9Q0T7FMp
AbstractEntity: http://pastebin.com/kEdvw17r
Když si dám get na user, tak se mi vrátí pouze proměnné definované v userEntitě, nikoli všechny.
- Šaman
- Člen | 2661
Domnívám se, že tohle ti nebude fungovat už z principu – anotace nejsou součást jazyka PHP a zpracovávají se pomocí Nette při vytváření instance dané třídy. A jestliže tu abstraktní třídu neinstancuješ, tak poděděná třída o anotacích svého předka vůbec neví. V tomto případě bys musel definovat všechny property a metody klasickým PHP způsobem, pak to bude samozřejmě i dědit.
- Jack06
- Člen | 168
Tak jsem to zkusil přepsat:
Abstract: http://pastebin.com/BjvqF05K
User: http://pastebin.com/C2RdgpTT
A bohužel stále to samé, sql mi vrací z databáze pouze sloupce z User
Šaman napsal(a):
Domnívám se, že tohle ti nebude fungovat už z principu – anotace nejsou součást jazyka PHP a zpracovávají se pomocí Nette při vytváření instance dané třídy. A jestliže tu abstraktní třídu neinstancuješ, tak poděděná třída o anotacích svého předka vůbec neví. V tomto případě bys musel definovat všechny property a metody klasickým PHP způsobem, pak to bude samozřejmě i dědit.
- Šaman
- Člen | 2661
U Usera jsi anotace mohl nechat, ale to je teď vedlejší. Máš všechno
v jedné tabulce, resp. fungují ti metody z abstraktní třídy, pokud je
zkopíruješ do Usera? Nezapomeň, že ona row
je
ActiveRow
vytvořená asi nějakým UserRepository a má nejspíš
vazbu na tabulku User. Má sloupečky account
, city
,
company
(případně id_account
, …)?
- Jack06
- Člen | 168
Repository: http://pastebin.com/9Vj59qkZ
Database: http://www.2imgs.com/68dc2f05d6
Mám to v jedné tabulce a záznamy vybírám pomocí fetch. Vrací mi to následující: http://www.2imgs.com/5bef1c36e5
// Když to hodím z abstractu do User, tak to funguje.
Editoval Jack06 (27. 4. 2013 10:53)
- Jack06
- Člen | 168
Teď nechápu k čemu bych měl a jaké property definovat, když je to YetORM. Ten vrací stejně $this->row a ne lokální. Popravdě ani v examples na gitu YetORM nevidím nic víc, než mám napsané.
Šaman napsal(a):
A zůstaly ti v těch entitách nějaké property? Takhle se skoro divím, že ti to vůbec něco načítá (kromě id_user).
Zkus si v abstraktní třídě definovat pár property klasicky v PHP, jako public.
- Šaman
- Člen | 2661
Sorry, máš pravdu, právě jsem to zkoušel.. Bohužel, to, že YetORM
vrací hodnoty pomocí ActiveRow
je jeho hlavní vlastnost, ale pro
mě je to zároveň největší slabina..
Asi ti neporadím – předpokládám, že metody z Abstract (pokud nejsou
v anotacích) ti fungují, jen si pokladají dalši dotazy?
- Jack06
- Člen | 168
Nepoloží to další dotaz na databázi, normálně se jen ten sloupec přidá do sql dotazu, ale jelikož pracuji se záznamem jako s entitou a strkám to jako setDefaults do formuláře, tak i přesto že v sql dotazu pak je třeba ten sloupec z abstractu, tak se mi ale ve vytvořené instanci User neobjeví.
Toto mi přijde jako hodně velké mínus, když nefunguje takto dědičnost. :-(
Šaman napsal(a):
Sorry, máš pravdu, právě jsem to zkoušel.. Bohužel, to, že YetORM vrací hodnoty pomocí
ActiveRow
je jeho hlavní vlastnost, ale pro mě je to zároveň největší slabina..
Asi ti neporadím – předpokládám, že metody z Abstract (pokud nejsou v anotacích) ti fungují, jen si pokladají dalši dotazy?
- uestla
- Backer | 799
@Jack06: Předpokládám, že se tu jedná
o použití metody toArray()
, a že ruční volání getteru
z rodiče funguje standardně…
Je
to pravda, gettery to vrací pouze z nejnižší úrovně, což tam bylo
z důvodu, aby se nezanášely nějakými interními gettery na úrovni
YetORM\Entity
. To už ale není třeba.
Implementováno. (včetně dědičnosti anotovaných propert)
@Šaman: V čem vidíš onu slabinu? Z
ActiveRow
se berou pouze property, pokud chce člověk
složitější logiku, může si napsat getter…
Editoval uestla (27. 4. 2013 14:27)
- Šaman
- Člen | 2661
Tak tomu řikám ultra fast podpora, jestli už je dědičnost implementovaná. :)
OT: Nevýhoda je v tom, že nemohu vytvořit plnohodnotnou entitu, aniž by tato nebyla v databázi. Myslím něco takového:
<?php
$article = new Article();
$article->title = "Nadpis";
$article->content = "Bla bla bla."
$article->author = $user;
$articleRepository->persist($article);
?>
Ale to není nic proti YetORMu, spíš přemýšlím, jestli vůbec
používat, či nepoužívat NDb. Na to, že každá entita obsahuje
activeRow
jsme pak taky narazili u optimalizace projektu, kde se
vypisuje tisíce záznamů najednou (bez stránkování).
- uestla
- Backer | 799
@Šaman: To máš pravdu. Tohle mi vadí taky.
Paradoxně tohle možná vyřeší úpravy ActiveRow
chystané pro
Nette 2.1, kvůli kterým bude nutné YetORM
upravit.
Zatím si to tak spojuji v hlavě, ještě jsem nic nenapsal, ale nejspíš konstruktor upravím na
function __construct(YetORM\Row $row = NULL)
{
$this->row = $row;
}
Třída YetORM\Row
bude extendovat ActiveRow
s tím, že zachová __set()
ze stávající stable verze, a
upraví update()
na něco jako
function update()
{
$tmp = $this->modified;
$this->modified = array();
return parent::update($tmp);
}
EDIT: S tím, že persistence na straně repository by mohla vypadat
function persist(& $entity)
{
$row = $entity->toRow();
if ($row === NULL) {
$entity = new Book($this->getTable()->insert($row->get<CoolNázev>()));
} else {
$row->update();
}
}
EDIT2: Ale jak jsem už předeslal, do vydání stable verze Nette na tuhle funkcionalitu nešáhnu, navíc to bude chtít pořádně osázet testy.
Editoval uestla (27. 4. 2013 16:55)
- Tharos
- Člen | 1030
Šaman napsal(a):
Ale to není nic proti YetORMu, spíš přemýšlím, jestli vůbec používat, či nepoužívat NDb.
Já jsem teda od NDB zase utekl, už asi potřetí v životě :(. Začal jsem si psát podobné tenké ORMko nad Dibi :-P no a šlo to překvapivě dobře od ruky… Je inspirované NotORMem, YetORM a ještě pár postupy, se kterými jsem se někde setkal a oslovily mě. YetORMu se to v mnohých věcech podobá, jiné věci jsou v tom zase řešené jinak…
Pokud by byl zájem, asi bych to mohl nějak zdokumentovat a někam dát. Na jednu stranu se mi to moc dokumentovat a vystavovat nechce, protože na tom zrovna nejsem úplně nejlíp s časem… a taky mám trochu problém s tím, že jsem v podstatě „napsal další ORM“ a nerad bych byl už z tohohle důvodu za blázna :-P… Ale kdyžtak dej vědět.
Editoval Tharos (27. 4. 2013 17:28)
- Šaman
- Člen | 2661
Co máte proti ORMům? ORM je mapper, který zařizuje ukládání Entit jako objektů do relační databáze a naopak. Píšu ho pro každý projekt od té doby, co jsem začal data udržovat v Entitách místo v polích.
Vy myslíte ORM framework. Nad Dibi už docela dlouho píše takový ORM framework Petr Procházka, dokonce už má RC verzi. Ještě se podívám, jesli existuje dokumentace, protože to je často kámen úrazu. YetORM má tak málo kódu, že se většina věcí dá vyčíst z kódu, u Petrova ormu ne.
- castamir
- Člen | 629
OT
@Šaman ORM je obecně model na ukládání dat. Co se
ORM Petra Procházky týče, díval jsem se na něj a mnoho věcí mi na něm
vyloženě vadí a nejsou to zrovna maličkosti, ale pro mě dost zásadní
věci (od struktury frameworku, přes propojení entit s repozitářem a
mapperem až po ukládání). Spoustu věcí má fakt vymakaných (hlavně vazby
mezi entitami), ale přes ten způsob ukládání a napojení té struktury se
prostě nedokážu přenést.
- Šaman
- Člen | 2661
No, pokud by ses domluvil s autory a začlenil YetORM do Nette Database, bylo by to super. NDb není jen klasická databázová vrstva, ORM hodně připomíná, ale možnost vytváření vlastních entit dost chybí. Aspoň by se nováčkové naučili vytvářet a předávat plnohodnotné entity – jinak dost dlouho trvá, než začnou používat některý z „ORMů“.
- uestla
- Backer | 799
Dobré poledne.
S dovolením bych se tématem vrátil k YetORM :-)
Verze 1.0.0
Před chvilkou vyšla verze 1.0.0, zde je sumáž zásadních změn:
- přidána mezivrstva
YetORM\Row
(umožní snadnější přechod na stable Nette 2.1 (které bude teď někdy)) - možnost vytváření entit bez nutnosti existence v databázi
- sjednocení properties (dělí se na
AnnotationProperty
aMethodProperty
) - podpora pro
@internal
anotaci u entitních getterů (tak, aby se nepromítaly dotoArray()
) - a další různé optimalizace (i za cenu menších BC breaků)
Díky za pozornost.
Editoval uestla (12. 5. 2013 12:15)
- Tharos
- Člen | 1030
Blahoželám k významnému pokroku. :)
Persistence, jak jsi naimplementoval, pro mě osobně skrývá pár myšlenek k zamyšlení. Rád bych je zde zveřejnil, protože je teď aktuálně také řeším v tom svém mapperu nad dibi.
Co vypíše následující kód? A co by vůbec měl vypsat?
$author = $book->getAuthor();
$author->setName('Franta');
$author = $book->getAuthor();
echo $author->getName();
„Franta“, anebo snad něco jiného?
Kam tím mířím je, že u takhle pojaté persistence mi v hlavě naskakuje hned několik scénářů, kdy nemám jasno, co dostanu za výsledek. Ba co víc kdy nemám ani jasno v tom, co bych sám v takové chvíli chtěl… Prostě je to pro mě neintuitivní.
Mé současné řešení funguje tak, že entity jsou read-only (mají jenom
gettery) a vytváření (respektive úpravy) záznamů se dějí přes
repositáře stylem
$bookRepository->createBook('Some book', 2, 'some description')
(respektive $bookRepository->update(1, $values)
).
Je to asi trochu divné, ale ve své podstatě praktické a chování je jasné. Také to je hezky kompatibilní s typickým scénářem, kdy dostanu data z formuláře a mám z nich něco uvařit. Zápis:
$book = new Book;
$book->setTitle('Some book');
$book->setAuthor($author);
$book->setDescription('some description');
$bookRepository->persist($book);
mi prostě přijde neohrabanější oproti prostému (a navíc atomickému):
$bookRepository->createBook('Some book', 2, 'some description');
Protože je to ale asi trochu neobvyklý přístup, váhám, zda si persistenci přes settery a persist() přece jen nenaimplementovat. Jen v ní cítím dost WTF záležitostí – asi bych byl schopen vysypat z rukávu množství dalších ukázek, kde bych intuitivně nevěděl, co mohu očekávat za výsledek… Nebo to vidím moc černě?
Editoval Tharos (16. 5. 2013 9:35)
- uestla
- Backer | 799
Ad ukázka: Očekával bych, že vrátí 'Franta'
. Proč?
Protože u YetORM
to tak nefunguje :-D
Teď vážně – tohle souvisí s IdentityMap, se kterou by měla pracovat
jednotka mapující hodnoty entity. Přemýšlím, jak nenásilně vytvořit
lazy mapování (tedy mapovat až při přístupu k property) tak, aby samotná
YetORM\Entity
měla co možná nejméně třídních proměnných a
metod (ideálně pouze magické metody…).
To, co jsem si zatím pospojoval v hlavě, ale co mi papír nebere, je:
- IdentityMap – zajišťuje existenci jediné instance konk. entity
- RepositoryContainer – kontajnér pro entitní repozitáře
- Repository – „práce“ s konkrétní entitou, má přístup k RepositoryContainer
- Mapper – mapuje konk. úložiště na konk. entitu, kvůli možným závislostem má rovněž přístup k RepositoryContaineru
Problémy, na které např. narážím:
- více-hodnotový identifikátor entity (např. více-sloupcový)
- defaultní hodnoty (netriviální, např.
CURRENT_TIMESTAMP
– řešit už při vytvoření entity, nebo až při persistování, …)
Atd. prostě klasické mindráky při znovuobjevování kola, asi si půjdu zahrát karty…
- MartinitCZ
- Člen | 580
Zauvažuj nad přejmenováním složky E např. na Exceptions ..... Bude to lepší ;)
- uestla
- Backer | 799
Malý update:
Reakce na Jack06a: Přidal jsem podporu pro zápis entit v anotacích, který zohledňuje aliasy. Využívá se k tomu Aliaser.
POZOR: týká se to opravdu jen anotací, pokud píšeme třídu do řetězce, je třeba uvést její celé jméno, např. v
new YetORM\EntityCollection($selection, 'Model\Entities\Book');
// nebo
class BookRepository
{
protected $entity = 'Model\Entities\Book';
}
Přepsal jsem testy tak, aby využívali namespace a bylo názornější, jak to používat.