DI, služby, továrničky a implementace rozhraní

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

Po delší době jsem se rozhodl napsat jednu aplikaci v Nette.
A pořád nějak úplně nerozumím domu, jak je navržen DI.

Obecně od DI očekávám dvě věci
 – že mi umožní jednu službu snadno používat z více míst
 – že mi umožní službu nahradit v aplikaci interfacem a v konfiguraci pak zvolit konfiguraci
 – že bude řešit vzájemné závislosti mezi jednotlivými službami

První use case je jasný

`services
App\Api\WirelessAPI

`

a Presenteru pak

	/**
	 * @inject
	 * @var WirelessAPI
	 */
	public $api;

Ale jak docílit toho, abych mohl v Presenteru napsat

	/**
	 * @inject
	 * @var IAPI
	 */
	public $api;

A v configu pak jen nastavit, že chci použít WirelessAPI jakožto třídu implementující interface IAPI.

S interfacem dokáží pracovat továrníčky, ale jak to chápu já, tak jediný smysl továrníček je lazy inicializace služeb, místo služby samotné dostanu továrníčku a nad ní si sám musím zavolat create() pokud službu potřebuji.

A jediný důvod pro to psát něco jako

interface IBarFactory
{
	/**
	 * @return Bar
	 */
	public function create();
}

je pouze ten, aby IDE vědělo jako službu továrnička vyrobí a fungovalo autocomplete.
Mimochodem, proč neexistuje interface pro továrničky přímo v nette? Nebylo by pak nutné psát ručně tu metodu create a bylo by jasné, že tam musí být.

Popřípadě co kdybych chtěl o použité implementaci služby rozhodovat až za běhu, například volit jinou službu pro debug prostředí a jinou pro produkční prostředí?

Děkuji za odpověď.

Oli
Člen | 1215
+
+2
-

Interface můžeš úplně klidně použít. Má to jen 1 podmínku, která jde obejít. V aplikaci musí být jako služba registrována právě 1 třída implementující toto rozhraní. Jinak by aplikace nevěděla, kterou z těch interface má použít. To se ale nechá obejít direktivou autowire: off. Potom se taková služba musí předat ručně.

services:
	- MyCoolClass # ktera implementuje rozhrani IClass
	anotherClasWithInterface:
		class: MyVeryCoolClass # ktera implementuje to samé rozhrani IClass
		autowire: off # autoamaticky se nepreda jako zavislost
	- CoolService # Vyuziva MyCoolClass
	- VeryCoolService(@anotherClasWithInterface) # dostane MyVeryCoolClass
class CoolService
{
	public function __construct(IClass $class){}
}
// ...
class VeryCoolService
{
	public function __construct(IClass $class){}
}
jiri.pudil
Nette Blogger | 1032
+
+2
-

Jak píše @Oli, autowiring podle rozhraní funguje v nette/di úplně out-of-the-box.

Když chceš v debug prostředí službu nahradit, v configu si ji pojmenuj a v config.local.neon přepiš:

services:
	api: DebugAPI

Továrny mají trochu jiný use case, než popisuješ. Služby se standardně vytvářejí vždy až ve chvíli, kdy je někde reálně potřebuješ. Vytvoři se ale vždy jedna jediná instance, která se pak předává všude, kde je vyžadovaná, což není vždy žádoucí. Továrnu tedy využiješ všude tam, kde potřebuješ vytvářet více nezávislých instancí, typicky třeba u komponent.

Konvence s rozhraním a metodou create je jen zjednodušení práce, kdy za tebe Nette samo vygeneruje implementaci; schválně se podívej do temp/cache/Nette.Configurator na vygenerovaný kontejner. Ten interface pak vyžaduješ jako závislost, proto si jej musíš napsat sám.

Editoval jiri.pudil (22. 7. 2017 9:16)

tprochazka
Člen | 13
+
0
-

Díky moc za odpověď, jdu to vyzkoušet.

Teoreticky by se šlo vyhnout psaní i toho interfacu, kdyby to fungovalo tak, že napíšu do konfigu název, jednou pustím aplikaci, patřičný interface se vygeneruje a pak ho můžu začít používat. Alespoň tedy PHP storm mi ukazuje i vygenerované soubory, což je potřeba už kvůli debuggingu, ale uznávám, že to asi není příliš běžné řešení, spíše se na to používají generátory kódu.

jiri.pudil
Nette Blogger | 1032
+
+3
-

kdyby to fungovalo tak, že napíšu do konfigu název, jednou pustím aplikaci, patřičný interface se vygeneruje a pak ho můžu začít používat

To mi připadá dost krkolomné kvůli takové prkotině. Ale máš pravdu, že je psaní těch továrních rozhraní po chvíli dost repetitivní. Taky mě to kdysi přestalo bavit, tak jsem si na to napsal plugin :)