YetORM – kvaziORM nad Nette\Database

uestla
Backer | 796
+
0
-

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

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

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

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.

Jan Tvrdík
Nette guru | 2595
+
0
-

@K0nias: Já to chápu, ale považuji to za úmysl a ne chybu.

Tharos
Člen | 1030
+
0
-

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

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

@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

K0nias
Člen | 10
+
0
-

To ste me teda moc nepotesili panove. :) Uz jsem to prepsal

Šaman
Člen | 2635
+
0
-

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

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

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.

  1. 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…
  2. Nevím, kolik ORM featur plánuješ přidat, ale určitě by neškodila třeba identityMap
Jack06
Člen | 168
+
0
-

Jak řešíte například triviální výběr jednoho záznamu, stejně jako například unikátního kde ID, ale podle jiného sloupce?

něco jako toto, což nelze používat:

public function findByUser($key)
{
	return new Settings($this->getTable()->where('user_id', $key));
}
uestla
Backer | 796
+
0
-

@Jack06 Protože

$this->getTable()->where('user_id', $key)

nevrací ActiveRow, ale Selection, čili použij

$this->getTable()->where('user_id', $key)->fetch()
Jack06
Člen | 168
+
0
-

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.

viz obr

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

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

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

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

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

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

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)

Šaman
Člen | 2635
+
0
-

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.

Jack06
Člen | 168
+
0
-

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

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

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?

Šaman
Člen | 2635
+
0
-

Tohle by asi vyřešila přetížená metoda _toArray(). Zjišťování property z anotací u předka je problém, ale zjištění všech getterů by snad šlo.. jen mám pocit, že reflexe ti s dědičností nepracuje a pak vznikají tyto případy..

uestla
Backer | 796
+
0
-

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

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

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

Š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)

castamir
Člen | 629
+
0
-

OT:
@Tharos: těch vlastních ORM je tu poslední dobou jak hub po dešti. Taky si dělám jeden nad dibi :D

hAssassin
Člen | 293
+
0
-

@Tharos, @castamir > heh, tak to sme tri :-) mohli bysme pak zalozit novou sekci v addons specialne pro ORMs nad Nette :-D

Šaman
Člen | 2635
+
0
-

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.

hrach
Člen | 1834
+
0
-

Hádejte, kdo dneska odpoledne forkl YetORM a vybušil z někoho naprosto brilantní vlastní ORMko nad Nette\Database :D ?!? :D

castamir
Člen | 629
+
0
-

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.

castamir
Člen | 629
+
0
-

@hrach: buď ty anebo Petr… ale jsem asi slepý, bo ten fork nevidím…

hrach
Člen | 1834
+
0
-

@castamir Nedelal jsem to jako fork na githubu, protoze je to kompletne prepracovane, ale jasne to vychazi z YetORM, takze bude zachovana licence, autorstvi, atp… :)

Šaman
Člen | 2635
+
0
-

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ů“.

castamir
Člen | 629
+
0
-

@hrach a zveřejníš to někde?

uestla
Backer | 796
+
0
-

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 a MethodProperty)
  • podpora pro @internal anotaci u entitních getterů (tak, aby se nepromítaly do toArray())
  • 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
+
0
-

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

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

Zauvažuj nad přejmenováním složky E např. na Exceptions ..... Bude to lepší ;)

Šaman
Člen | 2635
+
0
-

martinit napsal(a):

Zauvažuj nad přejmenováním složky E např. na Exceptions ..... Bude to lepší ;)

Rozhodně. Já už si to předělal. Složku i namespace. Jen bych použil jednotné číslo.

uestla
Backer | 796
+
0
-

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.

JJanek
Člen | 2
+
0
-

Ahoj.
Mohl bych poprosit o doplnění licence? Rád bych YetORM používal i v projektech.:-)

uestla
Backer | 796
+
0
-

Ahoj, máš na mysli konkrétně znění?

Protože je všude uváděno MIT

JJanek
Člen | 2
+
0
-

Ou, díky. Už vidím, moje chyba. Ještě jednou díky.

Skippous
Člen | 21
+
0
-

Ahoj, právě testuji knihovnu na jednom menším projektu a používá se opravdu jednoduše. Zajímá mě jestli plánuješ další vývoj, případně podporu výčtového typu enum nebo definici vazeb pomocí anotací. Díky

uestla
Backer | 796
+
0
-

Ahoj, současný master budu rozšiřovat už jen minimálně, věci jako výčtové typy či rozšiřování
anotací osobně neplánuji…

Mám rozpracováno něco málo z nové verze, ale kdoví, jestli to vůbec někdy spatří světlo
světa…