Service - odstínění Presenteru a Komponent od EntityManažeru
V předchozích dvou článcích jsem se snažil dopátrat, jak kdo implementujete modelovou Service vrstvu pro Doctrine ORM 2 v Nette Frameworku. Dlouhodobě totiž hledám ideální řešení, protože jsem se svým stávajícím lehce nespokojen. Ono funguje a je dokonce i celkem ohebné a použitelné ve spoustě případů a situací. Nicméně stále mám pocit, že je tam něco špatně a tak jsem se vydal pátrat, co to je.
Vzal jsem tedy své stávající řešení a našel dva body, které mě trápí nejvíce. Jak se ale v komentářích ukázalo, tyto dva body spolu úzce souvisí (ještě více, než jsem se původně domníval), proto se na ně nedá koukat odděleně. Díky mému oddělení a snaze soustředit se opravdu jen na ony dva problémy došlo k informačnímu šumu, který bych chtěl v tomto článku napravit.
První chybou bylo, že jsem se v článcích zmiňoval spíše o celé service vrstvě, i když mi ve skutečnosti šlo spíše o service třídu.
Základní dělení service vrstvy
Service vrstvu jsem – už když jsem s doctrine začínal – začal dělit na dvě části. A to na část, která načítá data a část, která s daty manipuluje. Část, která načítá data je v doctrine ve své podstatě již vyřešena. Representuje ji Repository a je poměrně pevně svázaná s entitou (nechme stranou, jestli je to dobře – je to funkční řešení a zatím jsem nenarazil na případ, kdy by to vadilo). Nicméně část, která se stará o manipulaci s daty v doctrine ve své podstatě, chybí (ano, můžete komunikovat přímo s entity manažerem, ale je to takové – no vždyť víte). A tohle je ta část, kde přichází na scénu Service třída, kterou je potřeba vymyslet.
Service třída
Stávající implementace jsou tedy metody:
- create – kde prvním parametrem je pole, kde klíč odpovídá property v entitě a hodnota je její hodnotou
- update – kde prvním parametrem je entita a druhým parametrem je pole (stejné jako v prvním případě)
- delete – kde prvním je entita
Jak to celé funguje? Vytvoří se instance Service, jako služba v konstruktoru dostane instanci entity manžeru a fullclass entity, které se týká. Při zavolání create se spustí createEntityPrototype (vytvoří prázdnou instanci entity – u entit nepoužívám „parametrizované“ konstruktory). Poté se zavolá fillData (naplní entity hodnotami z pole). Pak se vytvořená entita připojí k entity manažeru a pokud nebylo řečeno jinak (poslední parametr metody), tak se změny flushnou a vrátí se hotová entita. Podobně fungují i ostatní metody.
Honza Tichý v komentářích
napsal: Svazuje Tě to v možnosti mít pro jednu entitu víc různých
servisů, či naopak mít servis pracující s více entitami najednou.
. To
byla věc, nad kterou jsem si dlouho lámal hlavu a stále trochu lámu. Jako
příklad uvádí identitu a různé způsoby autentizace.
Tento příklad řeším tak, že mám entitu Identity a entitu Credentials (do budoucna je v plánu entita OAuth , jako další způsob přihlašování). Na načítání těchto dat není nic zajímavého. Zajímavější bude registrace nového uživatele.
Než bych složitě popisoval co a jak, raději ukážu kód:
class CredentialsService extends \Nella\Doctrine\Service
{
public function create(array $values, $withoutFlush = FALSE)
{
$service = $this->getContainer()->getService('IdentityEntity');
$values['identity'] = $service->create($values, TRUE);
return parent::create($values, $withoutFlush);
}
}
$values = array( /* v takovémto formátu dostanu data (třeba z formuláře) */
'username' => "vrtak-cz",
'password' => "tajneheslo",
'email' => "muj@email.cz",
'dispalyName' => "Patrik Votoček",
);
$service = $form->getDoctrineContainer()->getService('CredentialsEntity');
$entity = $service->create($values); /* vrátí naplněnou instanci CredentialsEntity s asociací na vyplněnou IdentityEntity */
Věc, která se mi nelíbí, je „service locator“ přístup ke službě
obstarávající IdentityEntity. Pokud bych ale tuto službu
vstřikoval v konstruktoru služby obsluhující CredentialsEntity ,
tak by se například u mazání vytvářela naprosto zbytečně.
Tak to je tedy důvod, proč jsem nechápal – a v podstatě stále
nechápu – obsluhu více entit jednou service nebo naopak jedné entity
pomocí více service.
Posledních pár dní přemýšlím, jak to vylepšit a hlavně, jak zmiňuje
Honza a Filip,
zda vůbec a jak se zbavit $entityClass v service třídě.
5 komentářů
Karel Hák
nový
Není závislost na $entityClass v servisní třídě způsobena tím, že vní jsou věci, které tam být nemají?
Koukal do tvé implementace (kterou jsem se zprvu inspiroval), tak mi přijde, že metody update, delete, create tam prostě nepatří. To jsou operace, které se podle mně hodí spíše do repository (a je to tak uvedeno i v článku 5 vrstev modelu od Honzy Tichého).
Já to chápu tak, že do servisní vrstvy už podle mně entity manager nepatří (a už vůbec ne nějaké zpracování PDO vyjímek), ale pouze do nich pošlu repository, s kterými potřebuje pracovat. Každopádně s Doctrine teprve začínám, takže si rád vyslechnu proti argumenty, pokud si budete myslet, že co jsem napsal je blbost :)
Patrik Votoček
nový
Pokud se bavíme o Repository tak jak ji popisuje Honza ve svém článku pak máš pravdu. Pokud se ale bavíme o Repository jako patternu, tj tak jak ji já používám. Pak pravdu nemáš, protože save / update / delete nemají u repository co dělat (viz. také komentář pod Honzovým článkem http://www.phpguru.cz/…rstev-modelu#…).
Pokud to shrnu tak u mě DAO je reprezentováno dvěma třídami. Jednou z nich je Repository (READ) a druhou Service (CREATE, UPDATE, DELETE).
HosipLan
nový
[1] Podle mě to blbost není. Sdílím úplně stejný pohled na věc. Jenom mi bylo blbé to psát po třetí, ke třetímu článku v sérii :)
Patrik Votoček
nový
Já ale neříkám že to je blbost :-). Já jenom říkám že to tak nepoužívám a proč to tak nepoužívám.
mbskot
nový
Zdravim, nakoniec teda akym sposobom sa dostavas k repositorom? Mas v kazdej takejto sluzbe (alebo v baseService) getRepository()?