YetORM – kvaziORM nad Nette\Database
- Pavel Kravčík
- Člen | 1195
Možné to není, protože mě nepustí YetORM výjimka. Napadá mě to
v téhle situaci třeba.
Ale rozumím tomu principu, asi je to tak čistější do budoucna.
Šlo mi to – dát si do té getMetody json_decode, přišlo mi, že to patří do Entity.
Díky za nullable.
- Pavel Kravčík
- Člen | 1195
@uestla: Nepřemýšlel si o rozšíření EntityCollection o další možnosti filtrování podle NTDB? Na mysli mám where() podmínky na $selection.
- Pavel Kravčík
- Člen | 1195
A jak to dopadlo? Třeba pak, když člověk se chce pokusit napsat DataSource pro Grido, tak musí převést EntityCollection do Array a filtrovat to jako pole. Což myslím není moc hezké.
Jen mi zajímalo, jestli není třeba někde beta – přepisovat třídy ve Vendoru mi rozumné nepřišlo. :))
- ryder
- Člen | 17
Zdravím,
s YetORM, resp. s Nette začínám a YetORM mě oslovila jako abstrakce, kterou jsem hledal :)
Řeším problém s transakcemi, které nevím jak správně s YetORM používat:
Pokud chci uložit entitu, která se skládá z dalších entit, tak to potřebuji obalit celé do transakce, abych měl v DB uloženy buď kompletní data nebo nic.
Jak tedy realizovat transakce, pokud ukládám několik různých entit do různých repository?
- uestla
- Backer | 799
To je dobrý dotaz.
Původně jsem chtěl navrhnout mít service, který by měl instance repozitářů všech svázaných entit. Pak mi došlo, že transakce se na repozitáři zvenčí volat nedají. Nejpřímočařejší (ale nehezké) by bylo mít vazbu realizovanou přímo uvnitř „hlavního“ repozitáře (repozitáře s hlavní entitou), který by měl ostatní repozitáře v sobě.
Čistší by asi bylo mít oddělenou vrstvu pro transakce někde bokem a ne takhle natvrdo svázanou s repozitářem. Zkusím to nějak rozumně napsat a přidat, kdyžtak dám vědět. Díky za námět!
- ryder
- Člen | 17
Děkuji za reakci :)
Zatím to řeším přidáním metody insert do Repository, která provede insert nad NDB, a transakce si řeším ve vrstvě nad Repository také nad NDB, ale není to čisté řešení a moc se mi nelíbí.
Stav kdy by se řešili transakce bokem, nezávisle na Repository by byl fajn :)
- uestla
- Backer | 799
Ahoj,
tak jsem přidal třídu YetORM\Transaction
coby vrstvu pro
transakce. Každá repository si vytváří vlastní instanci a na ní pak volá
svoje „transakční“ metody, takže se navenek nic nemění. Přidává to
ale možnost si ve vlastní službě vytvořit vlastní Transaction
instanci a v ní pak persistovat více entit z různých repozitářů:
use Nette\Database\Connection;
class MyService
{
private $books;
private $authors;
private $transaction;
function __construct(Connection $connection, BookRepository $books, AuthorRepository $authors)
{
$this->books = $books;
$this->authors = $authors;
$this->transaction = new YetORM\Transaction($connection);
}
function persistAll($books, $authors)
{
$this->transaction->transaction(function () use ($books, $authors) {
foreach ($books as $book) {
$this->books->persist($book);
}
foreach ($authors as $author) {
$this->authors->persist($author);
}
});
}
}
- mdsystem
- Backer | 5
Ahoj,
mám dotaz ohledně vytváření entit z relací.
Mám entitu book a v ní následující metodu
function getAuthor()
{
return new Author($this->record->author);
}
Vadí mi to new. Neměl bych si spíš do té entity předat relaci která vytváří Author entitu a zavolat něco jako
function getAuthor()
{
return $this->authorRelation->createEntity($this->record->author);
}
Je mi jasné že vazbu na author relation bych musel dát i do book relatin a ta by ji předávala svým entitám, ale přijde mi, že jsou tak dodrženy principy DI.
Nebo mi něco uniklo?
Editoval mdsystem (11. 5. 2015 15:08)
- mdsystem
- Backer | 5
uestla napsal(a):
Většinou si při složitějších vazbách udržuji službu
EntityCreator
, která má metodycreateAuthor($row)
apod. Tu si pak konstruktorem předávám do jednotlivých entit, aby tak vytváření instancí probíhalo prostřednictvím téhle služby.
Díky za odpověď.
Samozřejmě jsem to napsal před tím blbě. Tím authorRelation jsem myslel
authorRepository. Nicméně jestli jsem tě správně pochopil, není špatně
pokud entita zná repository (což je také služba), kterou nechá jinou entitu
vytvářet. To jestli to zabalím ještě do nějaké obecné služby (tvá
EntityCreator), už podle mě není podstatné. Prostě není problém, aby
entita volala createEntity metodu nějaké repository.
Šlo mi o to jestli tím nějak nejdu proti myšlence ORM.
Editoval mdsystem (12. 5. 2015 1:39)
- uestla
- Backer | 799
Předávat repozitory je neprozíravé v tom, že pokud jsou entity na sobě
závislé vzájemně, dostaneš se do cyklické závislosti uvnitř
SystemContainer
u. Proto si vytvářím nezávislou
EntityCreator
, kterou si předávám jak do entity, tak do
repozitářů. V repozitářích si pak akorát upravím
createEntity()
tak, aby volala metodu právě oné služby.
- mdsystem
- Backer | 5
uestla napsal(a):
Předávat repozitory je neprozíravé v tom, že pokud jsou entity na sobě závislé vzájemně, dostaneš se do cyklické závislosti uvnitř
SystemContainer
u. Proto si vytvářím nezávislouEntityCreator
, kterou si předávám jak do entity, tak do repozitářů. V repozitářích si pak akorát upravímcreateEntity()
tak, aby volala metodu právě oné služby.
Jo, tak už jsem na to také přišel. Když se dva repository potřebují v konstruktoru navzájem, není to úplně ono 8o). Jen mi to nejdřív přišlo ne úplně pěkné mít službu, která zná v podstatě všechny repository.
- mdsystem
- Backer | 5
Ještě jsem přišel na problém s kolekcemi. Pokud přetížím konstruktor entity abych mohl do entity předávat nějaké další parametry (např. $imageDir co máš použit v testu), tak se mi to shoří při vytváření kolekce, na tom, že nezná druhý parametr konstruktoru. Samozřejmě mohu jeho výchozí hodnotu nastavit na NULL, ale to pak v Entitách získaných z kolekce ten $imageDir nebudu mít.
Jediné co mě napadlo, ale nejsem si jistý správností a přijde mi to neohrabané, je při vytvářaní EntityCollection místo názvu třídy entity předat callback na objekt, který mi bude zajišťovat volání createEntity z její repository, což by mohla být zase tebou navrhovaná služba EntityCreator. Pak ale, vytvářím službu, která zná všechny repository a všechny Entity na ní závísí a to mi nepřijde úplně kosher.
- uestla
- Backer | 799
Služba EntityCreator
nezná všechny repozitáře, jen všechny
závislosti všech entit. Ale to je jen proto, že jsem si to tak zvolil.
Můžou existovat tenčí továrničky pro každou entitu zvlášť.
Ohledně EntityCollection
– to, co popisuješ, funguje, tj.
místo názvu entitní třídy jde předávat callback, tj. např.
Nette\Utils\Callback::closure($entityCreator, 'createAuthor')
.
- mdsystem
- Backer | 5
uestla napsal(a):
Služba
EntityCreator
nezná všechny repozitáře, jen všechny závislosti všech entit. Ale to je jen proto, že jsem si to tak zvolil. Můžou existovat tenčí továrničky pro každou entitu zvlášť.Ohledně
EntityCollection
– to, co popisuješ, funguje, tj. místo názvu entitní třídy jde předávat callback, tj. např.Nette\Utils\Callback::closure($entityCreator, 'createAuthor')
.
Už v tom mám jasno, jen jsem to původně chtěl narvat do repository, tak
aby se každý repository staral o své entity a nedošlo mi, že mi mohou
vzniknout cyklické závislosti.
Moc dík za reakce, dost mi to zjednodušil život.
- Gappa
- Nette Blogger | 208
uestla napsal(a):
Většinou si při složitějších vazbách udržuji službu
EntityCreator
, která má metodycreateAuthor($row)
apod. Tu si pak konstruktorem předávám do jednotlivých entit, aby tak vytváření instancí probíhalo prostřednictvím téhle služby.
Ahoj,
šlo by prosím postnout ukázku takové služby? Potřeboval bych trošku „nakopnout“ :)
Díky
- uestla
- Backer | 799
Jasně, např.
class EntityCreator extends Nette\Object
{
function createCategory($row = NULL)
{
return new Category($this->createArticle, $row);
}
function createArticle($row = NULL)
{
return new Article($this->createCategory, $row);
}
}
Obě entity totiž na sobě závisí navzájem:
class Category extends YetORM\Entity
{
private $articleFactory;
function __construct(\Closure $factory, $row = NULL)
{
parent::__construct($row);
$this->articleFactory = $factory;
}
function getPublishedArticles()
{
$selection = $this->record->related('article', 'category_id')
->where('published', TRUE)
->where('written <= NOW()');
return new YetORM\EntityCollection($selection, $this->articleFactory);
}
}
A v jednotlivých repozitářích pak
use Nette\Database\Context as NdbContext;
class CategoryRepository extends YetORM\Repository
{
private $creator;
function __construct(EntityCreator $creator, NdbContext $database)
{
parent::__construct($database);
$this->creator = $creator;
}
function createEntity($row = NULL)
{
return $this->creator->createCategory($row);
}
}
Obdobně u entity Article
a její
ArticleRepository
…
- uestla
- Backer | 799
Verze 9.0.0
Před malou chvílí jsem vydal verzi 9. Hlavní změnou je přidání CI a code coverage.
Došlo také k pár změnám a menším BC breakům (proto navýšení major verze).
Velké díky za pomoc patří @Felix ! :-)
- Pavel Kravčík
- Člen | 1195
Ještě drobnost k nové verzi. Proč checkEntity
zůstala
private? Mohla by být protected? :) To je prakticky jediný věc, kvůli které
„forkuju“.
Use-case, dědím si Repository do BaseRepository a při každém volání persist() ukládám odlitek změn někam do „historie“.
- CZechBoY
- Člen | 3608
Jak vkládat do M:N tabulky?
ZKoušel jsem to klasicky přes vlastní repozitář + entitu, ale hází mi to
chybu při volání setRow s integerem.
https://ctrlv.cz/AP97
Ještě přidám jak to volám
$MNEntity = $this->MNRepository->createEntity();
$MNEntity->id1 = $id1;
$MNEntity->id2 = $id2;
$this->MNRepository->persist($MNEntity);
Editoval CZechBoY (8. 4. 2016 20:04)
- uestla
- Backer | 799
Ahoj, přemýšlel jsem nad tím. Problém je, že např. v
Repository
přetěžuji
__call()
a v závěru se odvolávám
na rodiče. U traity by to znamenalo si vytvořit aliasní metodu a tu pak volat namísto
parent::
. To se mi ale moc nelíbí, tak to zatím nechávám
otevřené…
- idiox
- Člen | 14
Ahoj :)
Jak byste prosím řešili nastavení defaultního jazyka pro repository nebo entitu?
Mám entitu post(id, parent, author) a entitu jazykových mutací post_lang(lang, title, content) a když si načtu
<?php
$posts = $this->postRepo->findParents();
?>
tak pak musím v každým cyklu psát
<?php
$post->getTitle( $this->lang );
// getTitle vrací PostLang( $this->record->related('post_lang', 'post_id')->where(['lang_id.code' => $this->language])->fetch()->title
?>
a rád bych tam měl prostě jen
<?php
$post->getTitle();
?>
Díky za nápady :)
- uestla
- Backer | 799
Ahoj,
defaultní jazyk bych držel v repozitáři a upravil v něm metodu
createEntity()
, kde bych do entity vložil defaultní jazyk.
Pak bych přidal metodu Post::getTranslation($lang = NULL)
,
která by vracela spřaženou instanci PostLang
podle jazyka –
pokud by byl NULL
, vrátila by defaultní překlad. Instance bych
navíc cachoval, abych ušeřil dotazy.
Všechny následné metody Post::getTitle()
apod. by měly
volitelný parametr $lang
, který by vracel hodnoty z instance
překladu.
Tedy vše výše uvedené v kódu (psáno na koleně):
class PostRepository extends YetORM\Repository
{
private $defaultLang = 'en';
public function createEntity($row = NULL)
{
$class = $this->getEntityClass();
return new $class($this->defaultLang, $row);
}
}
class Post extends YetORM\Entity
{
private $defaultLang;
private $translations = [];
public function __construct($defaultLang, $row = NULL)
{
$this->defaultLang = $defaultLang;
$this->record = Record::create($row);
}
public function getTranslation($lang)
{
if (!isset($this->translations[$lang])) {
$row = $this->record->related('post_lang', 'post_id')
->where('lang_id.code', $lang)
->fetch();
$this->translations[$lang] = new PostLang($row);
}
return $this->translations[$lang];
}
public function getTitle($lang = NULL)
{
return $this->getTranslation($lang ?: $this->defaultLang)->getTitle();
}
}
Snad ti to alespoň ukázalo možný postup :-)
Kdybys něčemu nerozuměl, zeptej se.
Editoval uestla (20. 12. 2016 10:26)
- Pavel Kravčík
- Člen | 1195
Ahoj @uestla, chtěl bych přetížit AnnotationProperty – nenapadá Tě hezký způsob jak na to? Nechce se mi jít do forku – spíše kdyby to šlo rozšiřovat pomocí interface. Připravím PR klidně, jen zatím vymýšlím jak na to.
Use case – chceme sami rozhodovat, kdy se má sloupec v ResultSet měnit
v DateTime. Nelíbilo se mi, že pro každý sloupec, který má nastaveno
date
se tvoří v Nette (new DateTime) i když to není potřeba.
Napsal jsem tedy driver, který to nedělá. V entitě bych však rád zachoval
obojí chování v property nastavit jak string či DateTime a vytvoření
new DateTime
by byla zodpovědnost entity. Bohužel to neprojde
přes tu kontrolu v AnnotationProperty#96. Díky.
- uestla
- Backer | 799
@PavelKravčík Je ono použití stringu namísto
DateTime
z důvodu výkonu?
V danou chvíli bych navrhoval spíše změnit use case (tedy trvat vždy
pouze na stringu a v případě potřeby jej ručně na DateTime
převést- třeba pomocí explicitního getteru na entitě) než ohýbat
interní AnnotationProperty
(i třeba pomocí rozšíření na
interface a vlastní implementace).
- Pavel Kravčík
- Člen | 1195
@uestla: Ano – výkonnově se nám to moc nelíbilo.
Díky to je také dobrý nápad, chtěli jsme to mít programátorsky přívětivější, ale explicitní funkce bude čistější řešení i pro testování.