Service - odstínění Presenteru a Komponent od EntityManažeru

Patrik Votoček

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í”((nikoli jediné správné)) ř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”((přihlášení)).

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' => "[email protected]",
	'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ě.

« »