Předávání služeb do Presenteru pomocí DI

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 8136
+
0
-

Služby předává DI do presenteru skrze metodu setContext() (případně __construct, see), kterou zavolá PresenterFactory. Stačí v presenteru metodu napsat a uvést argumenty a pomocí auto-wiringu budou předány:

class SignPresenter extends BasePresenter
{
    public $user;
    public $model;

	function __construct(Nette\Web\User $user, ArticleModel $model) // nebo setContext()
	{
		$this->user = $user;
		$this->model = $model;
	}

	...

}

Toto je zatím experimentální feature, implementace se může změnit. Bylo by možné také vytvořit samostatnou CompilerExtension pro řízení presenterů a továrny.

Každopádně mějte na paměti, že podobné předávání objektů pomocí DI zabraňuje lazy-loadingu, tudíž tam, kde by to zpomalilo aplikaci, si předejte jen továrnu na službu.

Honza Marek
Člen | 1664
+
0
-

Neboli metoda setContext přijme roli jakéhosi pseudokonstruktoru, zatímco v opravdovém konstruktoru sedí $context jako žába na prameni a neni způsob, jak jej odtamtud vykopat.

To se mi moc nelíbí. Předchozí stav, kdy existoval setter na context a konstruktor byl volný, mi vyhovoval víc. I když to pochopitelně taky nebylo akademicky čisté.

Rozumným kompromisem by bylo, kdyby byla zachována snadná implementovatelnost původního setContextu. Třeba že by metoda getContext nebyla final a byla by používána i v metodách jako getHttpRequest apod.

David Grudl
Nette Core | 8136
+
0
-

To není pseudokonstruktor, ale zcela regulérní setter injection (nikdo netvrdí, že setter může přijímat právě jeden objekt).

Ale ok, zkus to navrhnout tak, jak by se ti to líbilo. Jen je potřeba počítat s tím, že už samotný UI\Presenter má nějaké závislosti a pochopitelně se mohou časem lehce měnit.

David Grudl
Nette Core | 8136
+
0
-

Prohodil jsem zkušebně chování __constructor a setContext, tj. pro předávání vlastních služeb se místo setContext() použije konstruktor. Ten commit berte čistě jako výzvu k diskusi, protože každé řešení má své pro a proti.

Z hlediska DI je čistý způsob předávat závislosti v konstruktoru. Kupodivu proti tomu chování vystartovali kluci s Media, prý jim to blokuje konstruktor. Beru to jako unáhlenost, protože je to samozřejmě blbost.

Předávání závislostí přes konstruktor má ten problém, že se složitě řeší přidávání dalších závislostí v potomcích. Potomek prostě musí uvést své závislosti a všechny závislosti rodiče, které mu předá přes parent::__construct(). Což jednak psaní konstruktorů značně komplikuje, z hlavy ho nenapíšete a především způsobí obrovské problémy, když se rodičovské závislosti změní. Což se klidně může stát.

Z toho důvodu UI\Presenter nabízí (resp. nabízel) pomocnou metodu setContext(), přes kterou bylo možné předat vlastní závislosti, a to stejným způsobem jako u konstruktoru, aniž by bylo nutné jej přepisovat.

Méně čistá je metoda, kdy se konstruktor vyhradí jen pro závislosti potomků a závislosti rodiče UI\Presenter se předají jiným způsobem (tu jsem právě experimentálně komitnul).

Otázka je, kterou cestu nakonec zvolit.

llook
Člen | 407
+
0
-

Začnu tím, co já bych od toho očekával:

  1. Všechny nutné závislosti by se měly ukojit během vytváření objektu, tj. ideálně konstruktorem.
  2. Lazy-load služby, které se využijí jen někdy.
  3. Předejít možné budoucí změně hlavičky konstruktoru při změnách v závislostech.
  4. Objekt by měl dostat pouze ty služby, které fakt potřebuje.

Teď presenter dostává celý systémový service locator (context), což splňuje první tři body, ale není tak docela jasné, co všechno odtamtud může vyžadovat.

Možným řešením by byl samostatný kontejner (sub-context), který by uspokojoval pouze potřeby třídy UI\Presenter a nic víc. Zhruba něco na tenhle způsob:

class PresenterContext
{
	private $systemContext;
	public function __construct(DI\Container $systemContext)
	{
		$this->systemContext = $systemContext;
	}

	public function getHttpRequest()
	{
		return $this->systemContext->httpRequest;
	}

	// ...atd.

}

Výchozí implementace by pouze takto zapouzdřovala podmnožinu service locatoru, ale dala by se v případě potřeby nahradit. Konstruktor bázové třídy by pak vyžadoval pouze tento PresenterContext, další závislosti v potomcích by si pak už pořešili autoři těch potomků (nejčastěji asi dalšími parametry konstruktoru).

David Grudl
Nette Core | 8136
+
0
-

llook napsal(a):

Možným řešením by byl samostatný kontejner (sub-context), který by uspokojoval pouze potřeby třídy UI\Presenter a nic víc. Zhruba něco na tenhle způsob:

Nikoliv, jednoznačným řešením je předávat jen služby, které presenter opravdu potřebuje. Ale z důvodu zpětné kompatibility zatím potřebuje celý kontejner, což se v další verzi dá jako deprecated a později zruší. V tuto chvíli je kontejner regulérní závislost presenteru.

dundee
Člen | 23
+
0
-

U tříd, které si uživatelé fw poděďují, cítím značný rozdíl mezi závislostmi frameworku (o těch nechci pokud možno nic vědět) a závislostmi té poděděné třídy. Ty by naopak měli být co možná nejvíce vidět.

Obecně vidím několik variant:

  • všechny závislosti přes konstruktor – značná nevýhoda v tom, že presenter je pak závislý na implementačních detailech fw a při změně závislostí UI\Presenteru je potřeba měnit své presentery
  • závislosti uživatele přes konstruktor, závislosti fw setterem – IMHO nejčistší varianta. Uživatel pak může používat DI v presenterech úplně stejně jako je zvyklý kdekoliv jinde a není závislý na implementaci fw.
  • závislosti uživatele setterem, závislosti fw přes konstruktor – relativně dobré řešení, ale WTF pro všechny lidi, kteří už DI použili někde jinde a očekávají, že pro své závislosti mají použít konstruktor.
  • property injection (ala Java) pro vlastní závislosti – nejsnadnější pro psaní kódu (napíše se jen anotace @inject k property), ale třída pak už nedává tak jasně najevo, jaké jsou její závislosti (je potřeba pročíst kód třídy).

Editoval dundee (28. 2. 2012 8:41)

Filip Procházka
Moderator | 4668
+
0
-

Mě napadlo

$presenter = new UI\Presenter($moje, $zavislosti);
$presenter->run($request, $application);

Kdyby se do Application přidaly gettery na Http\Request a pár dalších, které se do ní stejně předávají, Presenter by byl závislý pouze na Application, kterou by dostal až při spuštění.

https://github.com/…tte/pull/564 – fungující pull

Napadá někoho nějaká nevýhoda?

Editoval HosipLan (28. 2. 2012 9:17)

jantichy
Člen | 24
+
0
-

Zkopíruju sem svůj příspěvek, co jsem psal Davidovi do svého Facebooku:

Největší problém všech dosavadních řešení je to, že se do presenterů injectuje celý kontejner a pak se uvnitř něj používá jako ServiceLocator. To je špatně, protože není vůbec zřejmé, proč se tam vlastně ten kontejner injectuje (tajení závislostí) a co z něj je potřeba a co se předpokládá, že v něm bude nakonfigurováno, aby presenter vůbec běžel.

Takže první a hlavní směr snažení by měl být zrušit injectování celého kontejneru a místo něj injectovat do presenterů jen jednotlivé dílčí služby, které presenter opravdu potřebuje. Tím bychom měli vyřešeno tajení závislostí.

Druhotná diskuze pak je na téma, zda používat constructor injection, nebo setter injection. Obojí je regulérní deklarace závislostí.

Constructor injection je o dost čistější, protože bez něj se mi instance presenteru vůbec nevytvoří. Zatímco u setter injection se musím spolehnout, že mi to tam někdo nasettuje a pak to kontrolovat, že to opravdu udělal. To ale nemá nic společného s tajením závslostí, v obou případech jsou závislosti (narozdíl od injectování kontejneru) jasně deklarované.

A tady přichází na řadu praktičnost celé věci. A z jejího pohledu mi přijde velice smysluplné nechat konstruktor zcela volný pro injectování uživatelských závislostí, které se liší presenter od presenteru. A všechny závislosti, které jsou „systémové“ a napříč naprosto všemi presentery společné, injectovat přes settery. Což je pro vývojáře výhoda (má volný konstruktor a nemusí vůbec řešit tyhle systémové závislosti, protože si to udělá nette samo) a pro Nette to nevadí (ví, jaké systémové závislosti každý jeho presenter má, tak si je tam při vytváření presenteru pokaždé nastrká).

Takže vlastně totéž, co psal Dan Milde výše – závislosti uživatele přes konstruktor, závislosti frameworku setterem.

awsickness
Člen | 98
+
0
-

jantichy presne jak si napsal tak bych si to predstavoval idealni reseni. ted jen doufam ze to brzy bude.

llook
Člen | 407
+
0
-

HosipLan: To je další možnost, všechny FW závislosti předávat v metodě run. Je něco z toho někdy potřeba ještě před startem životního cyklu presenteru? Pokud ne, tak by to mohl klidně dostat až s tím startem, žádnou nevýhodu teď nevidím.

Editoval llook (28. 2. 2012 12:44)

jantichy
Člen | 24
+
0
-

A k tomu všemu výše pak ještě jeden dovětek, který možná na první pohled obrátí naruby vše, co jsem dosud napsal, ale jen na první pohled.

Dovedu si totiž představit, že část aplikací a vývojářů opravdu chce jet přes ServiceLocator přístup, protože jim to je bližší, jednodušší, už to tak používají… To je zcela legitimní požadavek.

V takovém případě je hezký vstřícný krok jim uvnitř presenterů opravdu ten kontext nabídnout přes nějaké getContext(). Prostě jako alternativní způsob, jak se k věcem z kontejneru dostat.

Takže mi pak dává smysl do presenteru kontejner při vytváření strkat (ideálně výše uvedeným systémovým způsobem přes settery) a pak ho přes getContext nabízet. Ale:

  • Nedělat ho povinný. Když jedu aplikaci čistě DI přístupem, tak chci v testech, aby mi test presenteru prošel, aniž bych ho tam musel injectovat/mockovat. Prostě i pro případ, kdy je $this->context prázdné, tak musí vše fungovat naprosto stejně.
  • Nevázat na něj tedy vůbec nic jiného kromě případu, že si jej chce uživatel vytáhnout přes getContext(). To je jediný důvod, proč tam ten kontejner strkáme. Takže samotný presenter ho nesmí vůbec k ničemu používat (a ani nemůže, pokud má být kontejner volitelný a nepovinný). Pokud presenter potřebuje pro své fungování jakoukoliv další systémovou službu, musí si o ni říct přes extra setter, viz můj předchozí příspěvek výše.
  • Variantně by to šlo udělat jako func getRouter { if ($this->router) return $this->router; elseif ($this->getContext() && $this->getContext()->getRouter()) return $this->getContext()->getRouter(); else throw new exception }, což je ale už zase tak trochu prasárna.
  • A protože je nabízení contextu uvnitř presenterů jen velice alternativní přístup pro bad-practice postupy, mělo by být jeho injectování co nejméně na očích, tedy rozhodně přes setter injection namísto constructor injection.

Editoval jantichy (28. 2. 2012 12:57)

Pavel Kouřil
Člen | 128
+
0
-

Já rozhodně stojím na straně Honzy a Media.

Setter injection mi na „systémové“ věci přijde mnohem lepší; už jen pokud by se jednoho krásného dne někdo rozhodl Presenteru ulehčit od jeho spousty zodpovědností a vyčlenil z něj různé systémové věci typu flash zprávičky nebo odkazy jako různé služby.

Protože v ten moment, pokud by se vše předávalo konstruktorem, tak máme docela šíleně zabordelené konstruktory s XY parametry … a nechtěl bych zažít ten očistec, který by nastal v ten moment, kdy bych potřeboval nějakému předkovi přidat další službu. :)

hrach
Člen | 1834
+
-1
-

Tyvoe, to není o žádný straně media a Honzy. To je o tom, že místo posměšku po twitteru napíšu RFC do fora…

Pavel Kouřil
Člen | 128
+
0
-

„Strana“ nebylo nejlepší vyjádření, „pohled na věc“ (Constructor/setter injection) by asi bylo lepší vyjádření, ale teďka to už zpětně editovat nebudu. :)

Ale RFCčko by bylo vhodnější než tweet, to jo, no. Nicméně diskusi by to IMHO vyprodukovalo víceméně podobnou… ?

Elijen
Člen | 171
+
0
-

jantichy napsal(a):

  • A protože je nabízení contextu uvnitř presenterů jen velice alternativní přístup pro bad-practice postupy, mělo by být jeho injectování co nejméně na očích, tedy rozhodně přes setter injection namísto constructor injection.

Dokud nebude nějak rozumě (systémově) vyřešen lazy-loading, tak bych se slovami jako „bad-practice“ byl opatrnější :)

mkoubik
Člen | 728
+
0
-

OT: k tomu lazy-loadingu – nedalo by se to vyřešit tak, že by se kromě SystemContaineru vygenerovaly i rozšiřující třídy pro proxy-objekty (wrappery) pro každou službu? Ty by si držely továrničku a pomocí __get(), __call() apod. vytvořily objekt až když si na něj někdo sáhne a jinak jenom delegovaly chování na obalený objekt.

Je to trochu prasárna, ale prošlo by to typehintingem a instanceof. Jedinej problém by byl s get_class() a obsazením jména property pro továrničku a pro obalovaný objekt.

Muselo by se to trochu líp promyslet, ale neříkejte že jsem první koho tenhle přístup napadl.

Edit:

Případně pomocí reflexe pro:

class Service {
  public function test($param) {
    ...
  }
}

vygenerovat něco jako:

class LazyService extends Service {
  private $_factory;
  private $_object;

  public function __construct($factory) {
    $this->_factory = $factory;
  }

  public function test($param) {
    if ($this->_object === null) {
      $this->object = $this->_factory->create();
    }
    return $this->_object->test($param);
  }
}

Editoval mkoubik (28. 2. 2012 18:03)

David Grudl
Nette Core | 8136
+
0
-

Pajka napsal(a):

Já rozhodně stojím na straně Honzy a Media.

To je dobře, my s Honzou nejsme ani v nejmenším v rozporu, jen prostě reaguju na to, že píše senzacechtivé nesmysly na twitter a také upozorňuju, že tento commit nevznikl jako řešení situace, ale otevření diskuse k řešení jedné otázky.

David Grudl
Nette Core | 8136
+
0
-

jantichy napsal(a):

Největší problém všech dosavadních řešení je to, že se do presenterů injectuje celý kontejner a pak se uvnitř něj používá jako ServiceLocator. To je špatně

Ale to přece všichni víme, o tom není vůbec potřeba diskutovat a předělá se to v momentě, kdy bude jasné, jak to předělat, tedy jestli ty věci injektovat přes settery nebo přes konstruktor.

Druhotná diskuze pak je na téma, zda používat constructor injection, nebo setter injection. Obojí je regulérní deklarace závislostí.

To je pro mě primární diskuse, protože když není jasné, jak tam ty závislosti dostat, tak to zatím neřeším. Až to bude jasné, tak se změní jen pár řádků.

Constructor injection je o dost čistější, protože bez něj se mi instance presenteru vůbec nevytvoří. Zatímco u setter injection se musím spolehnout, že mi to tam někdo nasettuje a pak to kontrolovat, že to opravdu udělal. To ale nemá nic společného s tajením závslostí, v obou případech jsou závislosti (narozdíl od injectování kontejneru) jasně deklarované.

Konstruktor injection je více zřejmé než setter injection. Za chvíli se k tomu vrátím.

Vysvětli mi prosím, proč jsi dělal takový povyk, když jsem použil konstruktor injection. Doslova jsi napsal, že „blokuju konstruktory presenterů“. A to jsem tam měl jen jednu ze cca 4 závislostí (ano, i samotný kontejner je z důvodu kompatibility závislost, byť dočasná, nicméně v tuto chvíli doufám nehodnotíme, jaké závislosti presenter má, takže jde o závislost jako každou jinou).

Na jedné straně píšeš, že „constructor injection je o dost čistější“ (zcela souhlasím), na druhé straně šílíte, že vám „blokuju konstruktory presenterů“ :-))

A tady přichází na řadu praktičnost celé věci. A z jejího pohledu mi přijde velice smysluplné nechat konstruktor zcela volný pro injectování uživatelských závislostí, které se liší presenter od presenteru. A všechny závislosti, které jsou „systémové“ a napříč naprosto všemi presentery společné, injectovat přes settery.

(Neexistují žádné systémové závislosti, to jsou úplně normální závislosti třídy UI\Presenter)

Což je pro vývojáře výhoda (má volný konstruktor a nemusí vůbec řešit tyhle systémové závislosti, protože si to udělá nette samo) a pro Nette to nevadí (ví, jaké systémové závislosti každý jeho presenter má, tak si je tam při vytváření presenteru pokaždé nastrká).

Jinými slovy žádáš: udělej závislosti třídy UI\Presenter méně zřejmé..

Bulvárně řečeno: Medio nesmyslně zabíjí DI tak, že chce více tajit závislosti presenterů. To jako vážně? Uááá

Podstatné je to, že jsi nedal žádný argument, proč lepší setter než konstruktor. Slova „přijde mi velice smysluplné“ nejsou argument, mně zase přijde (třeba) velice smysluplné to udělat naopak a závislosti Presenteru přenášet přes konstruktor a uživatelské závislosti přes uživatelkou funkci, která tam k tomu je a jmenuje se setContext.

Tak se pojďme přestat pošťuchovat a dejme dohromady argumenty pro nebo proti konstruktoru. (u UI\Presenter)

David Grudl
Nette Core | 8136
+
0
-

A já rovnou začnu:

  • PRO: konstruktor má tu magickou vlastnost, že nemusí udržovat kompatibilitu argumentů s předkem. Tedy při dědičnosti presenterů není třeba nové služby přidávat s hloupým =NULL
  • PROTI: při přepisování konstruktorů zapomene 9/10 programátorů zavolat parent::__construct().
Filip Procházka
Moderator | 4668
+
0
-

Já bych si dovolil trošku zlehčit Davidovo PROTI. V IDE kliknu „overide methods“ > „__construct“ a zmíněnou konstrukci mi doplní. V konstruktoru by se mohla provádět stejná kontrola, jako ve startup().

PROTI:

  • „strašně“ dlouhý konstruktor
  • musím se ručně starat o předání závislostí presenteru, když chci předávat svoje služby
  • u setteru se mi o to může „automaticky“ starat DIC a nemusím o tom vědět, popřípadě se to dá zjednoduššit pomocí „abstraktní definice“ a můžu si dědit závislosti
services:
	presenter:
		setup:
			- setHttpRequest
			- setRouter
	...

	myPresenter < presenter:
		class: MyPresenter(@model)

Nenapadají mě výhody při předávání závislostí presenteru UI\Presenter v konstruktoru.

Editoval HosipLan (28. 2. 2012 21:47)

David Grudl
Nette Core | 8136
+
0
-

HosipLan napsal(a):
Nenapadají mě výhody při předávání závislostí presenteru v konstruktoru.

UI\Presenteru nebo presenteru? Té argumentaci moc nerozumím, při použití setContext() jsi přece žádný konstruktor neřešil, nebo ne? DIC se stará jako o setContext(), tak o konstruktor. A argumenty ídéčkem neberu vůbec.

dundee
Člen | 23
+
0
-

PRO:

  • při použití autowiringu se dá zcela vynechat konfigurace presenteru (není potřeba v konfigu vyjmenovat závislosti), což u setter injection nejde
  • doporučovaný způsob předávání závislostí
  • nezatajuje závislosti
  • pokud zůstane konstruktor volný pro DI uživatelů, budou moci používat jak konstruktor, tak vlastní settery. Budou mít možnost volby. Pokud se konstruktor bude používat pro interní závislosti UI\Presenteru, těžko ho bude moct někdo ještě použít a bude nucen použít settery.

PROTI:

  • parametrů konstruktoru může být hodně (při špatném návrhu presenterů)

Co se týče těch tweetů, tak nemůžu mluvit za Honzu, ale já jsem rozhodně nereagoval na to, že nově používáš u UI\Presenter constructor injection, ale na to, že takhle viditelným předáním kontejneru do presenteru nabádáš lidi ještě více než kdy dřív k používání anti-patternu ServiceLocator. Už slyším vývojáře: „Chtěl jsem zkusit DI u presenterů, ale pak jsem zjistil, že už se tam předává kontejner, tak jsem si všechno vytáhl z něj.“. Kdyby se tam kontejner předával méně okatě, tak si toho vývojář třeba ani nevšimne a použije standardní a doporučované DI.

David Grudl
Nette Core | 8136
+
0
-

Díky za argumenty.

ad Méně okaté předávání kontejneru z edukativních důvodů: už jsem to psal na facebooku, Nette je framework který sleduje určité priority, ale nikoliv učební pomůcka. Já může dát getContext() jako @deprecated a eliminovat ho tak z API, můžu někdy v budoucnu přidat vyhození E_USER_NOTICE nebo jej udělat nepovinným, ale rozhodně nemůžu znásilňovat návrh tak, aby byl politicky korektní.

Vývojář půjde cestou, k jaké ho povede dokumentace, quick start, články na různých blozích, a ne podle toho, jakým šméčkem si předám kontejner, když se zrovna nikdo nedívá.

Filip Procházka
Moderator | 4668
+
0
-

David Grudl napsal(a):

UI\Presenteru nebo presenteru?

*nenapadají mě výhody předávání závislostí potřebných pro chod UI\Presenter přes konstruktor (viz výše)

Té argumentaci moc nerozumím, při použití setContext() jsi přece žádný konstruktor neřešil, nebo ne?

Tedy otázkou je, jestli uživatelský (programátora) konstruktor bude setContext(), nebo __construct()? Může mi tedy vzniknout několik situací

Všechno předávám konstruktorem

UI\Presenter i můj potomek mají všechny služby pěkně vyjmenované, krásně mi je tam doplní autowiring je to voňavé a teoreticky dokonalé. Až na to, že v dalších potomcích se upíšu, abych předal všechno, co potřebuje rodič i potomek a nechci vidět další zanoření a že jsem jich viděl…

Přesně z tohohle důvodu jsem začal razit připojování komponent pomocí return v továrničce a né konstruktorem (new Component($parent, $name)), protože mi vadilo hlídat argumenty předka.

Construktor for UI\Presenter reserved

Smířím se s tím, že UI\Presenter mi zaterasil konstruktor a nebudu ho používat. Budu mít settery na svoje služby a nebudu řešit předávání služeb UI\Presenter rodiči, když ho budu chtít překrýt.

To není moc dobré řešení

Constructor jenom pro uživatele

Nette ustoupí (přesně to co jsi commitl) a constructor uvolní a já si přes ně budu moct předávat svoje věci. Nette si pak někde samo bude doplňovat co potřebuje UI\Presenter. Tohle mi příjde jako dobrá cesta.

Kdyby Nette závilosti (UI\Presenter) byly v konstruktoru, tak mi to může eventuelně vadit v momentě, když bych chtěl vlastní třídy, protože mám vžité na vytváření třídy používat __construct() a je to konstrukce jazyka, nechci používat pseudo-kostruktoru setContext(), ten může používat Nette, protože se to dá krásně zautomatizovat.

Jak jsem psal výše, když budu mít pro sebe __construct() (ten bych taky chtěl používat pro své služby) tak je mi jedno, že Nette si svoje věci předává přes setContext().

Tady je ale nevýhoda z teoretického hlediska, kdy můžu vytvořit instanci UI\Presenter, která nebude fungovat správně, bude existovat, ale nespustím ji. V momentě spuštění totiž začne naříkat, že jí něco chybí.

DIC se stará jako o setContext(), tak o konstruktor.

Ano stará, otázka je, který bude „můj“.

A argumenty ídéčkem neberu vůbec.

ok, to chapu

dundee napsal(a):

nabádáš lidi ještě více než kdy dřív k používání anti-patternu ServiceLocator.

Sranda je, že takto je nabádá i Symfony, oni se asi nesmířili s tím, že by v controllerech neměli lazy-loading služeb. Just saying.

Kolem a kolem: Přepsal jsem si kompletně DI v Nette na Symfony DIC a všechny Nette služby injectoval setterama. Používal jsem DIC přes Presenter::setContext($context) i přes Presenter::__construct($context). A ze všeho toho byla z praktického hlediska nejmenší bolest DIC v konstruktoru (service locator).

Jsme banda masochistů :)

jantichy
Člen | 24
+
0
-

Neexistují žádné systémové závislosti, to jsou úplně normální závislosti třídy UI\Presenter.
Jinými slovy žádáš: "udělej závislosti třídy UI\Presenter méně zřejmé.
Podstatné je to, že jsi nedal žádný argument, proč lepší setter než konstruktor.

Ahoj, zkusím to vysvětlit od začátku znovu a lépe a oddělit od sebe věci, co spolu navzájem nesouvisí.

Injectování kontejneru do presenteru obecně

Tohle je myslím poměrně jasný bod pro všechny. Závislost presenterů na celém kontejneru je špatně, presenter by si měl injectovat (jakkoliv) jenom a pouze ty konkrétní dílčí služby, které potřebuje.

Pro lidi, co chtějí uvnitř presenterů používat context jako ServiceLocator, by mohla být alternativní verze s injectovaným kontejnerem, ovšem volitelným a jen a pouze pro tento účel. Kontejner by se uvnitř presenteru neměl využívat vůbec pro nic jiného. Cokoliv budu uvnitř presenteru potřebovat pro jeho běh, němělo by se brát z tohoto kontejneru, ale mělo by tam být injectováno redundantně vedle toho kontejneru.

Rozumím a chápu to, že stávající injectování a využívání kontejneru v presenterech je dočasný provizorní stav, který je nutno vyřešit a je to v TODO to vyřešit. Takže jak říkám, v tomhle bodě žádný „ideový“ problém není, je to jen otázka vymyšlení konkrétní implementace a je v tom snad vše na všechny strany jasné.

Co je ale důležité pro moje následující řádky, je skutečnost, že se to někdy v budoucnu BUDE MěNIT. Pravděpodobně i několikrát. Viz dále.

Constructor vs. setter injection

Tohle je něco, co mně osobně leží na srdci daleko víc. A uznávám, že jsem to dosud nevysvětlil úplně do podrobna, protože jednak je to na dlouho a jednak jsem tak nějak myslel, že to vidí ostatní stejně :). Takže to zkusím vysvětlit na příkladu.

Jenom předesílám, že pokud níže mluvím o presenterech, mám na mysli zejména Nette\Application\UI\Presenter a jeho potomky, což je něco, s čím pracujete v devadesáti pěti procentech případů.

Dalším předpokladem je to, že constructor injection je trochu čistější než setter injection, ale i setter injection je legitimní deklarace závislostí – pokud se ukáže, že je pro některé jasně vymezené případy řádově praktičtější (což doufám, že níže ukážu), tak není důvod se mu vyhýbat a všechno rvát jen do konstruktoru.

Tak, pojďme na to.

V aplikací mám řádově desítky presenterů. Do každého z nich se injectují nějaké závislosti. Přičemž se dají rozdělit na dvě celkem odlišné skupiny:

  • Něco, čemu osobně říkám systémové závislosti. Jsou to závislosti, které jsou potřeba vůbec pro to, aby obecně všechny presentery fungovaly tak, jak mají. Jsou to závislosti, které se injectují do úplně všech presenterů bez výjimky. Zpravidla jsou to věci dané přímo frameworkem (ale nemusí). Takže to jsou závislosti, které dnes zajišťují například vracení aktuálního requestu, response, application, session, usera. (Neřešme teď prosím, jestli jsou tyhle věci v presenteru potřeba nebo ne a jestli by bylo lepší je tam injectovat po jedné namísto contextu. To je jiná diskuze, kterou jsem záměrně vyhodil pryč do předchozí samostatné kapitoly.) Co je ale jisté, že tam nějaké takovéhle „systémové“ závislosti jsou a nějaké vždy budou. A HODNE pravděpodobné, že se budou v budoucnu v čase průběžně měnit podle toho, jak se bude framework vyvíjet. (Takže v blízké době bude možná nahrazeno či doplněné injectování kontejneru injectováním jednotlivých služeb, osobně očekávám další refactoringy presenterů tak, aby se například zcela zbavily závislostí nejen na kontejneru, ale třeba i na Application apod.)
  • Něco, čemu osobně říkám uživatelské závislosti. To jsou závislosti, které si programátor definuje sám podle toho, co chce, aby mu ten který presenter dělal. Uživatelské závislosti jsou zpravidla různé pro každý presenter.

V zásadě tu jsou dvě smysluplné cesty, kudy se vydat, přičemž celým cílem tohoto mého příspěvku je ukázat, že ta druhá je lepší a bylo by fajn, kdyby byla v Nette důsledně používána:

  1. Všechny závislosti obou typů injectovat přes konstruktor.
  2. Systémové závislosti injectovat přes settery a uživatelské injectovat přes konstruktor.

Předpokládejme teď nějaký jednoduchý ArticlePresenter, který dělá něco s článkem, takže má závislost na ArticleService.

Vše injectovat přes konstruktor

To je něco, co tu bylo doteď a co se mi moc nelíbí. Náš ArticlePresenter by vypadal nějak takto:

class ArticlePresenter extends \Nette\Application\UI\Presenter
{
	private $articleService;

	public function __construct(\Nette\DI\IContainer $context, ArticleService $articleService)
	{
		parent::__construct($context);
		$this->articleService = $articleService;
	}
}

Když budu mít v aplikaci padesát různých presenterů, budu muset tohle překopírovávání kontextu do předka psát padesátkrát. Což je dost zbytečné.

Zejména ale nastane problém v okamžiku, kdy se třeba na základě první subkapitolu na začátku tohoto příspěvku v celém základním Nette změní, že se například injectování kontextu nahradí za injectování jednotlivých servis. Pak budu muset vzít úplně celou svou aplikaci a ve všech padesáti presenterech, v jednom po druhém změnit všechny konstruktory. Plus případně související zápisy v konfiguraci kontejneru, pokud by to třeba náhodou nezvládl autowiring sám:

class ArticlePresenter extends \Nette\Application\UI\Presenter
{
	private $articleService;

	public function __construct(Request $request, Response $response, Application $application, ..., ArticleService $articleService)
	{
		parent::__construct($request, $response, $application, ...);
		$this->articleService = $articleService;
	}
}

A pak za další měsíc udělá někdo refactoring presenterů a třeba co já vim zruší závislost na Application a místo toho vyhodí ven do nějaké vlastní služby vytváření odkazů. Takže zase budu muset vzít aplikaci a ve všech padesáti presenterech přepsat:

class ArticlePresenter extends \Nette\Application\UI\Presenter
{
	private $articleService;

	public function __construct(Request $request, Response $response, Linker $linker, ..., ArticleService $articleService)
	{
		parent::__construct($request, $response, $linker, ...);
		$this->articleService = $articleService;
	}
}

A pak přijde někdo na to, že některé „systémové“ závislosti by bylo hezké lazy-loadovat, takže místo nich samotných je lepší tam frkat jen jejich továrničky – a zase nefunkční aplikace a zase padesátkrát přepisovat…

K těmto změnám ve frameworku docházelo, dochází a bude rozhodně vždycky docházet – jsou žádoucí, protože se jimi stává framework čistší a hezčí. Ale každá taková změna je strašný neuvěřitelný opruz a je to pro nás jako pro vývojářské firmy DOST drahé.

A vůbec se mi i filozoficky nelíbí princip, že nějaké změny de facto „uvnitř frameworku“, které jenom mění nějaké jeho vnitřní chování a vnitřní návrh, mi zasahují do „uživatelské“ části mé aplikace.

Systémové závislosti přes setter, uživatelské přes konstruktor

To je něco, co by se mi moc líbilo. Náš ArticlePresenter by vypadal nějak takto:

class ArticlePresenter extends \Nette\Application\UI\Presenter
{
	private $articleService;

	public function __construct(ArticleService $articleService)
	{
		$this->articleService = $articleService;
	}
}

Nemusím tam v padesáti presenterech opisovat kolem dokola nic navíc, definuju si tam jenom a pouze své „uživatelské“ věci. A jenom ty, co jsou speciálně potřeba pro ten či onen presenter. Závislosti, co se opakují napříč úplně všemi presentery nebo co jsou dané de facto „interním fungováním frameworku“ (a zpravidla mě jako vývojáře ani tak moc explicitně nezajímají, protože jsou prostě díky Nette a tomu, jak funguje, dostupné ve všech presenterech), nemusím opisovat pořád kolem dokola.

Když se v Nette cokoliv změní, například injectování kontextu nahradí za injectování jednotlivých servis nebo se nějaká „systémová“ závislost přidá, ubere nebo změní, nijak mi to nezasahuje do mé aplikace. Všech padesát presenterů zůstane i nadále tak, jak je mám od začátku. Nemusím vůbec na nic sahat, jedeme dál.

Takže asi tak. Co vy na to?

Editoval jantichy (29. 2. 2012 10:23)

jantichy
Člen | 24
+
0
-

Dobře, svůj výše uvedený dlouhý příspěvek shrnu do krátkých bodů. Jaké jsou výhody předávání systémových závislostí přes setter injection a uživatelských přes konstruktor:

  • Eliminuje to opakované psaní téhož kódu a tedy zbytečnou práci a prostor pro chyby. Nemusím v každém presenteru zas a znova ručně přehazovat všechny „systémové“ závislosti, které se opakují napříč všemi presentery.
  • Přehlednější oddělení „uživatelských“ závislostí specifických pro daný presenter, které chci vidět hned, od „systémových“ závislostí, které se opakují v úplně každém presenteru, kterých může být (a mělo by být) v budoucnu hodně a mezi kterými by se mohly ty zajímavé specifické ztratit.
  • Interní chování frameworku se jakoby „zapouzdřuje“ tak, že jeho změny nezasahují do aplikace samotné. Možnost jakýchkoliv flexibilních budoucích změn a refactoringu v samotném frameworku. Po každé změně ve frameworku se mi nerozseká aplikace, nebudu muset pokaždé polovinu aplikace přepsat, vše bude fungovat.
dundee
Člen | 23
+
0
-

Já bych ještě zareagoval na Davidovo „Bulvárně řečeno: Medio nesmyslně zabíjí DI tak, že chce více tajit závislosti presenterů. To jako vážně? Uááá“.

Pojďme se zamyslet nad tím, proč se vlastně závislosti v konstruktoru zveřejňují.

  • kvůli testovatelnosti – když zveřejníme závislosti, hned vidíme, co máme v UNIT testu namockovat, aby třída správně fungovala.
  • kvůli konfigurovatelnosti – můžeme pak velmi snadno změnit implementaci jakékoliv závislosti.

Obecně řečeno by tedy třída měla mít v ideálním případě VŠECHNY závislosti v konstruktoru. Jak je to ale u presenterů? Platí to také? Já si myslím, že nikoliv.

Presentery zpravidla nechceme testovat pomocí UNIT testů, protože náročnost jejich tvorby je značná a přínos většinou minimální (presentery by neměli obsahovat žádnou logiku). Daleko lépe presenter otestujeme buď integračním testem nebo ještě lépe end2end testem, tedy například Seleniem. V integračním testu pak budeme presenter vytvářet buď pomocí kontejneru a nebo pomocí presenterFactory. V obou dvou případech nám předávání systémových závislostí přes settery nebude nijak vadit. Nedojde k WTF typu „ono to nefunguje, nevím proč, asi to zatajilo závislosti“.

Co se týče konfigurovatelnosti, tak pro presentery předávání VŠECH závislostí přes konstruktor také nepotřebujeme. Chceme běžně měnit implementaci HttpRequest předávanou do svých presenterů? Ne, nechceme. Pokud už se opravdu stane, že budeme chtít napříč naší aplikací pracovat s jiným HttpRequest objektem, prostě použijeme vlastní PresenterFactory, která ho do presenterů bude předávat. Nevznikne tak žádné WTF, kdybychom toto dělali v konfiguraci a u jednoho presenteru zapomněli implementaci HttpRequest změnit.

jantichy
Člen | 24
+
0
-

David Grudl napsal(a):

  • PRO: konstruktor má tu magickou vlastnost, že nemusí udržovat kompatibilitu argumentů s předkem. Tedy při dědičnosti presenterů není třeba nové služby přidávat s hloupým =NULL
  • PROTI: při přepisování konstruktorů zapomene 9/10 programátorů zavolat parent::__construct().

Tohle je myslím jenom implementační detail. Protože s tím, co já navrhuji výše, by UI\Presenter měl konstruktor úplně prázdný. Takže by se v potomcích od něj nemusela dodržovat žádná kompatibilita s ničím a nemuselo by se ani myslet na volání parent::__construct(). Takže zase o pár problémů a věcí k zapamatování méně.

David Grudl
Nette Core | 8136
+
0
-

jantichy napsal(a):

V zásadě tu jsou dvě smysluplné cesty, kudy se vydat,

Nikoliv, jsou tři:

  1. Systémové závislosti injectovat přes konstruktor a uživatelské injectovat přes settery.

Variantu a) jako naprosto nepoužitelnou nemusíme snad vůbec brát v úvahu, to je zcela zřejmé, (každopádně je fajn, že jsi to vysvětlil pro ty, kterým to třeba zřejmé není, ale trošku to vrací diskusi o 2 měsíce zpět). Nevím, jestli jsem se nevyjádřil dost zřetelně, ale jde mi o rozhodování mezi variantami b) a c), s tím, že před „komitem“ se používala c), po „komitu“ se používá b).

A stále mi chybí argument, proč zvolit tu a ne tu druhou.

Vojtěch Dobeš
Gold Partner | 1316
+
0
-

Argumenty z mého pohodlného pohledu vývojáře (přičemž se vůbec nechci starat o předávání systémových závislostí, protože de facto vždycky je chci mít na místě):

b) Systémové závislosti injectovat přes settery a uživatelské injectovat přes konstruktor.

+ mohu definovat potomka presenteru s libovolným konstruktorem
+ a zaregistrovat si ho jako službu opět bez jakéhokoliv přihlédnutí k sys. závislostem
systémové závislosti musím předat pomocí setterů, ale to se dá vyčlenit (viz Hosiplanův příklad configu s < presenter, což nevypadá špatně)
+ pokud presenteru nepředávám nic, nemusím volat disableOriginalConstructor v testech při mockování presenteru

c) Systémové závislosti injectovat přes konstruktor a uživatelské injectovat přes settery.

nemůžu definovat potomka presenteru s libovolným konstruktorem (musím myslet na sys. závislosti)
+ můžu ho zaregistrovat jako službu bez jakéhokoliv přihlédnutí k sys. závislostem (zcela)
ale musím vyjmenovat settery v setup, jinak nevyužiji autowire
obvykle budu muset volat disableOriginalConstructor v testech při mockování presenteru

U mě nakonec vítězí vlastní služby přes konstruktor.

Editoval vojtech.dobes (29. 2. 2012 16:02)

dundee
Člen | 23
+
0
-

b) Systémové závislosti injectovat přes settery a uživatelské injectovat přes konstruktor:

PLUS:

  • vývojář hned vidí, jaké uživatelské závislostí tam jsou
  • lze použít vyšší level autowiringu, kdy se konstrukce presenteru nemusí vůbec konfigurovat

MÍNUS:

  • u god-like presenterů může konstruktor dost nabobtnat

c) Systémové závislosti injectovat přes konstruktor a uživatelské injectovat přes settery:

PLUS:

  • nemůže nebobtnat konstruktor, protože tam není :)

MÍNUS:

  • není na první pohled patrné, jaké uživatelské závislosti presenter má
  • více psaní konfigurace

Za mě tedy překvapivě :) také vlastní služby přes konstruktor.

David Grudl
Nette Core | 8136
+
0
-

dundee napsal(a):

  • vývojář hned vidí, jaké uživatelské závislostí tam jsou

Ale právě naopak! Vypadá to, že závislostí je mnohem méně, než jich tam doopravdy je. Prostě tzv „systémové“ (zavádějící termín) nejsou vidět. Presenter, který nemá žádné vlastní závislosti, budí dojem, že nemá žádné závislosti. (Viz tvé mínus: není na první pohled patrné, jaké uživatelské závislosti presenter má)

lze použít vyšší level autowiringu

Můžeš to prosím rozvést?

David Grudl
Nette Core | 8136
+
0
-

David Grudl napsal(a):

Nikoliv, jsou tři:

  1. Systémové závislosti injectovat přes konstruktor a uživatelské injectovat přes settery.

Ok, přidám ještě čtvrtou variantu:

  1. Systémové závislosti injectovat přes konstruktor a uživatelské injectovat přes jeden setter

Výhoda tohoto řešení je v tom, že se vše předává jedné funkci (stejně jako u konstruktoru), autowiringem, a tedy nijak nekomplikuje setup v konfigurátoru.

SendiMyrkr
Člen | 30
+
0
-

Já bych si taky dovolil se svojí troškou…

Prvně, to jak budou zadávaný uživatelský služby podle mě s řešením moc vážně nesouvisí, s drbornou vyjímkou níže, a viděl bych to jako best practice do dokumentace… nevim proč uživatele(programátora) omezovat v tom jak zadávat jeho vlastní služby… Když chce setter nastaví si setter, když chce konstruktor nastaví si konstruktor…

K systémovým bych používal spíš settery a to ze tří důvodů:

  1. pokud použiju konstruktor tak ho prakticky zabiju pro uživatele, každá změna byť jen pořadí nebo typu v parametrech si vyžádá zásah do aplikace a úpravy, který budou ve většině případů jenom pro to abych změnil pár písmenek a na samotnou logiku bez vlivu, to mě přijde jako zbytečný puritánství…
  2. když použiju settery tak budu muset dělat úpravy jenom pokud dojde k opravdu zásadní změně… A to si myslim bude případ v řádech desetin promile, takže změna služeb bude mít dopad na aplikace blízký nule…
  3. Když použiju setter tak je můžu přetěžovat(ano konstruktor taky, já vim) a například při nastavení služby vytvořit/nastavit ty uživatelský služby/objekty který takovou službu potřebujou nebo například pokud je chci logovat…

Ano, settery jsou víc magický pro testování, ale opravdu je to zrovna u presenterů takovej problém?

awsickness
Člen | 98
+
0
-

neslo by udela nekde poll se 3 / 4 variantama a podle toho jak se to odvoli tak se to pouzije ? takhle se to tu davaji porad ty same + a – takze to hlasovani by mozna nebylo od veci.

David Grudl
Nette Core | 8136
+
0
-

awsickness napsal(a):

neslo by udela nekde poll se 3 / 4 variantama a podle toho jak se to odvoli tak se to pouzije ? takhle se to tu davaji porad ty same + a – takze to hlasovani by mozna nebylo od veci.

This is not Nette-way ;)

awsickness
Člen | 98
+
0
-

tak pak se nemusime tady hadat ktera cesta je lespi ;) jiste uz si svoji vybral :)

dundee
Člen | 23
+
0
-

David Grudl napsal(a):
Ale právě naopak! Vypadá to, že závislostí je mnohem méně, než jich tam doopravdy je. Prostě tzv „systémové“ (zavádějící termín) nejsou vidět.

Viz můj post o něco výše. Opravdu potřebuji znát systémové závislosti (a to rozdělení opravdu má smysl) presenteru? Já si to nemyslím. Když presenter konfiguruju, potřebuju znát pouze ty uživatelské. O ty systémové by se měla postarat PresenterFactory.

lze použít vyšší level autowiringu

Můžeš to prosím rozvést?

Nevím, jestli to Nette kontejner umí, ale my používáme upravený kontejner ze Symfony a ten umí to, že přes reflexi zjistí jaké závislosti třída potřebuje a pokud tyto závislosti je schopný vytvořit, tak třídu instancuje i bez explicitně napsané konfigurace. Tedy stačí třeba něco takového:

<?php
  presenter_front_offer:
    class: HomeSweetHome\FrontModule\OfferPresenter
?>
David Grudl
Nette Core | 8136
+
0
-

Máme třídu Parent a jeho potomka Child. Parent má závislosti A a B, Child přidává ještě závislost C. Má tedy závislosti A, B a C.

Ty se mi snažíš říct, že tě závislosti A a B u třídy Child absolutně nezajímají (doslova: nemusíš je znát), že je vývojář nemusí moc vidět, ale závislost C tě zajímá a byla by chyba, aby nebyla očividná.

Já ti říkám, že to je blbost. Prostě Child má tři stejně důležité závislosti.

ten umí to, že přes reflexi zjistí jaké závislosti třída potřebuje

Jakým způsobem to, krom konstruktoru, může zjistit? Hádá? Volá všechny metody začínající na „set“? Nebo mu to naznačujete anotacemi? To jde proti smyslu DI.

Borci, jestli máte zájem, tak vám udělám v Mediu inhouse školení Dependency Injection ;)

dundee
Člen | 23
+
0
-

Chceš tím tedy říct, že protože jsou všechny závislosti stejně důležité, měli bychom je předávat všechny konstruktorem? Měl jsem dojem, že jsi před chvíli psal, že to rozhodně nechceš.

Honza: * a – Všechny závislosti obou typů injectovat přes konstruktor.*
Ty: Variantu a) jako naprosto nepoužitelnou nemusíme snad vůbec brát v úvahu, to je zcela zřejmé.

Jakým způsobem to, krom konstruktoru, může zjistit?

Může to zjistit z konstruktoru. Proč bych měl chtít něco víc? Tohle je jeden z důvodů, proč se doporučuje constructor injection. V případě, že takovýhle autowiring nepoužiješ, tak se u většího projektu z psaní konfigurace brzy zblázníš.

David Grudl
Nette Core | 8136
+
0
-

David Grudl napsal(a):

Variantu a) jako naprosto nepoužitelnou nemusíme snad vůbec brát v úvahu, to je zcela zřejmé

Tady jsem se unáhlil, protože použitelný způsob, jak se mohou injektovat závislosti UI\Presenteru (ehm „systémové“) i jeho potomků (uživatelské) skrze konstruktor existuje. Takže ještě přidávám pátou variantu:

  1. Vše injectovat přes konstruktor a vytvořit PresenterDependencies

UI\Presenter by vypadal takto:

class Presenter extends Control
{
	function __construct(PresenterDependencies $deps)
	{
		....
	}
}


class PresenterDependencies
{
	function __construct(...vsechny zavislosti presenteru...)
	{ .... }
}

a jakýkoliv potomek by pouze předal jeden objekt PresenterDependencies, čímž by se odstínil vůči změnám v UI\Presenteru. Z hlediska DI jde o naprosto čisté řešení.

David Grudl
Nette Core | 8136
+
0
-

dundee napsal(a):

Chceš tím tedy říct, že protože jsou všechny závislosti stejně důležité, měli bychom je předávat všechny konstruktorem? Měl jsem dojem, že jsi před chvíli psal, že to rozhodně nechceš.

Onu variantu, v tuto chvíli jednu z pěti variant, rozhodně nechci, je zcela mimo hru. Mezi dalšími variantami stále váhám.

Může to zjistit z konstruktoru. Proč bych měl chtít něco víc? Tohle je jeden z důvodů, proč se doporučuje constructor injection. V případě, že takovýhle autowiring nepoužiješ, tak se u většího projektu z psaní konfigurace brzy zblázníš.

Dane, bavíš se se mnou jako s malým dítětem. Ano, Nette umí autowiring, Nette dokonce umí předávat presenterům uživatelské i systémové závislosti (fakt!), a dělá to tak už nějaký pátek. Rozhodně nezabíjí, na rozdíl od této diskuse, DI ;-)

Psal jsi mi, že vám stačí napsat class: HomeSweetHome\FrontModule\OfferPresenter a autowiring všechno vyřeší. Já se ptám, jak vyřeší tebou preferovaný stav, kdy v konstruktoru budou jen uživatelské služby a na „systémové“ se použijí settery. Jak jeden řádek předá presenteru „systémové“ objekty Http\Request nebo Router, které potřebuje ke generování odkazů?

dundee
Člen | 23
+
0
-

David Grudl napsal(a):
a jakýkoliv potomek by pouze předal jeden objekt PresenterDependencies, čímž by se odstínil vůči změnám v UI\Presenteru. Z hlediska DI jde o naprosto čisté řešení.

Ani toto není čisté řešení. Porušuješ tím pravidlo předávání přímých závislostí a Demeterův zákon. PresenterDependencies je ServiceLocator přebarvený na zeleno.

Psal jsi mi, že vám stačí napsat class: HomeSweetHome\FrontModule\OfferPresenter
a autowiring všechno vyřeší.
Já se ptám, jak vyřeší tebou preferovaný stav, kdy v konstruktoru budou jen
uživatelské služby a na „systémové“ se použijí settery.
Jak jeden řádek předá presenteru „systémové“ objekty Http\Request nebo Router,
které potřebuje ke >generování odkazů?

Davide, psal jsem to tu už několikrát. Systémové závislosti by mohla do presenteru dodat PresenterFactory. PresenterFactory si je bude buď tahat z kontejneru a nebo je dostane jako závislosti konstruktorem.

Editoval dundee (1. 3. 2012 8:10)

Patrik Votoček
Člen | 2221
+
0
-

dundee napsal(a):

Psal jsi mi, že vám stačí napsat class: HomeSweetHome\FrontModule\OfferPresenter
a autowiring všechno vyřeší.
Já se ptám, jak vyřeší tebou preferovaný stav, kdy v konstruktoru budou jen
uživatelské služby a na „systémové“ se použijí settery.
Jak jeden řádek předá presenteru „systémové“ objekty Http\Request nebo Router,
které potřebuje ke >generování odkazů?

Davide, psal jsem to tu už několikrát. Systémové závislosti by mohla do presenteru dodat PresenterFactory. PresenterFactory si je bude buď tahat z kontejneru a nebo je dostane jako závislosti konstruktorem.

Pokud to dobře chápu tak @dundee to myslí tak že to bude fungovat stejně jako teď (presenterFactory explicitně volá setContext nad presenterem a cpe mu tam tak závyslosti pomocí autowire). Jen né pro „uživatelské“ ale pro „systémové“ závyslosti.

Tj. bylo by tam něco takového:

/**
 * Create new presenter instance.
 * @param  string  presenter name
 * @return IPresenter
 */
public function createPresenter($name)
{
	$presenter = $this->context->createInstance($this->getPresenterClass($name));
	$this->context->callMethod(array($presenter, 'setRequest'));
	$this->context->callMethod(array($presenter, 'setResponse'));
	$this->context->callMethod(array($presenter, 'setApplication'));
	// ...
	return $presenter;
}

Dalo by se to řešit i tak další variantou:

class Presenter extends \Nette\Application\UI\Presenter
{
	public function setContext(Request $request, Response $response, Application $application, ...)
	{
		//...
	}
}

/**
 * Create new presenter instance.
 * @param  string  presenter name
 * @return IPresenter
 */
public function createPresenter($name)
{
	$presenter = $this->context->createInstance($this->getPresenterClass($name));
	$this->context->callMethod(array($presenter, 'setContext'));
	return $presenter;
}

Tj. že by se prohodilo stávající fungování a místo containeru se předávali „systémové“ závyslosti jako jednotlivé argumenty metody setContext.

Patrik Votoček
Člen | 2221
+
0
-

Btw. pokud je variantou D myšlena stávající implementace v Nette 2.0.x. Resp. je to to co už dnes běžně funguje a běžně se používá. Tak hlasuji pro tuto variantu.

Jednak, protože už jsem si na to zvyknul. Druhak protože se tady X let na fóru šíří a hlásá myšlenka nikdy nesahej na construktor v presenteru a došlo by tak ke značnému zmatení „nováčků“ / méně pokročilích uživatelů (i když uznávám že to je takový důvod nedůvod).

dundee
Člen | 23
+
0
-

@Patrik: Nevýhoda varianty D je, že ti nedává žádnou jinou možnost, než injectovat uživatelské závislosti přes ten jeden setter (případně víc setterů). Varianta B přináší větší svobodu, protože pak pro uživatelské závislosti můžeš použít konstruktor, jeden setter nebo víc setterů. To znamená, že nikdo nemusí nic přepisovat, varianta B se přizpůsobí všem požadavkům.

Patrik Votoček
Člen | 2221
+
0
-

@dundee: nikoli, varianta D v aktuálním současném stavu funguje tak že můžu klidně definovat všechno to co jsi napsal. S jedinou vyjímkou a tou je parent::__construct($context) (autowire se o vše postará).

Kde se $context dá nahradit za již zmiňované PresenterDependencies.

Pokud navíc vemu v úvahu možnost definovat si vlastní setter injection pro presenter tak autowire končí (žádná z variant tohle neřeší).

Filip Procházka
Moderator | 4668
+
0
-

Líbí se mi, co píše @**dundee**, protože kdo jiný, než @presenterFactory, by měl vytvářet presenter. Tedy o settery se postará on, nad konstruktorem zavolá autowire, nebo podle naming-convention by se mohl podívat, jestli neexistuje služba/factory se stejným názvem a použil tu.

O systémové závislosti UI\Presenter by se tedy staral vždy @presenterFactory. Stačí tedy prohlásit za standard, že na vytvoření libovolného presenteru se musí použít @presenterFactory a je po problému se skrytým závislostmi :)

dundee
Člen | 23
+
0
-

Bavil jsem se teď s Vaškem Purchartem a myslím, že teď asi nemá moc smysl DI presenterů řešit :) Pokud se mu povede UI\Presenter dekomponovat, nebude pravděpodobně potřeba o tomhle vůbec diskutovat.

Editoval dundee (1. 3. 2012 14:37)