Podpora pro automatické injectnutí kruhových závislostí služeb v Containeru
- mystik
- Člen | 312
Narazil jsme teď v jednom projektu na problém kruhových závislostí služeb. Jako doporučované řešení jsem na fóru našel tohle:
https://forum.nette.org/…-sluzeb-v-di#…
Takovou továrničku samozřejmě není problém si napsat, ale napadlo mě proč to nedělá přímo Container automaticky?
Jak by to mohlo fungovat:
- createService vy se rozdělilo na dvě části constructService a initService
- constructService by pouze vytvořila objekt service a předala závislosti v konstruktoru
- initService by dělala zbytek – zavolání setup, inject závislostí do metod a property
- pokud bych si vyžádal službu z Containeru tak by se nejdříve zavolala constructService, ta by mohla vyžadovat pro závislosti konstruktoru získání dalších služeb, ale u všech by se volalo pouze constructService
- pokud by vznikla kruhová závislost v závislostech konstruktoru tak by to stejně jako teď hodilo exception
- pokud ne tak by se postupně vytvořily všechny služby potřebné pro constructor injection
- teprve potom by se zavolaly pro všechny tyto služby jejich metody initService, které by provedly dodatečné injectnutí. Tady už nám kruhové závislosti nevadí, protože služby už existují. Jen je musíme pospojovat.
Je nějaký důvod proč to Nette takto nedělá? Neměl by asi být takový problém to doplnit. Když tak můžu připravit pull-request. Jen sem se chtěl ujistit, že tohle chování není by-design.
Editoval mystik (27. 3. 2014 14:54)
- David Matějka
- Moderator | 6445
Zajimavy, presne nad timhle chovanim jsem premyslel vcera a napadlo me stejny reseni. Napadlo me par technickych problemu, ktery by se snad nechaly vyresit, je tam vsak jeden pomerne zasadni problem: nejaka sluzba muze (i kdyz by nemela) v konstruktoru spolehat na to, ze zavislost, kterou uz obdrzela, je plne funkcni a inicializovana.
- Filip Procházka
- Moderator | 4668
Vůbec se mi nelíbí jak jsi to navrhl, celý container se strašlivě zkomplikuje.
Pokud máš takové závislosti v aplikaci, může to být signálem že máš blbě navrženou aplikaci.
- mystik
- Člen | 312
@matej21: To mě taky napadlo, ale v takovém případě už to stejně nejde nijak automatizovaně vyřešit.
@Filip: Jestli ty závislosti nejsou chyba návrhu sem přemýšlel jako první. Ale nenapadl mě způsob jak se jich zbavit aniž by to podstatně zkomplikovalo aplikaci.
Myslím, že Container potom o tolik komplikovanější nebude, pokud sem tedy něco nepřehlídl. Rozdělení createService* na dvě části je jen minimum kódu navíc. Jediné co by se změnilo dál by bylo, že v $this->creating by se seznam vytvářených služeb držel až do chvíle, kdy se série volání vrátí do první úrovně creteService(), místo aby se odtud service odstranila hned. A po návratu na první úroveň by se pak nad všemi service v $this->creating v cyklu zavolaly ty initService().
- David Matějka
- Moderator | 6445
Myslim, ze takovyhle univerzalni reseni by toho vic rozbilo nez spravilo. Myslim, ze DIC si nemuze dovolit predavat zavislosti, ktere nejsou plne inicializovane.. Maximalne pokud by to bylo implementovane tak, ze by container builder dokazal ve fazi kompilace rozpoznat kruhove zavislosti a tohle reseni umel aplikovat jen na dotcene sluzby.
Ale kruhova zavislost se ve vetsine pripadech necha odstranit snadno, takhle by to svadelo ke psani spatne navrzenych aplikaci :) vcera jsem se teda taky dostal do stadia, kdy to neslo odstranit. Nakonec jsem to vyresil proxy tridou s accessorem na dotcenou sluzbu.
EDIT: kdyz uz jsem u toho, tak rovnou napisu reseni, kdyby nekdo potreboval
interface IFoo
{
public function doFoo();
}
interface IBar
{
public function doBar();
}
class FooImplementation implements IFoo
{
protected $bar;
public function __construct(IBar $bar)
{
$this->bar = $bar;
}
public function doFoo()
{
....
}
}
class BarImplementation implements IBar
{
protected $foo;
public function __construct(IFoo $foo)
{
$this->foo = $foo;
}
public function doBar()
{
....
}
}
services:
- FooImplementation
- BarImplementation
Ted nam tam vznikla kruhova zavislost, vytvorim si nette generovany accessor pro FooImplementation
interface FooAccessor
{
/**
* @return IFoo
*/
public function get();
}
a proxy tridu
class FooProxy implements IFoo
{
protected $fooAccessor;
public function __construct(FooAccessor $accessor)
{
$this->accessor = $accessor;
}
public function doFoo()
{
return $this->fooAccessor->get()->doFoo();
}
}
a neon upravime:
services:
fooImplementation:
class: FooImplementation
autowired: false
-
implement: FooAccessor
create: @fooImplementation
- FooProxy
- BarImplementation
Editoval matej21 (27. 3. 2014 16:06)
- David Matějka
- Moderator | 6445
@mkoubik: nn factory vytvari, accessor vytahne z containeru tu stejnou instanci
- mystik
- Člen | 312
@matej21: No další možnost by byla tohle aplikovat jen na základě nějaké konfigurace.
services:
fooService:
class: \FooService
lazyInit: true
Container by takhle označené služby inicializoval až odloženě jak jsme navrhoval. U ostatních služeb by to ale šlo stejně jako dosud. Pokud si něco takhle označím tak beru na vědomí že to ostatní service můžou do konstruktoru dostat neinicializované.
@mkoubik: Nejde o to že by to rozseknout nešlo, ale dost mi to zbytečně zkomplikuje aplikaci. Proto hledám jednodušší řešení.
Editoval mystik (27. 3. 2014 17:43)