Co dál s Quickstartem – inject*() vs setContext()

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Filip Procházka
Moderator | 4668
+
0
-

Quickstart byl nedávno přepsán a zrefaktorován. Zbývá nám ovšem vyřešit poslední dilema.

V Nette 2.0.5 je možné pro předání závislostí do presenteru využívat inject*() metody, ale v 2.0.4 je pouze setContext. V současném stavu QS zůstat nemůže, protože se chceme kompletně zbavit volání $this->context. Máme tedy dvě cesty.

Používat inject*()

Presentery by se přepsaly na inject*() metody a doplnila by se poznámka, že kdo má Nette starší jak 2.0.5, tak má použít setContext().

class TaskPresenter extends BasePresenter
{
	private $tasks;
	private $taskLists;

	public function injectTasks(TaskList\Tasks $tasks, TaskList\TaskLists $taskLists)
	{
		$this->tasks = $tasks;
		$this->taskLists = $taskLists;
	}
}

Já tvrdím, že kdo bude začínat nový projekt, bude jej začínat na aktuálním Nette. IMHO nemá tedy smysl se ohlížet na lidi, kteří si jej již vyzkoušeli. Pro ně by tam byla poznámka, že předtím se to dělalo tak a teď se to dělá tak. Navíc naučíme nové nováčky, aby striktně používali předávání závislostí do presenteru a nesahali na DIC. Nebudou totiž většinou ani vědět jak, pokud jim to neukážeme.

Použít setContext()

Pokud chceme požívat setContext() podobně jako inject*(), rychle narazíme na problém s dědičností.
BasePresenter::setContext() a TasksPresenter::setContext() musí mít zkrátka identické parametry.

Jediné použitelné řešení, které neskončí selháním je nechat si předat celý DIC a v metodě si z něj vyzobat co potřebujeme.

class TaskPresenter extends BasePresenter
{
	private $tasks;
	private $taskLists;

	public function setContext(Nette\DI\Container $dic)
	{
		parent::setContext($dic);

		$this->tasks = $dic->tasks;
		$this->taskLists = $dic->taskLists;
	}
}

Je to řešení. A je to řešení, které bude kompatibilní zpět až do 2.0.0. Což je ovšem jeho jediná výhoda.

Chtěl bych dokončit aktualizaci QS tak, abych měl čisté svědomí. Bylo by tedy super, kdybys Davide rozhodl, co chceš mít v QS.

Honza Marek
Člen | 1664
+
0
-

Napsat inject* a kdo má starší, ať použije $this->context.

Majkl578
Moderator | 1364
+
0
-

Jsem samozřejmě pro variantu se setContext, kvůli kompatibilitě – dokumentace je přeci psána pro 2.0.x (tj. 2.0.0 a dále).
Zároveň je tato varianta plně future-compatible, jen se pak nahradí _assignment_ v setContext za inject* metody, poměrně bezbolestné.
Variantu s inject* uvést jako poznámku, že to tak bude/je možné.

Jan Endel
Člen | 1016
+
0
-

Ale to už David někde říkal ne? Že v podstatě je to jedno, že tam stejně tak jako tak musí být poznačeno jak to udělat i tím druhým způsobem, takže v podstatě jde jen o pořadí odstavců.

ale stejně jsem pro injectFoo ;)

Patrik Votoček
Člen | 2221
+
0
-

Jsem jednoznačně pro variantu 2 (setContext). Dokumentace by totiž měla být vždy zpětně kompatabylní s 0 jako setinkovou verzí. Tj. v dokumentaci pro 2.0.x by mělo být vše funkční i na verzi 2.0.0. Předejde se tím spoustě zbytečných dotazů a otázek: „proč tohle nefunguje“ (na které bude vždy stejná odpověď: „a máš Nette 2.0.3+?“).

Pokud někdo žije v naivní představě že nováčci si čtou QS jenom jedmou, když si poprvé zkoušejí Nette. Tak by se měl rychle probudit. Spousta lidí si totiž pamatuje že o tomhle už někde něco četla tak se koukne a hned ví.

Takže nováček, který před X měsíci prošel QS s Nette 2.0.0. Později začal psát svou aplikaci s Nette 2.0.2. A teď má problém jde hledá v dokumentaci ví že někde tohle četl. Vleze do QS a hle to je ono. Vloží to do své aplikace. A ono to co? Ono to nefunguje! I když to tak bylo napsané v dokumentaci!

Tohle je můj čistě subjektivní názor.

Filip Procházka
Moderator | 4668
+
0
-

@pilec: Ano, přesně to David psal. Problém je v tom, že my se zkrátka neshodneme. A bylo by dost nefér, kdybych třeba teď šel a celý QS předělal na inject*(), když s tím nesouhlasí několik guruů, které respektuji.

Chce to zásah autority ;)

Editoval HosipLan (5. 9. 2012 18:25)

Jan Tvrdík
Nette guru | 2595
+
0
-

+1 pro setContext(), souhlasím s Patrikem

David Ďurika
Člen | 328
+
0
-

napise tam variantu pre 2.0.x a potom aj pre 2.0.5+

toto je dost velka zmena takze by som to tam spomenul…

David Grudl
Nette Core | 8074
+
0
-

Asi by se dalo používat inject() a důsledně dodržet, že všude v kódu bude uvedena poznámka:

	// do verze Nette 2.0.5 se metoda jmenuje setContext()
	public function inject(Service $service)
	{
	}
MartinitCZ
Člen | 580
+
0
-

Osobně spíš vidím prodlém jinde. Proč se tak velká změna prováděla ted a nenechala se na 2.1? Myslim si, že tyhle setinkové verze mají být výhradně na úpravu bugů. ;)

Z toho důvodu:
@Patrik Votoček: +1

Nebo případně jak píše @achtan. Verzovat quickstart.

@David Grudl : Tak to sorry, já myslel, že to je nějaká nová fičura ;)

Editoval martinit (5. 9. 2012 19:12)

David Grudl
Nette Core | 8074
+
0
-

BTW kód quickstartu by každopádně měl obsahovat přiměřené množství komentářů, ideálně u každé metody. Ne kvůli parametrům, ale pro vysvětlení, co dělá, kdy se volá.

David Grudl
Nette Core | 8074
+
0
-

@martinit k žádné změně nedošlo. Podpora inject() je důležitá kvůli dopředné kompatibilitě. Inject je bugfix na problémy se setContext.

hrach
Člen | 1834
+
0
-

inject. :)

Honza Marek
Člen | 1664
+
0
-

Já si myslim, že to setContext nikdo (ze začátečníků) nikdy nemoh reálně používat. Proto jsem pro inject. Chtělo by to výzkum :)

jtousek
Člen | 951
+
0
-

Jak v případě inject řeší případ, že několik služeb má stejnou třídu a tedy nelze použít autowiring? Pokud neřeší, zůstávám u $this->context.

Do dokumentace bych zařadil raději tu novější metodu s komentářem, že ve starší verzi je to jinak.

22
Člen | 1478
+
0
-

Kdyby jen ze začátečníků :-) jsem inject* začal používat asi před měsícem, do té doby $this->context. Jinak stačí dát do vyhledávání setContext vs. $this->context, je to 1:9. Myslím, že setContext je hodně neznámé a ani bych s tím lidem nemotal hlavu a nechal je při $this->context u starších verzí, s tím, že pokud nechtějí porušovat DI, tak by měly použít setContext() a od 2.0.5 toto řešit výhradně přes inject* metody.

Editoval 22 (5. 9. 2012 20:40)

hrach
Člen | 1834
+
0
-

Jo, to je pravda, priznejte mi, kdo pouzival setContext misto ->context. Nikdo, pac to bylo nepouzitelne ;)

Filip Procházka
Moderator | 4668
+
0
-

Super tedy, takže to zítra přepíšu na inject*() a důsledně přidám komentářů :)

Patrik Votoček
Člen | 2221
+
0
-

hrach napsal(a):

Jo, to je pravda, priznejte mi, kdo pouzival setContext misto ->context. Nikdo, pac to bylo nepouzitelne ;)

Já!!!

HosipLan napsal(a):

Super tedy, takže to zítra přepíšu na inject*() a důsledně přidám komentářů :)

Ty to ale nemáš přepsat na inject*() ale na inject() pokud čtu dobře :-)

Jan Tvrdík
Nette guru | 2595
+
0
-

@HosipLan: Rovnou můžeš opravit callbacky na starší zápis. Tj. s funkcí callback bez spoléhání na novinku v Nette\Object.

Majkl578
Moderator | 1364
+
0
-

Souhlasím s tím, co napsal Patrik.


Podpora inject() je důležitá kvůli dopředné kompatibilitě. Inject je bugfix na problémy se setContext.

Ale zároveň je to i zásadní feature. Že je to bugfix si nemyslím, ostatně chování PHP při rozšiřování setContext není bug Nette, ale zcela regulérní chování samotného jazyka. Zároveň existuje přímočaré (a vpodstatě zažité) řešení – vytahání služeb z DIc do privátních propert (a klidně i v konstruktoru, skrze který se DIc předává). Takové řešení se – jako bonus – jednoduše upgraduje na inject*.
(A jde vlastně vůbec o dopřednou kompatibilitu? Kompatibilitu čeho? Nic se přeci nemění, přidává se jen nová vlastnost.)

Asi by se dalo používat inject() a důsledně dodržet, že všude v kódu bude uvedena poznámka:

	// do verze Nette 2.0.5 se metoda jmenuje setContext()
	public function inject(Service $service)
	{
	}

A pomůže to vůbec něčemu? Taková metoda má přeci stejné „problémy“ jako předchůdce setContext – při dědění se chová stejně (nelze rozšířit počet povinných parametrů). V důsledku jsme vlastně jen změnili název metody, ale problém zůstal.


Celá tahle diskuze nemusela nikdy vzniknout, kdyby se do x.y.z verzí nedávaly featury. :)

David Grudl
Nette Core | 8074
+
0
-

Dopředná kompatibilita řeší skutečnost, že už dnes víme, co bude v 2.1 deprecated (a házet E_USER_DEPRECATED) a dáváme možnost lidem psát dnes aplikace tak, aby fungovaly později. Má to jednoznačně přínos pro uživatele, snižuje to potřebu v budoucnu něco upravovat, což jsou klíčové hlediska, kterým všechny argumenty, co jsem tu četl, nesahají po kotníky.

Samozřejmě se Nette 2.0.5 mohlo jmenovat 2.1.0 a tato diskuse by pak skutečně nevnikla, vznikla by totiž diskuse o něčem mnohem a mnohem komplikovanějším.

David Ďurika
Člen | 328
+
0
-

Majkl578 napsal(a):
A pomůže to vůbec něčemu? Taková metoda má přeci stejné „problémy“ jako předchůdce setContext – při dědění se chová stejně (nelze rozšířit počet povinných parametrů). V důsledku jsme vlastně jen změnili název metody, ale problém zůstal.

nie, nezostal! Ked FooPresenter dedi od BasePresentera kt. ma inject() a potrebujes injectnut dalsie veci tak si vo FooPresenter spravis nieco ako injectFoo()

Ivorius
Nette Blogger | 119
+
0
-

Mluvím za sebe jako mírně pokročilý začátečník :) Začal jsem na verzi QS, kde byly modely tvořený děděním NDB\Selection, což jsem záhy zjistil, že byla cesta do pekel. Naštěstí se to v QS změnilo a já pochopil, že to je rozhodně lepší cesta.

Teď používám ->context, ale jestliže mi v QS někdo ukáže použití inject a vysvětlí proč je to takto lepší, tak nemám sebemenší problém to začít používat.

Nový začátečníci si stáhnou stejně novou verzi, a takový jako já si myslím, že raději se přizpůsobí nejnovější stable verzi, nebo si hold pojedou to svoje, protože to již mají trošku zažité.

Patrik Votoček
Člen | 2221
+
0
-

Ivorius napsal(a):

Teď používám ->context, ale jestliže mi v QS někdo ukáže použití inject a vysvětlí proč je to takto lepší, tak nemám sebemenší problém to začít používat.

https://pla.nette.org/…ect-autowire :-)

Majkl578
Moderator | 1364
+
0
-

achtan napsal(a):

nie, nezostal! Ked FooPresenter dedi od BasePresentera kt. ma inject() a potrebujes injectnut dalsie veci tak si vo FooPresenter spravis nieco ako injectFoo()

Řeč byla o inject(...), nikoliv o inject*(...), viz samotným Davidem navržené inject(Service $service).

Filip Procházka
Moderator | 4668
+
0
-

Když budu mít k dispozici jenom jednu metodu inject() na presenter, mám stejný problém jako se setContext()

Teyras
Člen | 81
+
0
-

Patrik Votoček napsal(a):

Ivorius napsal(a):

Teď používám ->context, ale jestliže mi v QS někdo ukáže použití inject a vysvětlí proč je to takto lepší, tak nemám sebemenší problém to začít používat.

https://pla.nette.org/…ect-autowire :-)

Pořád nechápu, jak mám řešit, že mám víc služeb stejné třídy. Pokud u služby zakážu inject, ztratím asi jediný čistý způsob, jak jí dostat do presenteru, nebo ne?

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

Teyras řekl bych že přes inject to nepůjde – dokud se nevymyslí něco jako mapování služeb podle názvu parametrů, tak Nette netuší, podle čeho by se měla rozhodnout. Řešením je tedy vlastní PresenterFactory, I suppose?

David Grudl
Nette Core | 8074
+
0
-

Jsem přesvědčen, že vůbec nemá smysl přihlížet k dogmatům („dokumentace musí být kompatibilní s 2.0.0“ – proč?), ale stačí kriticky zvážit, co je nejpřínosnější pro uživatele.

Abych celou věc trošku zrelativizoval: Quick-start je důležitá část dokumentace a troufám si tvrdit, že udělat jej 2.0.5-only způsobí řádově méně škod, než když se v něm dědí Selection ;-)

Tedy:

  • Prioritním cílem je uživateli srozumitelně vysvětlit co nejčistější a preferovanou cestu.
  • Druhotným cílem je nezmást uživatele starších verzí.

Nelze věc bagatelizovat a předpokládat, že quick start je jen pro ty, co si právě stáhli poslední revizi. To je naivní, tudíž se nesmí nic tajit, všechno vysvětlit a na nekompatibilitu upozornit.

A dá se to udělat chytře! Co třeba setContext() vůbec nezmiňovat a začít intuitivně předávat závislosti konstruktorem. Přičemž musíme předat také závislosti rodičovské třídy, ať už to bude UI\Presenter nebo BasePresenter. To je logické. A tady se dá říct, že od verze 2.0.5 existuje způsob, jak tento problém obejít, a to pomocí metod inject*. Uživatele starších verzí vyzveme k upgrade.

Teyras
Člen | 81
+
0
-

vojtech.dobes napsal(a):

Teyras řekl bych že přes inject to nepůjde – dokud se nevymyslí něco jako mapování služeb podle názvu parametrů, tak Nette netuší, podle čeho by se měla rozhodnout. Řešením je tedy vlastní PresenterFactory, I suppose?

No právě… V takové situaci mi přijde lepší nepřekomplikovávat autowiring a rozhodnout to za Nette, ale to teď nejde jinak, než vlastní PresenterFactory… Ale to už je trochu OT

Pavel Kouřil
Člen | 128
+
0
-

Rozhodně setContext (s tím, že se v něm udělá assignment do proměnných té třídy). A inject* zmínit jako alternativu pro novější verze.

začít intuitivně předávat závislosti konstruktorem

A nebo todle.

yonix
Člen | 37
+
0
-

A mam to za sebou pred par mesiacmi som sa riadne potrapil ako zaciatocnik z pochopenim QS. na poslednu verziu som bol upozorneny na chetu ked som sa dostal ne neuveritelne začarovaneho kruhu. po prepisani aplikace podla noveho QS vše funguje aj to to skvele lebo som konečne pochopil aj veci ktore som len tupo kopyroval a stale im nechapal…

Tato verzia qs je urcite veľkym prinosom pre každeho začinajúceho nettistu.

Ďakujem všetkym guru ktory sa na nej podielaly.

jsvelta
Člen | 39
+
0
-

Prečo sa v tomto konkrétnom prípade (QS) nepoužije predávanie služby priamo v __construct()? Akú býhodu má nastavovať to cez metódu inject*()? Pochopil by som, keby sa to nenastavovalo vždy.

MartinitCZ
Člen | 580
+
0
-

@**jsvelta**: V presenterech se na __constructor() nešahá :)

David Ďurika
Člen | 328
+
0
-

jsvelta napsal(a):

Pochopil by som, keby sa to nenastavovalo vždy.

Nenastavuje sa to vzdy… mozes mat prezenter bez akychkolvek zavislosti…

vvoody
Člen | 910
+
0
-

achtan napsal(a):

jsvelta napsal(a):

Pochopil by som, keby sa to nenastavovalo vždy.

Nenastavuje sa to vzdy… mozes mat prezenter bez akychkolvek zavislosti…

Nastavuje, v tom zmysle že PresenterFactory zavolá každú inject*() metódu.

David Ďurika
Člen | 328
+
0
-

to ano, ale inject* metody niesu povinne, o tom som hovoril

jsvelta
Člen | 39
+
0
-

achtan napsal(a):

Nenastavuje sa to vzdy… mozes mat prezenter bez akychkolvek zavislosti…

Mne šlo konkrétne o QS, kde tá závislosť povinná je. Ak by ju ten objekt (napr. HomepagePresenter) nedostal, tak by nedopadol dobre.

Nevedele som, že pri volaní constructoru sa neuplatňuje autowire. Po zbežnom nahliadnutí do zdrojáku je jasné, že inject* je na to ideálna metóda.

jsvelta
Člen | 39
+
0
-

Nedalo mi to a pozrel som sa poriadne. Podľa mňa sa na konštruktor šahá a tá závyslosť je povinná. Teda malo by to byť v ňom.

Container.php obsahuje toto:

<?php
	public function createInstance($class, array $args = array())
	{
		$rc = ClassReflection::from($class);
		if (!$rc->isInstantiable()) {
			throw new ServiceCreationException("Class $class is not instantiable.");

		} elseif ($constructor = $rc->getConstructor()) {
			return $rc->newInstanceArgs(DIHelpers::autowireArguments($constructor, $args, $this));

		} elseif ($args) {
			throw new ServiceCreationException("Unable to pass arguments, class $class has no constructor.");
		}
		return new $class;
	}
?>
vvoody
Člen | 910
+
0
-

Autowire je na konštruktore tak isto ako na inject* metódach. Neviem čo vlastne riešiš. Je len na tebe kadiaľ predáš závislosti. Len pri konštruktore si je treba dávať pozor aby sme nezabudli zavolať konštruktor rodiča a ak má nejaké závislosti, tak mu ich predať.

class foo{

	private $connection;

	public function __construct(Connection $c){
		$this->connection = $c;
	}
}

class bar extends foo{

	private $storage;

	public function __construct(Connection $c, Storage $s){
		parent::__construct($c);
		$this->storage = $s;
	}
}

alebo

class foo{

	private $connection;

	public function injectDataabse(Connection $c){
		$this->connection = $c;
	}
}

class bar extends foo{

	private $storage;

	public function injectStorage(Storage $s){
		$this->storage = $s;
	}
}

ktoré sa ti viac páči?

jsvelta
Člen | 39
+
0
-

Samozrejme že je jedno, či sa to predá konštruktorom, alebo settrom. Obe varianty budú funkčné. Ide mi o správne používanie DI. Ak ide o závislosť, ktorá je pre objekt nevyhnutná a bez nej nemože žiť, tak by ju mal dostať cez konštruktor. Ak ide o závislosť, ktorá povinná nie je a bez nej može žiť, tak by sa mala nastavovať cez setter.
Ak vytvorím objekt ručne a po vytvorení do neho „nevstrieknem“ závislosť, tak ten objekt bude v nekonzistentnom stave a volanie nejakej metódy skončí chybou.
QS by mal byť i o tom, aby učil nováčikov prgramovať správne. Alebo ich aspoň upozorniť, že je to školské riešenie, ktoré by sa reálne nemalo používať.

Diskusia je o tom, či inject*() alebo setContext(). Podľa mňa je ale v tomto prípade správna varianta __construct(). Všetky tri varianty, z hľadiska funkčnosti, sú ekvivalentné.

vvoody
Člen | 910
+
0
-

jsvelta napsal(a):

…Ak ide o závislosť, ktorá je pre objekt nevyhnutná a bez nej nemože žiť, tak by ju mal dostať cez konštruktor.

Mal by sa aj vždy volať rodičovský konštruktor ale php ti to neprikazuje, takže ani konštruktor nieje stopercentná záruka predania závislostí ako sa zrejme domnievaš. Pri použití inject* metód má framework lepšiu kontrolu nad injectovaním závislostí. Pri injectovaní cez konštruktor triedy, ktorá je podedená, nemá framework žiadnu možnosť ako predať závislosti, keď ich programátor zabudne predať rodičovskému konštruktoru, alebo ho celkom zabudne zavolať. Takže ti odpovedám na otázku v čom sú lepšie inject* metódy: pri ich používaní je kód menej náchylný na chybovosť.

jsvelta
Člen | 39
+
0
-

Implementácia triedy je vecou autora danej triedy. Ak nevolá rodičovský konštruktor, je to chyba. Trieda mi poskytuje určité pevne dané rozhranie. Používaním tohoto rozhrania by som nikdy nemal dostať inštanciu triedy do nekonzistentného stavu.

Pri použití inject* metód má framework lepšiu kontrolu nad injectovaním závislostí.

Prečo? Konštruktor odvodenej triedy može mať menej parametrov ako konštruktor rodičovskej. Je to normálny jav.

studna
Člen | 181
+
0
-

Řešit jestli je předávání závislostí konstruktorem lepší než přes settery je podle mě hloupost. Stejně tak vybrat si jeden způsob a používat jen ten.

Ve výše uvedeném kódu konstruktor vs setter je nejideálnější (podle mého) kompromis.

class Database
{
	protected $db;

	public function __construct(Db $db)
	{
		$this->db = $db;
	}
}

class Model extends Database
{
	private $property;

	public function injectProperty(Property $property)
	{
		$this->property = $property;
	}
}
vvoody
Člen | 910
+
0
-

jsvelta napsal(a):

Samozrejme že je jedno, či sa to predá konštruktorom, alebo settrom. Obe varianty budú funkčné. Ide mi o správne používanie DI.

Obe varianty sú správne použitie DI. Ktorú si zvolí programátor je už len na jeho preferenciách.

Implementácia triedy je vecou autora danej triedy. Ak nevolá rodičovský konštruktor, je to chyba.

Ktorú nemožno rozumne detekovať.

Pri použití inject* metód má framework lepšiu kontrolu nad injectovaním závislostí.

Prečo?

Lebo framework nemôže zdetekovať či bol zavolaný rodičovský konštruktor, aby na to mohol upozorniť výnimkou.

@studna: Sám používam obe možnosti. Tu sa len snažím vysvetliť, prečo je v presenteroch v quickstarte odporúčané používať inject metódy.