Bezbolestná integrace Doctrine 2 ORM do Nette – Kdyby/Doctrine
- Filip Procházka
- Moderator | 4668
Zdravím,
Kdyby/Doctrine je doplněk pro
Nette, který do něj integruje Doctrine 2 ORM.
Samozřejmostí je podpora stable i dev Nette a také nejnovější
verze Doctrine (tedy 2.4@stable
).
Rozšíření je klasicky na Githubu k vašim službám včetně dokumentace, nejlépe ho instalujte pomocí composeru. Chyby nebo nápady na vylepšení hlaste prosím na githubu
Instalace
Pokud chcete používat vývojové Nette, tak do composer.json
dáme následující
"require": {
"nette/nette": "@dev",
"kdyby/doctrine": "@dev",
"kdyby/annotations": "@dev",
"kdyby/doctrine-cache": "@dev",
"kdyby/events": "@dev",
"kdyby/console": "@dev",
}
Pro stable Nette je to výrazně jednodušší
"require": {
"nette/nette": "~2.0",
"kdyby/doctrine": "~0.10",
}
(Pozn.: řada 0.*
je pro ~2.0@stable
Nette, řada
1.*
je pro ~2.1@dev
Nette)
Otázky směřujte sem
Další otázky prosím zakládejte jako samostatná témata na novém fóru help.kdyby.org. Díky!
Na co se těšit
- Integrace Nette formulářů tak jako to má Symfony (automatické mapování odeslaných hodnot na entity a zpět)
- Integrace Kdyby/Validator
- akadlec
- Člen | 1326
Měl bych dotaz. Když se vytvoří služba takto:
services:
articles: App\Articles(@doctrine.dao(App\Article))
Tak se ty nativní DQL skládají přímo v presenteru/komponentě kde se ta služba používá? V současné chvíli totiž používám jinou extension pro doctrine a mám to rozdělené na Entitu – Fasádu – Repozitář a jako službu si předám fasádu která podědí základní metody repozitáře a DQL které potřebuju navíc si pak poskládám v repozitáři.
Jak to tedy funguje s tvým rozšířením?
EDIT:
Takže jsem to pochopil trošku špatně a našel tento článek: http://www.frosty22.cz/…ccess-object
Pokud to tedy již chápu dobře, to co já mám teď jako fasádu je u kdyby model a repozitář zůstává prakticky bezezměn, je tak?
Editoval akadlec (18. 9. 2013 9:17)
- Filip Procházka
- Moderator | 4668
Když použijeme termíny na které jsi zvyklý, tak moje služby
App\Articles
jsou už v podstatě tvoje Facade
. DQL
můžeš psát přímo do nich, nebo do samostatných
tříd.
Hlavní myšlenkou tohoto návrhu je nedědit už dále DAO, protože tím porušuješ SRP
- Filip Procházka
- Moderator | 4668
Nelíbí se mi dědit dao jen kvůli autowiringu. A ty operace v konstruktoru jsou sice jen ilustrační, ale určitě matoucí.
- Filip Procházka
- Moderator | 4668
Přesně tak :) Zase tolik psaní to není, je to pár znaků a navíc nemusíš předávat všechny, protože si můžeš další DAO vytáhnout pomocí related
- Jiří Nápravník
- Člen | 710
Pěkné rozšíření, ale dneska by dalo zabrat, nevím, jeslti je to moje neznalost, či kde je problém. Zkrátka mám entitu Game, ta má v sobě kolekci $pictures (OneToMany). V kódu jsem potom k této kolekci přistoupil, upravil mj. jeden atribut v ní a uložil. Pseudokód
$game = $this->gameDao->find(1);
$game->getPictures()->get([0])->setPath($newPath);
$this->gameDao->save($game);
no a po uložení se v databázi ona změna neprovedla. Ŕíkal jsem si ok, tak nebero to prvky v kolekci, zavolám $this->gameDao->flush(); nicméně ani to změnu neprovedlo. Musím si exciplitně vytáhnout entitymanager a přes něj zavolat flush() a pak změna proběhne. Chápu to tedy tak, že pokud volám přes dao objekt metodu save/flush, tak se ty změny provádí jenom v rámci entity, ke které je DAO vázáno. Chápu to dobře? Je to opravdu žádoucí, nebylo by lepší pokud by alespoň flush u dao flushnul všechny změny a nemusel se tahat explicitně EntityManager?
- Filip Procházka
- Moderator | 4668
Ano, je to tak schválně. Jen jsem se ještě za toho 3/4 roku co to používám ještě nerozhodl, jestli je to žádoucí :)
Na druhou stranu, jsi si jistý že ten kód co jsi popsal je nejlepší možný způsob jak s tím pracovat?
$firstPicture = $this->picturesDao->findBy(array('game' => 1), array('id' => 'ASC'), +);
$firstPicture->setPath($newPath);
$this->picturesDao->save($firstPicture);
Na takovéty běžné operace je DAO super, ale jakmile zpracováváš něco většího, tak není od věci si předat i ten EntityManager.
- Jiří Nápravník
- Člen | 710
Díky, jak jsem tak nad tím přemýšlel, tak mi došlo, že to přeci jen dává asi spíše smysl, jak ot je teď implementováné, že by se zkrázka dao mělo starat opravdu jen o ty entity, které jsou jeho, jen to občas může holt zmást:-)
Ten kód byl spíše pseudokód + hodně zkrácený, abych naznačil, co myslím.
EntityManager si tady předám, ať mi to proběhne hezky v jedné transakci. Což je právě taky podle mě trochu problém toho řešení současného, že to neflushuje všechny změny, zkrátka když pracuji s více DAO a entitami v jedné mětodě, tak většinou chci, aby to proběhlo v jedné transakci, a tak nemohu využít save(), ale musím nakoenc sáhnout pro entitymanager. Ale sám nevím, co je lepší:)
- zimmi
- Člen | 94
Po instalaci přes composer dostávám
Nette\InvalidStateException
Ambiguous class Doctrine\Tests\DoctrineTestCase resolution; defined in /home/michal/www/doctrine/libs/doctrine/collections/tests/Doctrine/Tests/DoctrineTestCase.php and in /home/michal/www/doctrine/libs/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php.
Jak z toho ven?
- jiri.pudil
- Nette Blogger | 1029
Nenačítat vendor složku RobotLoaderem. Použij autoloader generovaný
Composerem (vendor/autoload.php
).
- zimmi
- Člen | 94
Zdravím!
Rád bych použil kdyby/doctrine v kombinaci s o5/Grido, který jako zdroj
přejímá Doctrine/Querybuilder.
Mám tedy entitu User, která dědí od IdentifiedEntity, s vlastnostmi
username, email, password atd.
S kódem dole dostávám
RuntimeException Not all identifier properties can be found in the ResultSetMapping: id
.
Id je přitom děděné z IdentifiedEntity a v selectu je taky uvedeno.
$users = $this->em->getDao(User::getClassName());
$u = $users->createQueryBuilder()
->select('u.id')
->from('AdminModule\User', 'u');
Když na tom kódu spustím getQuery() a teprve ten potom execute()-uju, vrací správný výsledek. Pokud byste někdo měl nějaký nápad, sem s ním.
Přikládám ještě část IdentifiedEntity, kde je vlastně jen změněno namapování vlastnosti id na sloupec guid v db.
abstract class IdentifiedEntity extends BaseEntity
{
/**
* @var string
* @ORM\Id
* @ORM\Column(type="string", name="guid")
* @ORM\GeneratedValue
*/
protected $id;
...
- Filip Procházka
- Moderator | 4668
Problém je v tomto řádku
->select('u.id')
Používáš hydrataci, která vytahuje entity, ale dovoluješ mu do SQLka dát pouze nějaký sloupec. To není možné takto kombinovat.
Buďto použij hydrataci na pole, nebo vytahuj celou entitu
->select('u')
Teoreticky můžeš ještě vytáhnout entitu jenom s idčkem
->select('partial u.{id}')
ale to se z různých důvodů nedoporučuje, protože je to pokročilá optimalizační technika a mohl by sis nadělat v aplikaci dost nepříjemný bugy (kterých si ani nemusíš všimnout!).
- Stic
- Člen | 28
Chcel by som sa spytat, ci je normalne ze po instalacii Kdyby/Doctrine mi stupol execution time radovo 3–6x? To som zatial vytvoril len jednu entitu ktora je vyuzita na uplne banalny vypis, aby som vyskusal funkcionalitu…
Execution time predtym na localhoste bol radovo 50–100ms po zacacheovani (200ms bez cache), po integracii Kdyby/Doctrine sa k tejto hodnote ani len nepriblizim a dostavam sa radovo na 300–600ms (bez cache aj okolo 1s).
Chapem ze doctrine nie je dokonaly, ale to naozaj zavadza az taky velky overhead? Uvazoval som totiz nad tym, ci nebude jednoduchsie vyuzit doctrine, pretoze kody vyzerali prehladnejsie, ale s takym navysenim execuion time mi to pride uplne useless, kedze 1s oneskorenie uz len na strane serveru mi pride ako riadny overkill…
- Filip Procházka
- Moderator | 4668
- Zapni nějaké cachování (apc/redis/memcache), bez memory cache se doctrine naprosto reálně používat nedá (ani čistá).
- Kdyby žádný overhead nepřidává, pouze v development módu hodně kontroluje, jestli jsi něco nezměnil, aby se ti aplikace chovala dle očekávání. Zapni aplikaci v produkčním módu a pak to změř.
Moje aplikace se načítá 2–8 vteřin v development módu a 200–600ms v produkčním módu na serveru.
- Stic
- Člen | 28
Ak to spravne chapem, tak Kdyby/Doctrine uz samozrejme DQL prelozene do SQL ma cacheovane pomocou nette nie? Teda tato konfiguracia uz je zbytocna, ci? A ako sa da pripadne v config.neon nastavit Doctrine na memcache?
- Filip Procházka
- Moderator | 4668
@Stic: Tohle nastavení za tebe řeší Kdyby. Tyhle čtyři nastavení si můžeš změnit na jednu z podporovaných hodnot
Trošku jsem vylepšil práci s memcachí, protože jsem si všiml, že ten
výchozí driver má setter a blbě by se konfiguroval, teď (po aktualizaci
Kdyby/Doctrine
) by to mělo jít nějak takto
doctrine:
metadataCache: memcache(@memcache.client)
queryCache: memcache(@memcache.client)
resultCache: memcache(@memcache.client)
hydrationCache: memcache(@memcache.client)
services:
memcache.client:
class: Memcache()
setup:
- addserver(localhost, 11211)
A nebo můžeš nastavit pro celou aplikaci datovou cache na memcache a
protože Kdyby/Doctrine
by default používá systémovou datovou
cache, bude se používat ta.
services:
cacheStorage:
class: Nette\Caching\Storages\MemcachedStorage()
- Filip Procházka
- Moderator | 4668
@enumag: ne, stačí jenom zapnout
storage, který nahradí ten systémový a tedy ho bude automaticky
používat i Kdyby/Doctrine
.
- enumag
- Člen | 2118
@Filip Procházka: A jak to funguje? RedisExtension v podstatě jen zaregistruje služby. Očekával bych tedy, že u memcache bude také stačit vytvořit službu memcache.client a předefinovat službu cacheStorage. Rád bych pochopil kde je rozdíl že to u memcache nestačí a u redisu ano. ;-)
- Filip Procházka
- Moderator | 4668
Přečti si ten můj příspěvek o memcachi znovu :)
Rozdíl je v tom, že když tu memcache narveš rovnou do doctriny a
nebudeš tam nechávat mezikrok v podobě Nette\Caching\Cache
bude
to rychlejší (ale přijdeš o zamykání).
- zimmi
- Člen | 94
Zdravím,
potřeboval bych mít možnost sestavit EntityManager
na základě
uživatelské identity. Pokud bude uživatel nepřihlášený, použijí se pro
nastavení Doctrine údaje uvedené v config.neon, pokud se ale přihlásí,
chtěl bych vytáhnout Nette\Security\IIdentity
a pro
přihlášení do DB použít jméno a heslo právě přihlášeného
uživatele.
Lze teda nějak v configu přepsat defaultní EntityManager
?
Pokud si teď podědím Kdyby\Doctrine\EntityManager
a zaregistruju
ho do services, dostávám
Service 'doctrine.dao': Multiple services of type Kdyby\Doctrine\EntityManager found: doctrine.default.entityManager, manager
.
- frosty22
- Člen | 373
Vzhledem k tomuhle řádku https://github.com/…xtension.php#L242
bych řekl, že by to snad mohlo jít v configu přepsat jako:
doctrine: # za předpokladu, že máš tu extension pojmenovano tak
orm:
defaultEntityManager:
class: Kdyby\Doctrine\EntityManager
create: Muj\EntityManagerFactory
Editoval frosty22 (17. 11. 2013 14:41)
- frosty22
- Člen | 373
Ona je možná ta cesta špatně, nejsem si jistý odkud to tenhle řádek bere:
isset($builder->parameters[$this->prefix(‚orm.defaultEntityManager‘)])
ale myslím, že není problém, když si tam hodíš dump($builder->parameters) a zjistíš, co to je za parametry .. možná to je:
doctrine: # za předpokladu, že máš tu extension pojmenovano tak
orm.defaultEntityManager:
class: Kdyby\Doctrine\EntityManager
create: Muj\EntityManagerFactory
- frosty22
- Člen | 373
@enumag no já myslím, když ty služby jsou tam přidané přes extension a v té je tedy i použito $this->prefix(…), což ti zaregistruje ty služby pod vlastní namespace, který je podle toho názvu, jak máš přidanou extension – v tomhle případě doctrine.
Nebo jestli myslíš, že to registruje pod:
services:
doctrine:
orm:
defaultEntityManager: ....
je to možné, ale já si myslím že ne kvůli případné kolizi názvů .. ale není nic jednoduššího než to zkusit .. :)
- Jiří Nápravník
- Člen | 710
Můžu se zeptat, jak to vypadá s integrací Nette formulářů a validátory? Koukal jsem, že na GitHubu je DoctrineForms, je to už použitelné nebo zatím spíše náčrt? Případně nějaký sample?
- Filip Procházka
- Moderator | 4668
DoctrineForms ještě nejsou ready. Je tam jen jednoduché mapování a validátor je spíš jenom náčrtek :) Až to bude použitelné tak dám určitě vědět.
- Filip Procházka
- Moderator | 4668
$schemaTool = new Doctrine\ORM\Tools\SchemaTool($em);
$schemaTool->dropSchema($em->getMetadataFactory()->getAllMetadata());
$schemaTool->createSchema($em->getMetadataFactory()->getAllMetadata());
Volá se to nad Connection
toho EntityManager
u, tak
bacha ať si nedropneš produkční databázi ;)
- duskohu
- Člen | 778
Caute snazil som sa rozchodit Kdyby\Doctrine, ale hned na zaciatku som narazil na problem, v app mam nette.ajax.js, teraz nie je dolezite ze preco tam mam, ale robi mi to jeden problem ked zapnem Kdyby\Doctrine\DI\OrmExtension, tak mi ladenka vypise: smeruje to sem
Class Nette\Config\CompilerExtension has been renamed to Nette\DI\CompilerExtension.
lenze ja tuto extension nemam zapnute, neviem ako to suvisi, ale zacne to robit len ked zapnem Kdyby\Doctrine\DI\OrmExtension
Nasiel som aj dalsie problem, podobne ako nette.ajax.js mam v app aj kcfinder a tam je nieco taketo:
class browser extends uploader {...
a ladenka na to:
Class 'uploader' not found
preco Kdyby\Doctrine riesi tieto veci?
Editoval duskohu (28. 11. 2013 13:16)
- Filip Procházka
- Moderator | 4668
Kdyby\Doctrine rozhodně nekontroluje žádné tvoje třídy, nijak to spolu absolutně nesouvisí, tohle kontroluje sám DI Container v Nette.
Ohledně té výjimky s přejenováním – vsadil bych se, že máš špatnou verzi kdyby/doctrine nainstalovanou, zkus si pořádně přečíst jak se to instaluje a udělat po správně.
- duskohu
- Člen | 778
@Filip Procházka instaloval som to takto, a ladenka hadze toto
"kdyby/doctrine": "@dev",
"kdyby/annotations": "@dev",
"kdyby/doctrine-cache": "@dev",
"kdyby/events": "@dev",
"kdyby/console": "@dev"
extensions:
console: Kdyby\Console\DI\ConsoleExtension
events: Kdyby\Events\DI\EventsExtension
annotations: Kdyby\Annotations\DI\AnnotationsExtension
doctrine: Kdyby\Doctrine\DI\OrmExtension
- Jiří Nápravník
- Člen | 710
No pokud to píše: Class Nette\Config\CompilerExtension has been renamed to Nette\DI\CompilerExtension. Tak prostě jednoduše v tom nette.ajax.js to přejmenuj na Nette\DI\CompilerExtension ne? Podle mě by tohle správně mělo házet chybu i bez kdyby/doctrine. Protože v 2.1 je to přejmenované.
- Filip Procházka
- Moderator | 4668
@duskohu, @enumag Vojtovo rozšíření jednoduše nepočítá s tím přejmenováním, kdybys nahradil
use Nette\Config\CompilerExtension;
za
use Nette\DI\CompilerExtension;
v souboru nette.ajax.js/history/Extension.php
tak to
pojede.
Nebo taky jednoduše zaregistruješ Vojtovo extension až po Kdyby
rozšířeních a mělo by se to též rozjet, protože v Kdyby se vytváří
aliasy na tu třídu, tedy jako vedlejší efekt opraví kompatibilitu v
nette.ajax.js
(teoreticky).
@Quinix ano mam to tak, takze to mam specifikovat na konkretne adresare?
To je velmi dobrý nápad ;)
Ještě je tu varianta, nekopírovat tyhle věci do app/
, ale
udělat si zvláštní složku libs/
do které tyhle knihovny,
které nejsou přes composer, budeš kopírovat. Samozřejmě je třeba
počítat s tím, že pro composer se používá vendor/
složka.
- akadlec
- Člen | 1326
Je to normální chování že když provedu změnu entity v entitě které jsou ve vazbě one-to-one dojde k persistování pouze parent entity? U té kolekce jak je zmíně výše bych to chápal, ale u jednoduché vazby se mě to zdá nežádoucí. Vytáhnu si entitu které měním parametry a také měním parametry druhé entitě ve vazbě tak by se mě líbilo aby došlo flushnutí změn v celém stromu.