Nextras\ORM – ORM nad Nextras\Dbal

Hanz25
Člen | 38
+
0
-

@hrach aha, tak to jsem si neuvědomil. Díky za odpověď, běhá to parádně.

batko
Člen | 219
+
0
-

Ahoj,

řeším následující:

<?php
/**
 *
 * @property OneHasMany|Book[] $book {1:m Book::$author}
 */

class Author extends Entity {

}

/**
 *
 * @property Author $author {m:1 Author ::$book}
 *
 */
class Book extends Entity

?>

Potřebuji, aby když si vytáhnu entitu Author (pomocí dotazu v mapperu), tak abych mohl nějak dofiltrovat knihy (například knihy jen starší 5ti let) a netvořilo mi to zbytečně mnoho dotazů do DB. Je to možno udělat v mapperu jedním dotazem?

Díky

hrach
Člen | 1834
+
+1
-

@batko

$author->books->get()->findBy(['publishedAt<=' => strtotime('-5 years')]);

Editoval hrach (8. 1. 2017 16:57)

goood
Člen | 26
+
0
-

Zdravím, potřeboval bych poradit. Dal jsem na Zoner hosting aplikaci v nette používající nextras orm. Bohužel mi to padá na „Nextras\Dbal\QueryException Unknown or incorrect time zone: ‚Europe/Prague‘“.
Poslal jsem jim odkaz jak to zprovoznit: https://nextras.org/…ysql-support
Podle vyjádření administrátora hostingu není možné toto zařídit. Lze to tedy v orm nějak obejít? Nebo mám zvolit jiný hosting? Díky moc

Editoval goood (12. 1. 2017 19:27)

goood
Člen | 26
+
0
-

Zatím jsem to obešel zakomentováním řádku přímo v dbal v MysqliDriver.php, ale to se mi teda nelíbí

CZechBoY
Člen | 3608
+
0
-

@goood Co to je za kravinu? Českej hosting nemá timezonu Prahy? Že by čas na změnu? :-)

Editoval CZechBoY (12. 1. 2017 21:22)

prebijak
Člen | 21
+
0
-

CZechBoY napsal(a):

@goood Co to je za kravinu? Českej hosting nemá timezonu Prahy?

Spíš nemá žádné informace o časových pásmech, MySQL je ve výchozím stavu neobsahuje: https://nextras.org/…ysql-support

goood napsal(a):

Zatím jsem to obešel zakomentováním řádku přímo v dbal v MysqliDriver.php, ale to se mi teda nelíbí

Nechá se to obejít také nastavením pevného časového pásma: https://nextras.org/dbal/docs/2.1/

Já mám ve své aplikaci

	connectionTz: auto-offset

Edit: Ale pokud můžeš, je lepší se tomu vyhnout: https://forum.nette.org/…nextras-dbal?p=3

Editoval prebijak (12. 1. 2017 21:30)

CZechBoY
Člen | 3608
+
0
-

@prebijak pak nefunguje automatický zjištění letní/zimní čas, ne? A je potřeba to řešit v php.

Editoval CZechBoY (12. 1. 2017 21:48)

hrach
Člen | 1834
+
0
-

Je to jak pisou kluci. auto-offset to vyresi, ale je treba si uvedomit, ze neni pak dobre pouzivat MySQL funkce, ktere pocitaji rozdil mezi dvema datumy. Tento rozdil pak muze byt o hodinu spatne, respektive nerespektovat korektne casove zony. That's all, vetsina aplikaci to stejne nepotrebuje, takze auto-offset muze stacit.

goood
Člen | 26
+
+1
-

@hrach @prebijak Díky vám, to funguje, toto nastavení jsem nějak přehlédl. Naštěstí zatím TIMEDIFF apod nepotřebuju, tak by to mělo být bez problémů

hrach
Člen | 1834
+
+1
-

Nova bugfix verze 2.2.1 – opravuje dve neprijemne chyby: https://github.com/…s/tag/v2.2.1

  • pokud jste pouzili derivovanou kolekci relationshipu ($author->books->get()->findBy(...)), tak pri persistu dane entity se dany relationship nacetl cely ($author->books), coz muze vest k zasadnimu zpomaleni a narazeni na memory limit.
  • razeni podle datetime property nefungovalo spravne, pokud nejaka z hodnot byla null.
David Kregl
Člen | 52
+
0
-

Ahoj,

existuje nějaký způsob, jak seřadit ICollection náhodně? Zkoušel jsem orderBy('RAND()'), ale to mi zařvalo.

Díky!

Editoval David Kregl (29. 1. 2017 11:16)

hrach
Člen | 1834
+
0
-

@DavidKregl ne, je treba bud na dbal vrstve pres mapper, nebo pokud uz je neco prefiltrovane, tak jedine v php pomoci ICollection::fetchAll a shuffle().

Hanz25
Člen | 38
+
0
-

Ahoj,

chtěl bych se zeptat jak se řeší kaskádové mazání ve vztahu m:n. Pokud mám vztah m:n a v definici entity uvedu

cascase[persist, remove]

a tato entita je právě mazána, tak mám pocit, že orm chce smazat entitu i na druhé straně, ovšem já bych potřeboval smazat pouze ten vztah.

Konkrétně: mám articles a tags. A jakmile smažu tag, tak chci zároveň odstranit spojení toho smazaného tagu s článkem, ovšem nechci pochopitelně smazat ten článek.

Zatím to mám vyřešené v databázi, ale chtěl bych tu logiku spíše směřovat k ORM.


A druhá věc: je nějaká možnost přes ORM z databáze vytáhnout hodnotu, která nebude definovaná jako properta entity? Prostě něco, co si spočítám v selectu, ať už by mělo jít o součet nějakých sloupců, nebo hodnotu selectnutou přes IF … THEN … ELSE, nebo jiným způsobem? Ono je sice super, že jakmile bohaté findBy/getBy metody selžou, člověk si může sestavit, nebo napsat vlastní SQL, ovšem vždycky se výsledek hydratuje na entitu, takže tam není šance propašovat nějakou vlastní hodnotu.

Díky.

Btw: jedu na nejnovější verzi a vše ok, je to super. viděl jsem commity na OR condition operator a už se těším na release (+1)

medhi
Generous Backer | 255
+
0
-

V aplikacích se velmi často musí ověřovat příslušnost entity k jiné (například k userovi). Typicky pokud například edituji nebo mažu článek, musím před akcí provést ověření. Nebylo by ideální to ověřovat pomocí $article = $user->articles->get()->getById($articleId);? Bohužel metoda getById() na této kolekci není k dispozici.

Jak to děláte vy?

Jan Tvrdík
Nette guru | 2595
+
0
-

Můžeš použít $user->articles->get()->get(['id' => $articleId])

Nicméně lepší je mít napsané rozumné ACL, takže třeba u nás máme na jednom projektu něco jako

$article = $this->articles->getById($articleId);
$this->requireEntity($article);
$this->requireAccess($article, Authorizator::ACTION_VIEW);
hrach
Člen | 1834
+
+2
-

@Hanz25

  • m:n
    • nesmis mit cascade removed ⇒ u vztahu cascade vyjadruje kaskadu na entitu na druhe strane, ne na mezivazebni tabulku
    • smazani tagu, kdyz k nemu uz nic nevede – to je funkcionalita remove orphan, zakladam issue: https://github.com/…m/issues/205; do implementace muzes pouzit nejaky event na entite, treba onPersist, mrknout, jestli je vztah modified, a pokud ano, provest patricnou logiku
  • select hodnoty, ktera neni property – uplne moc nechapu usecase, nechces uvest priklad? Obecne ORM proste pracuje s entitami, pokud chces vyselectovat neco, co v nich neni, neni problem to rovnou selectnout DBALem.

@medhi
Honza myslel getBy, nicmene prijde mi to jako dobra vec, aby tam ta metoda byla: https://github.com/…m/issues/204

JZechy
Člen | 161
+
0
-

Dá se nějak {enum} říct, že chci validovat hodnotu oproti všem konstantám ve třídě? Z příkladu lze zadávat třeba TYPE_*. Ale když těm konstantám žádný prefix nedávám a zkusím jenom hvězdičku:

/**
 * @property type $name {enum EnumClass::*}
 */
class MyEntity extends Entity {
// ...
}

Tak mi validace neprojde. A vypisovat všechny hodnoty do enumu…

Editoval JZechy (19. 2. 2017 19:38)

hrach
Člen | 1834
+
+1
-

@JZechy diky, fixed a oprava relased jako 2.2.3: https://github.com/…s/tag/v2.2.3 :)

JZechy
Člen | 161
+
0
-

@hrach Paráda, hned můžu odpoledne pokračovat :)

Hanz25
Člen | 38
+
0
-

@hrach

select hodnoty, ktera neni property – uplne moc nechapu usecase, nechces uvest priklad? Obecne ORM proste pracuje s entitami, pokud chces vyselectovat neco, co v nich neni, neni problem to rovnou selectnout DBALem.

Aha, ano máš pravdu, tady jsem spíš řešil konkrétní problém, než funkčnost orm. No a jak bych se mohl dostat třeba ke QueryBuilderu? Dá se dostat nějak z ORM, nebo si ho budu muset nějak zaregistrovat v configu zvlášť a pak ho zvlášť žádat přes DI?

Protože třeba v doktríním entity manageru se dostaneš ke query builderu a můžeš si vytvořit vlastní dotaz, který se bude hydratovat na pole.

hrach
Člen | 1834
+
+1
-

@Hanz25

Hanz25
Člen | 38
+
0
-

@hrach
Aha super, díky.

medhi
Generous Backer | 255
+
0
-

K čemu slouží metoda remove($entity) na hasMany vztahu? Lze nějak využít k mazání entit nebo mám raději používat repozitář?

K tomu se vztahuje ještě jeden dotaz. Z laického pohledu mě pořád přitahuje vyhýbat se repozitářům a občas pracovat pouze s entitami, protože tam mám rovnou ověřeno, že entita má nějaký vztah. Například pokud chci editovat článek přihlášeného autora, líbí se mi při editaci načíst článek pomocí $author->articles->get()->getBy(['id' => $articleId]) (zde by se mi ještě více líbilo něco jako $author->articles->getById(['id' => $articleId]), ale nevím jestli to dává smysl).

Stejně tak při mazání článku ověřuji, že je autora, čili $author->articles->remove($article) (I když zde je asi správný postup nejdříve ověřit zda mám právo článek smazat, viz výše, a pak ho smazat rovnou přes $articleRepository->removeAndFlush($article);.

Dalším důvodem, proč mě to láká je, že v presentech nevidím na repositář, ale pouze na fasády. A přijde mi divné psát do fasád primitivní funkce, které nemají přidanou hodnotu a pouze kopírují funkci repositáře, například

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

Protože fasád mám více a toto bych pak musel opakovat v každé z nich, což není DRY. A BaseFacade se mi nehodí. Možná traitu? Budu rád když mě v tomto nasměrujete správným směrem.

Díky moc

hrach
Člen | 1834
+
+1
-

K čemu slouží metoda remove($entity) na hasMany vztahu?

K rozpojeni vztahu, ne k smazani entity. Odstrani entitu ze vztahu.

$author->articles->get()->getBy([‚id‘ ⇒ $articleId])

ano, to je docela dobry pristup, sam casto pouzivam.

Stejně tak při mazání článku ověřuji, že je autora, čili $author->articles->remove($article)

ee, to je spatne, to mazes ten clanek ze vztahu, viz. vyse. ty by sis ho mel spravne vytahnout (klidne pres ten get, jak si psal), a pak ho smazat pres repository, model.

proč mě to láká je, že v presentech nevidím na repositář

Osobne nezavadim zadne fasady nad repozitarem, prijde mi to docela overhead. repozitar rovnou injektuji. Kdyz je slozitejsi logika, tak spis zavadim servisy, kterou tu celou slozitejsi logiku zapouzdri.

medhi
Generous Backer | 255
+
0
-

@hrach: Díky moc za odpovědi. Co si myslíš o tom zápisu author->articles->getById(['id' => $articleId])? Dává to smysl? Prostě mít možnost na relacích používat všechny metody co na kolekcích.

Jan Tvrdík
Nette guru | 2595
+
0
-

Ne, to mi smysl nedává. Spíš bych byl pro, aby author->articles mohlo vracet rovnou kolekci. Současné chování neumožňuje mít immutable relace.

hrach
Člen | 1834
+
0
-

@medhi dava my smysl jen pridani one metody getById() na ICollection, kde neni (to uz si navrhoval, je tu issue). Tj bylo by to pak author->articles->get()->getById($articleId).

medhi
Generous Backer | 255
+
0
-

@JanTvrdík Něco takového jsem asi myslel.

Je to pohled „uživatele“, kde mi přijde divné psát 2× get za sebou. Ale řešení nechávám na odbornících, i takto jsem spokojen :)

David Kregl
Člen | 52
+
0
-

Ahoj,

možná řeším úplně banální věc velmi složitě, ale chci se zeptat na radu.

Mám několik entit: Article, Person, ‚Event‘ a potřebuji pomocí jednoho search baru vyhledávat všechny tyto entity podle jejich vlastností (title, fullName, desxription, …).

Jak a kde mám položit dotaz, který mi vrátí kolekci těchto všech entit? Je to vůbec možné?
Jak řešíte vyhledávání ve více typech entit vy?

Díky!

Editoval David Kregl (7. 3. 2017 18:40)

hrach
Člen | 1834
+
0
-

@DavidKregl: orm neumoznuje vratit kolekci, ktera by mela vic typu entity. Vzdy muzes selectovat jen z jednoho repository. Nicmene to muzes podminit samozrejme jakymkoliv joinem, nejvic volnosti ziskas v mapperu, kde si muzes rovnou napsat sql. https://nextras.org/…s/2.2/mapper

Editoval hrach (7. 3. 2017 22:48)

Jan Tvrdík
Nette guru | 2595
+
0
-

Ono tohle zrovna nejde rozumně vybrat ani jedním SQL dotazem. Tj. stejně jako bych v čistém SQL poslal víc dotazů, tak bych i v ORM se zeptal více repositaru. Nebo změnil organizace dát v DB, aby to šlo rozumně jedním dotazem vytáhnout.

Lizardor
Člen | 35
+
0
-

Zdravím, snažím se rozběhat toto ORM a setkávám se s tímto problémem:
Service 'orm.mappers.blocations': Service of type Nextras\Orm\Mapper\IMapper needed by Nextras\Orm\Repository\Repository::__construct() not found. Did you register it in configuration file?

I po registraci do configu to stále nejede. Držím se struktury z „dema“.

hrach
Člen | 1834
+
0
-

@Lizardor mas vytvoreny BlocationsMapper? A jak se presne jmenuje? A jake je presne jmeno repository?

Lizardor
Člen | 35
+
+1
-

@hrach Díky, koukám do toho už takovou dobu a až díky tobě jsem si všimnul, že místo Repository jsem napsal Repositary … :-D

David Kregl
Člen | 52
+
0
-

@hrach @JanTvrdík Moc díky za postrčení. Nakonec jsem to vyřešil následovně, jen by mě zajímalo, jestli existuje nějaký způsob, jak bych mohl té query předat proměnou $query, která obsahuje vyhledáváný string jen jednou. Díky!

$result = $connection->query(
    "SELECT a.id, at.title, at.content, 'accomplishment' AS type FROM accomplishment AS a
     LEFT JOIN accomplishment_translation AS at ON a.id = at.accomplishment_id
     WHERE at.title LIKE %_like_
     OR at.content LIKE %_like_" .

    " UNION SELECT a.id, at.title, at.content, 'article' AS type FROM article AS a
     LEFT JOIN article_translation AS at ON a.id = at.article_id
     WHERE at.title LIKE %_like_
     OR at.content LIKE %_like_" .

    " UNION SELECT m.id, mt.title, mt.content, 'meeting' AS type FROM meeting AS m
     LEFT JOIN meeting_translation AS mt ON m.id = mt.meeting_id
     WHERE mt.title LIKE %_like_
     OR mt.content LIKE %_like_" .

    " UNION SELECT p.id, pt.title, pt.content, 'principle' AS type FROM principle AS p
     LEFT JOIN principle_translation AS pt ON p.id = pt.principle_id
     WHERE pt.title LIKE %_like_
     OR pt.content LIKE %_like_",
    $query,
    $query,
    $query,
    $query,
    $query,
    $query,
    $query,
    $query
);

Editoval David Kregl (9. 3. 2017 23:59)

hrach
Člen | 1834
+
0
-

@DavidKregl ne, musis tolikrat, kolikkrat mas placeholder.

Hanz25
Člen | 38
+
0
-

Ahoj,

řeším hrozně zvláštní problém s ukládáním entit ve vztahu m:n. Dělám ACL a k určité roli ukládám povolené actions, ovšem ten formulář funguje pouze pro jednu roli. V daném presenteru mám persistentní parametr $id, který vybírá určitou roli a zobrazuje k ní různé action, které se dají povolit/zakázat.

Výsledkem z toho formuláře je pole s idčky všech akcí, ke kterým by měl mít uživatel přístup (těch uložených i těch nových) A pak už se děje jen co píšu dole. Selectnu ty actions a uložím.

		// Přijímá z formu $allowedActions = [9, 10, 11];
		$allowedActionsCollection = $this->orm->actions->findById($allowedActions);

		$this->loadRole->actions->set($allowedActionsCollection->fetchAll());

		$this->orm->roles->persistAndFlush($this->loadRole);

Když ukládám dané actions k adminovi (specifický je jen idčkem 1 a tím, že je to role, ze které to zkouším), tak funguje vše dobře, ale na jakékoliv jiné roli to nejde. Přesněji řečeno, já té roli předám akce, které tam chci mít, ale z proklikávání laděnky to vypadá, že v RelationshipMapperManyHasMany jsou v jsou připraveny k odstranění/přidání naprosto špatné vztahy. Konečnou je exception Nextras\Dbal\UniqueConstraintViolationException kdy to selže kvůli unikátnímu klíči ve spojovací tabulce: Duplicate entry ‚10–8‘ for key ‚role_id_action_id‘, nebo v druhém případě vše proběhne bez chyby, ale žádný dotaz se na databázi nepošle.

Zkoušel jsem různě izolovat všechno možné, přihlásit se pod jinou rolí, ale vždy funguje pouze admin, což je dost zvláštní chování. Nenapadá vás něco? Díky

hrach
Člen | 1834
+
+1
-

@Hanz25 obecne by melo fungovat i, ale ne, ze by to melo mit vliv na funkcnost, to co si napsal.

// Přijímá z formu $allowedActions = [9, 10, 11];
$this->loadRole->actions->set($allowedActions);

Kdyztak mi prosim posli landenku na mail, jestli muzes. diky.

medhi
Generous Backer | 255
+
0
-

Lze nějak elegantně zkombinovat Nextras\ORM a dynamické snippety? Ty totiž fungují pouze, pokud cyklus, který je vypisuje, kteruje nad polem. Dle dokumentace, pokud chci jeden dynamický snippet překreslit, do takového pole umístím pouze ten jeden prvek a invaliduju obalovací snippet.

U ORM neiteruji při vykreslování nad polem, ale nad objektem relace a z něco nelze prvky odebrat a nechat tam pouze jeden.

hrach
Člen | 1834
+
0
-

@medhi to moc mi nedava smysl, dynamicke snippety vubec nejsou zavisle na tom, z ceho je generujes, potrebujes hlavne mit dynamic name, tam klidne muzes davat id z entity {snippet row-$row->id} by melo fungovat.

medhi
Generous Backer | 255
+
0
-

hrach napsal(a):

@medhi to moc mi nedava smysl, dynamicke snippety vubec nejsou zavisle na tom, z ceho je generujes, potrebujes hlavne mit dynamic name, tam klidne muzes davat id z entity {snippet row-$row->id} by melo fungovat.

Nemyslel jsem generování, ale spíš invalidaci. Dle nápovědy se dělá takto:

public function handleUpdate($id)
    {
        $this->template->list = $this->isAjax()
                ? []
                : $this->getTheWholeList();
        $this->template->list[$id] = 'Updated item';
        $this->redrawControl('itemsContainer');
    }

Tedy z pole záznamů se udělá pole o jednom prvku a invaliduje se obalovací snippet.

Pokud používám Nextras\ORM, tak výše uvedená $this->template->list není obvykle pole, ale relace OneHasMany. Z té ovšem nelze tak jednoduše smazat všechny „záznamy“ a ponechat pouze jeden. Musím ji převést na pole, což nevím zda je správný postup.

hrach
Člen | 1834
+
0
-

No, to je dost hloupa dokumentace. Pri updatu si by sis nemel natahnout vsechny zaznamy, ale jen ten, ktery updatujes. Do sablony pak dat traversable|array jen s tim jednim prvkem. A invalidovat jeho snippet.

A nebo pouzij fetchAll().

Lizardor
Člen | 35
+
0
-

Chtěl bych se zeptat je možnost porovnávat na základě pouze cizího klíče? Aniž by se musel generovat další (pro mě zbytečný) dotaz? Jelikož mám Entitu kde mám vazbu na druhou Entitu (relace) a při porovnávání už se nepotřebuji znova dotazem ptát na druhou tabulku (chtěl bych to porovnat na základě ID), ale zatím se mi nepovedlo docílit toho aby se mi negeneroval zmíněný zbytečný dotaz. Pokud sem to napsal nějak nesrozumitelně tak se více rozepíšu i s příkladem. Díky.

hrach
Člen | 1834
+
0
-

@Lizardor melo by fungovat findBy(['this->author' => 2]) misto findBy(['this->author->id' => 2]), ikdyz uz dlouho dlouho jsem to nepsal, tak nevim.

Lizardor
Člen | 35
+
+1
-

@hrach tak nakonec sem to vyřešil metodou getRawValue, ale díky :)

meridius
Člen | 34
+
0
-

Ahoj. Chci se zeptat, jak správně řešit vícenásobné filtrování kolekce vlastními metodami v mapperu.

Mám například vyhledávací formulář s více textovými poli, kde každé odpovídá jinému sloupci v tabulce. Pro každé vyplněné pole by měla vzniknout LIKE podmínka v dotazu. Více podmínek by se mělo spojovat například AND.

Problém je, že vlastní funkce v mapperu vrací novou koleci a na té kolekci nemůžu zavolat další funkci v mapperu. Vlastní funkce z mapperu lze tedy volat jen nad celou repository, což zabraňuje řetězení těchto funkcí.

Celý tento problém vznikl, protože do findBy nad kolekcí lze dát jen operátory =, !=, <=, <, >= a >.

Přehlížím něco?
Předem díky

hrach
Člen | 1834
+
0
-

@meridius ano, mas pravdu, presne tak to je, a chapu, ze to muze byt problem. aktualne neni jineho reseni, nez si vsechny ty like podminky predat naraz. Nicmene vyhledove je v planu, ze LIKE by byl podporovany i na kolekci.

meridius
Člen | 34
+
0
-

@hrach toho jsem se bál. Ale OK, vyřeším to tak a děkuju za potvrzení.

hrach
Člen | 1834
+
0
-

@meridius jedna se jen o like, nebo i o nejake jine custom operace?