Doctrine2 implementace modelové vrstvy
- gavec
- Člen | 68
Po nějakém čase se vracím zpět k Nette v rámci nově vznikajícího projektu a řeším aktuálně problém jak implementovat model.
Používám Kdyby/Doctrine a ikdyž jsem si přečetl spoustu článků, nedaří se mi model naimplementovat tak, jak bych si ho představoval. Především mám problém s předáváním závislostí, které bych chtěl mít definované na jednom místě v config.neon, nikoliv pomocí @inject anotace nebo inject metod.
Chtěl bych využít 5-ti vrstvé architektury, tak jak je to popsané na zdrojáku.
Implementace je poměrně pěkně popsaná i zde. Nicméně ani ta nezafungovala. Konkrétně předání DAO do BarModel ( v mém případně do fasády).
namespace App\Facade;
use App\DAO\Offer as OfferDAO;
class OfferFacade
{
public function __construct(OfferDAO $offerDAO)
{
$this->offerDAO = $offerDAO;
}
...
}
Padá to s chybou Unable to create service ‚doctrine.dao‘, value returned by factory is not Kdyby\Doctrine\EntityDao type i přes to, že v entitě mám definovou repository class, kterou mám i jako type hint v mé fasádě.
config.neon
services:
- App\Facade\OfferFacade(@doctrine.dao("App\\Entity\\Offer"))
Entita:
/**
* Offer
* ...
* @ORM\Entity(repositoryClass="\App\DAO\Offer")
*/
class Offer extends \Kdyby\Doctrine\Entities\BaseEntity
{
...
}
Dále jsem se dočetl, že DAO je v aktuální verzi deprecated a doporučuje se používat Repository s persistencí pomocí EntityManageru. Rád bych však fasádě předával už konkrétní Repository(případně DAO) a ne celý EntityManager.
Byl by někdo tak ochotný a uvedl nějaký vzorek implementace mých požadavků včetně definování services v config.neon? Jak jsem zmínil, proletěl jsem snad celý google, ale většina článků byla 2 a více let stará, tak možná i to je důvod, proč se mi implementace nedaří zprovoznit. Byl bych tedy rád, kdyby mě někdo seznámil s aktuálními best practises.
Editoval gavec (28. 6. 2015 16:30)
- gavec
- Člen | 68
Třídou myslíš přepravku nesoucí jen tyto 2 property? Je to skutečně potřeba? Neexistuje jednoduchý způsob, jak si zaregistrovat konkrétní repository jako službu a tu pak předat do facade? Nebo v jiném případě předat do facade jak zmiňuješ celý EntityManger a z něj si pak potřebné Repository vytáhnout?
Např ze Symfony2 mám zkušenost s tím, že pokud si předám Repository, mám přístup i k funkcím typu find, findAll atp? V čem je tento přístup špatný?
Bylo by to možné znázornit na konkrétním kódu, jak se něco takového implementuje?
Editoval gavec (28. 6. 2015 23:17)
- Azathoth
- Člen | 495
Asi jsem se špatně vyjádřil, myslel jsem, aby sis do Facade poslal em a
z něj vytáhl repository. Repository má metody finf, findAll, jak znáš ze
symfony. Jinak ohledně best practises se doporučuji zeptat zde https://gitter.im/nette/nette
já konkrétn repository neregistruji vždy si ji vytáhnu z em, a
složitější find metody a DQL skládám ve facade.
- gavec
- Člen | 68
Právě v tomto kroku je ten problém. EM ve facade mám. Repository konkrétní entity si taky získám. Nicméně o metodách find, findAll stále neví.
Odebral jsem dědičnost konkrétní repository na EntityRepository, která stejně vyhazovala exception, takže předpokládám že to k base metodám nemá jak přistoupit.
Bylo by možné ukazát na jednoduchém příkladě, jak vytváříš instanci facade včetně registrace teto služby? Předpokládám, že to řešíš pomocí anotace @inject pokud ji vytváříš stejně jako já v presenteru. Ve facade mám type hint na \Kdyby\Doctrine\EntityManager, takže tam taky není moc prostoru udělat chybu. Nicméně k base metodám v EntityRopository nejde stále přistoupit.
Napadá mě už jen, jestli nemá repository konkrétní entity dědit z něčeho jiného, když z EntityRepository to není košér.
- Šaman
- Člen | 2666
Teď jsem se Doctrine prokousával a možná chápu, v čem je problém. Ty
si píšeš vlastní třídu FooRepository
? To je ale to, co
Azathoth nedoporučuje, viz.
…já konkrétn repository neregistruji vždy si ji vytáhnu z em, a složitější find metody a DQL skládám ve facade.
Doctrine sama si vytváří repoziáře se základními metodami. A pokud
potřebuješ něco jiného, než tyto základní find
y, tak si to
dej do fasády. Taku se mi tento způsob zatím moc nezamlouvá, ale asi je to
o zvyku.
Takže žádné repoziráře nepiš, tahej je z EM a metody typu
findLast5()
si piš do fasády. Následně pak používej fasády
tak, jako jsi nejspíš doteď používal repository.
- gavec
- Člen | 68
Teď už tomu rozumím. Díky Šamane.
Jen by mě ještě zajímalo, jak do facade dostanu EntityRepository, protože getRepository volané na EntityManagerovi vyžaduje Entitu, ze které si FooRepository získá samo. Takže čekám nějaký jiný způsob než přes EntityManagera. Nebo v případě, že nedefinuju repositoryClass v entitě, použije se defaultně EntityRepository?
Omlouvám se za neověření, ale nemám to teď možnost vyzkoušet a celkem mě to zajímá.
Tím co psal Azathoth jsem pochopil to, že si repository neregistruje jako služby, aby si je předal přímo do facade, ale předá si celý EntityManger a z něj pak vytáhne svoji vlastní FooRepository :) Teď už chápu, že si je nevytváří vůbec.
Příjde mi to ale jako velká škoda. Na vlastní repository jsem byl zvyklý a zdá se mi to jako dobré a přehledné řešení. Dokázali by jste někdo vysvětlit jaké je tam úskalí, že se vlastní repository nepoužívají?
- Azathoth
- Člen | 495
Na to bude nejlepší se zeptat asi @FilipProcházka možná na chatu
nebo tady…
Jinak pokud nechceš doctrine logiku (sestavování dotazů) ve fasádě, tak
součástí Kdyby/Doctrine je QueryObject, který by se ti možná mohl
hodit. https://github.com/…resultset.md
- leninzprahy
- Člen | 150
Vlastní repository použít můžeš, např:
namespace App\Repository;
class Article extends \Kdyby\Doctrine\EntityRepository {
public function findLast5() {
....
}
}
ale musíš to říct příslušné entitě
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="\App\Repository\Article")
* @ORM\Table
*
*/
class Article {
...
}
fasáda pak může vypadat třeba:
namespace App\Facade;
use Kdyby\Doctrine\EntityManager;
class Blog {
/** @var \Kdyby\Doctrine\EntityManager */
private $em;
/** @var \Kdyby\Doctrine\EntityRepository */
private $articleRepository;
/** @var Rebilling\Repository\Account */
private $commentsRepository;
function __construct(EntityManager $em) {
$this->em = $em;
$this->articleRepository = $em->getRepository(App\Entity\Article::class);
$this->commentsRepository = $em->getRepository(App\Entity\Comments::class);
}
public function findLast5Articles() {
return $this->articleRepository->findLast5();
}
...
}
- gavec
- Člen | 68
@Azathoth
Po tom, co jste mi vysvětlili, že se vlastní repository nedá použít
konečně chápu, k čemu QueryObject je :)
@leninzprahy
Příklad, který si uvedl je kopie mé představy, resp. přesně takto to mám
naimplementované, ale ve výsledku to nefunguje. Takto je ve facade možné
použít metodyz vlastní repository (App\Repository\Article). V případě
potřeby find, findAll atp to začne vyhazovat vyjímku ve smyslu, že
v Doctrine\ORM\EntityRepository (nikoliv Kdyby\Doctrine\Repository) není
nastaven EM ($this->_em = NULL), pokud se nepletu.
Tuto verzi máš ověřenou a funkční? Je totiž možné, že mám špatně předané závislosti. Pokud to je funkční implementace, mohl bys doplnit ještě kde předáváš jaké závislosti? Asi by mě zajímal hlavně zápis v config.neon.
Editoval gavec (29. 6. 2015 14:10)
- leninzprahy
- Člen | 150
gavec napsal(a):
@leninzprahy
Příklad, který si uvedl je kopie mé představy, resp. přesně takto to mám naimplementované, ale ve výsledku to nefunguje. Takto je ve facade možné použít metodyz vlastní repository (App\Repository\Article). V případě potřeby find, findAll atp to začne vyhazovat vyjímku ve smyslu, že v Doctrine\ORM\EntityRepository (nikoliv Kdyby\Doctrine\Repository) není nastaven EM ($this->_em = NULL), pokud se nepletu.
\Kdyby\Doctrine\EntityRepository
jen rozšiřuje funkčnost
\Doctrine\ORM\EntityRepository
ze které dědí.
Tuto verzi máš ověřenou a funkční? Je totiž možné, že mám špatně předané závislosti. Pokud to je funkční implementace, mohl bys doplnit ještě kde předáváš jaké závislosti? Asi by mě zajímal hlavně zápis v config.neon.
Jo, normálně mi to funguje :)
V configu mám jen registrované rozšíření, nastavení doctriny a
tu fasádu
extensions:
doctrine: Kdyby\Doctrine\DI\OrmExtension
...
doctrine:
user: ...
...
metadata:
App\Entity: '%appDir%/Entity'
services:
- App\Facade\Article
- gavec
- Člen | 68
Tak jsem to prošel a mám to totožné s tebou. Jen mi to vyhazuje chybu „Call to a member function getUnitOfWork() on a non-object“ v Doctrine\ORM\EntityRepository::findBy(). Což je celkem logické, když v constructoru vyžaduje EntityManagera a ClassMetadata a dědí od něj Kdyby\Doctrine\EntityRepository.
@leninzprahy Můžeš mi vysvětlit, jak ti to může fungovat? :)
- leninzprahy
- Člen | 150
gavec napsal(a):
Tak jsem to prošel a mám to totožné s tebou. Jen mi to vyhazuje chybu „Call to a member function getUnitOfWork() on a non-object“ v Doctrine\ORM\EntityRepository::findBy(). Což je celkem logické, když v constructoru vyžaduje EntityManagera a ClassMetadata a dědí od něj Kdyby\Doctrine\EntityRepository.
@leninzprahy Můžeš mi vysvětlit, jak ti to může fungovat? :)
A nepřepisuješ v tom svém Repository konstruktor? Jestli ano, tak musí obsahovat
public function __construct($em, \Doctrine\ORM\Mapping\ClassMetadata $class) {
parent::__construct($em, $class);
}
Pomocí DIC tam stejně žádné závislosti nedostaneš, nevyrábí ho totiž DIC, ale EntityManager, respektive RepositoryFactory, takže ve většině případů tam konstruktor být vůbec nemusí…
- Filip Procházka
- Moderator | 4668
Co se týče repozitářů jako služeb, tak tohle chci rozhodně podporovat, ale zatím to není možné používat, kvůli cyklickým závislostem.
Doporučuji (alespoň prozatím) přístup s psaním vlastních facades jako ukazuje @leninzprahy, až to fixnu tak se z toho možná vyklube něco zajímavějšího, ale tak jako tak Facades podle mě v modelech budou mít stále důležité místo.
Dědění repozitářů nemám rád, protože to svádí k psaní 200 metod do jedné třídy, ve kterých se často brutálně duplikuje logika. To právě mají řešit QueryObjecty.