Doctrine2 implementace modelové vrstvy

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
gavec
Člen | 68
+
0
-

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)

Azathoth
Člen | 495
+
+1
-

řekni si v konstruktoru o entitymanager a z něj si vytáhni respository. Dao je deprecated.

gavec
Člen | 68
+
0
-

Jak je pak možné použít EM i v Repository(DAO) třídě? Aktuálně ji rozšiřuju o \Kdyby\Doctrine\EntityRepository, které EM nemá nastavené.

Editoval gavec (28. 6. 2015 17:10)

Azathoth
Člen | 495
+
+1
-

Myslím, že best practise je nedědit Repository třídu, takže myslím, že bude lepší si udělat třídu, která bude mít jako property EntityRepository a EntityManager.
Navíc repository neumožňuje mazat a persistovat. Na to je entity manager.

gavec
Člen | 68
+
0
-

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

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

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

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í findy, 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
+
0
-

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

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

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

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

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

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

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í…

gavec
Člen | 68
+
0
-

A jééje. Nechal jsem tam prázdný konstruktor a pak se můžu divit :O… Tak díky @leninzprahy.

Filip Procházka
Moderator | 4668
+
0
-

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.