DI, služby, továrničky a implementace rozhraní
- tprochazka
- Člen | 13
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
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
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
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
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 :)