Co dál s Quickstartem – inject*() vs setContext()
- Filip Procházka
- Moderator | 4668
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.
- Majkl578
- Moderator | 1364
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é.
- Patrik Votoček
- Člen | 2221
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
@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)
- David Ďurika
- Člen | 328
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 | 8218
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
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 | 8218
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 | 8218
@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.
- Honza Marek
- Člen | 1664
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 :)
- 22
- Člen | 1478
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)
- Filip Procházka
- Moderator | 4668
Super tedy, takže to zítra přepíšu na inject*()
a
důsledně přidám komentářů :)
- Patrik Votoček
- Člen | 2221
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
@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
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 | 8218
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
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
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
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.
- Majkl578
- Moderator | 1364
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
Když budu mít k dispozici jenom jednu metodu inject()
na
presenter, mám stejný problém jako se setContext()
- Teyras
- Člen | 81
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.
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
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 | 8218
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
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
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
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.
- David Ďurika
- Člen | 328
jsvelta napsal(a):
Pochopil by som, keby sa to nenastavovalo vždy.
Nenastavuje sa to vzdy… mozes mat prezenter bez akychkolvek zavislosti…
- jsvelta
- Člen | 39
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
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
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
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
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
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
Ř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
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.