Jak správně injektovat modely

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
kleinpetr
Člen | 480
+
0
-

Ahoj,

snažím se nějak naladit na best practice řešení a zrovna jsem přemýšlel nad tím jak je vlastně správné injektovat. V nějakých projektech injektuji např. databázové modely vždy v presenteru ve kterém model potřebuji, ale zase z hlediska přehlednosti a univerzálnosti jsem začal injektovat většinu věcí už někde v předkovi, např BasePresenteru, jak je to s výkonem nebo co je vlastně lepší řešení ?

Díky za rady a tipy :)

Oli
Člen | 1215
+
+2
-

Každá třída (teda i presenter) by měla mít jen takový závislosti, jaký potřebuje. Proto injectovat v BasePresenteru není dobrej nápad. Kdy je vhodný injectovat v BasePresenteru je, pokud tu službu potřebují všechny presentery (typicky, když máš menu z databáze). Pro injectování v potomcích BasePresenteru je vhodné použít inject metodu nebo @inject anotaci konstruktor.

Edit: Pro injectování v BasePresenteru inject metodu nebo @inject anotaci, protože jinak by jsi musel volat pokaždé parent::__construct, což by jsi měl vždy, ale kdo to dělá? :-)

Editoval Oli (7. 12. 2015 0:27)

kleinpetr
Člen | 480
+
0
-

Díky :)

Šaman
Člen | 2658
+
+2
-

V koncových presenterech se doporučuje používat konstruktor, stejně jako v jakékoliv běžné třídě. Ovšem anotace je kratší.
V abstraktních předcích se doporučuje používat anotaci, nebo inject metodu, aby zůstal konstruktor volný pro ty koncové presentery.

Taky se doporučuje, aby každý presenter byl buď abstract, nebo final, což souvisí i s tím popsaným výše.

kleinpetr
Člen | 480
+
0
-

Aha, takže BasePresenter by měl být abstract, poté když používám jakýsi SecuredPresenter tak ten by měl být také abstract nebo ne ? a koncové tedy final. Jen pro zajímavost, v čem je to dobré ?

a jaký je rozdíl mezi předáním přes konstruktor nebo anotací ?

Díky.

Šaman
Člen | 2658
+
+3
-

Všechny presentery, které jsou určené k tomu, aby se znich dědilo by měly být abstract (i ten SecuredPresenter). To abstract a final nedělá nic, než že při pokusu o nesprávné použití vyhodí výjimku (z abstract nevytvoří instanci a final nemůže být uveden jako předek). Je to jen pro pořádek.


Rozdíl mezi předáváním konstruktorem a anotací je zásadní – pokud předáváš povinnou závislost, měla by jít přes konstruktor. Pokud není něco k dispozici, tak se to sekne hned v konstruktoru a ani se nevytvoři instace závislé třídy (v tomto případě presenteru).

Nepovinné závisloti by měly jít předávat metodou – klasickým setterem, ale v Nette se dá předání zautomatizovat, pokud se ten setter pojmenuje inject….

A anotace je trochu fuj – technicky je to předávání závisloti do veřejné proměnné (takže ti to může teoreticky zvenku kdokoliv přepsat). Porušuje to zapouzdření a samozřejmě se může vytvořit instance i bez této závislosti. Připsáním anotace a typu si to Nette zpracují tak, že ihned po vytvoření instance se závislost nastaví, ale i tak je to běžným zdrojem chyb (komentář s jednou hvězdičkou, private/protected property) a pak se chyba projeví až když je daná závislost potřeba (takže na špatném místě – chyba je v definici property a špatně napsané anotaci). Tohle všechno se ti při předávání závislostí konstruktorem (které je mimo presentery jediné podporované pro automatický inject) nestane. Pokud se vytvoří instance, tak i se všemi závislostmi a pokud se nevytvoří, tak to zařve už v konstruktoru.

Anotace inject má jedinou výhodu, která jí ale stačíla k tomu, aby se stala dominantním způsobem v presenterech. Je s ní nejméně psaní :)


Edit: Odmazal jsem zmínku o metodách a akcích v abstraktním presenteru. Zapsat tam samozřejmě jdou, ale volat se musí až na konkrétním potomkovi.

Editoval Šaman (7. 12. 2015 0:55)

kleinpetr
Člen | 480
+
0
-

Mockrát děkuji za vyčerpávající souhrn :)

Přesně nějaký takový souhrn by se mi hodil když jsem s nette začínal a měl jsem trochu problém pochopit rozdíly mezi předáváním závislostí. Díky !

Šaman
Člen | 2658
+
0
-

Zajímavé povídání na toto téma je tady a úvaha k implementaci v Nette tady (ale je to 4 roky staré! – ber to jen jako úvahu nad výhody/nevýhody jednotlivých možností).

A zkusím se zeptat přímo nepovolanějšího @DavidGrudl : Jak je to aktuálně s předáváním závislostí presenteru konstruktorem? V odkazovaném článku je popis situace, kdy nastane špatně odhalitelná chyba, tím spíš že bude vzácná (v konstruktoru potomka chci sahat na závislost předka, která ale ještě není nastavená). Tenhle problém trvá? Takže v presenterech, ani finálních, není ideální používat konstruktor, nebo je to naopak nejčistčí způsob (jak jsem si doteď myslel)? Díky.

Editoval Šaman (7. 12. 2015 1:09)

David Grudl
Nette Core | 8215
+
+5
-

V presenterech je ideální předávat závislosti konstruktorem (a krom předávání tam pokudmožno nic jiného nedělat), ale klidně se dá používat i anotace @inject. Nebo metody inject, pokud se nejedná o finální presenter.

kleinpetr
Člen | 480
+
0
-

@Šaman Ještě se zeptám, teď jsem narazil na situaci, kdy potřebuji např. do BasePresenteru předat nějakou továrnu na form, který je v layoutě na všech presenterech. Ale jelikož jsem začal používat předávání konstruktorem v jeho potomcích tak kdybych chtěl i u BasePresenteru předat konstruktorem, musel bych u všech potomků upravit parent::__construct(...) Což se mi zdá blbý.. Jak tedy nejlépe řešit tuhle situaci (když nechci použít inject) ? Díky

castamir
Člen | 629
+
0
-

Přesně taková situace je tzv. dependency hell, tj. kvůli jediné změně závislostí musíš upravit všechny potomky. Abys to dělat nemusel, tak existují právě anotace @inject a metody inject*, které to za tebe vyřeší bez nutnosti upravovat potomky.

Pokud si tedy chceš zajistit tu továrničku pro všechny potomky BasePresenteru, tak v BasePresenteru použij inject a v potomcích ponech předání závislostí konstruktorem.

Šaman
Člen | 2658
+
0
-

V tomhle případě inject metodu, nebo anotaci použij. Přesně na tohle je dělaná.
A to, že nechceš u všech potomků upravovat konstruktor, to je přesně ten Constructor hell, kvůli kterému inject metody a anotace vznikly. (A proto funguje jen v presenterech, protože v jiných třídách tento problém není tak typický).

kleinpetr
Člen | 480
+
0
-

Super, díky za objasnění. Jediné co mi na tom vadí je že bude proměnná, do které injectnu, přístupná ze všech potomků.. Ale díky :)

Šaman
Člen | 2658
+
+1
-

Můžeš ji udělat private (a plnit ji inject*() metodou), je to čistější, než public.

O inject metodě můžeš uvažovat (v rámci konvencí Nette) jako o odloženém konstruktoru (a pozor, že v konstruktoru tedy nemůžeš používat proměnné, které se naplní až v inject*()).

kleinpetr
Člen | 480
+
0
-

Nojo, mockrát díky ! :)

Edit: když použiju private pro inject pormennou a zkouším vytvořit komponentu tak mi napíše: Call to a member function create() on a non-object

Editoval kleinpetr (31. 12. 2015 10:57)

Šaman
Člen | 2658
+
0
-

Jestli k ní přistupuješ v potomkovi, tak private je moc restriktivní, použij protected. To už nesouvisí s DI.

kleinpetr
Člen | 480
+
0
-

Právě, že jí používám v BasePresenteru kde vytvářím i createComponentFoo() kterou potom využívám v layoutě.

ViPEr*CZ*
Člen | 817
+
0
-

Tak jste dal private, ale injectujete furt přes anotaci. Pokud máte private proměnnou v BasePresenteru a v BasePresenteru ji také použijete, pak to musí fungovat, protože je proměnná viditelná. Jen neinjectujte službu anotací, ale přes inject* metodu jak už psal Šaman.

premek_k
Člen | 172
+
+1
-

Myslím, že toto se hodí: http://nette.matej21.cz/cs/di

Šaman
Člen | 2658
+
0
-

To je hodně dobrý tahák.

Trochu nesouhlasím jen s poznámkou 2, viz tohle vlákno. (Edit: Zmíněná poznámka byla odstraněná.) Pokud chci psát co nejčistěji, je inject metoda vhodnější, než anotace inject, protože nemusím porušovat zapouzdření (public property). Inject metoda má navíc stejnou strukturu, jako předávání konstruktorem a pokud ji chápu, jako odložený konstruktor, tak mi zůstane veškeré injectování konzistentní. (Ve většině ukázek jsou pro každou závislost samostatné injectFoo() metody, ale velmi se mi osvědčilo mít za konstruktorem jedinou inject metodu se všemi závislostmi – tedy stejně, jako kdybych injectoval konstruktorem.)

Editoval Šaman (2. 1. 2016 12:19)

David Matějka
Moderator | 6445
+
+1
-

@Šaman zvazoval jsem, ze tu 2. poznamku odeberu, tak teda jo, at si kazdej vybere :)

Šaman
Člen | 2658
+
0
-

@DavidMatějka: Super, tenhle tahák budu šířit dál.

Ještě bys mohl pro pořádek do poznámky 1 přidat slovo „vhodné“. Aby si někdo nemyslel, že v BasePresenteru předávání konstruktorem nebude fungovat. Bude, jen je to nepraktické.

Resp. možná bych to přeformuloval na „nevhodné v abstraktních Base* presenterech“.

Editoval Šaman (2. 1. 2016 14:43)

Jiří Nápravník
Člen | 710
+
0
-

Ok, přepíšu taky na konstruktor vše:-) Nicméně zajímá mě, voláte v konstruktoru u presenteru parent::__construct()? Když tam nic nemám, tak to moc nedává smysl, ale nevím jestli je to best practise.

A jak se tváříte ke Kdyby\Autowired – nemyslím ted obdobu inject, ale myslím konkrétně ty továrničky na komponenty.

Editoval Jiří Nápravník (2. 1. 2016 13:18)

Šaman
Člen | 2658
+
+1
-

IMHO parent::__construct() volat. Ať už kvůli konzistenci kódu, ale hlavně pokud se do konstruktoru parenta něco přidá (ač by dle konvence nemělo), tak to zařve.
Edit: A navíc u presenteru konstruktor není prázdný, jen se přes něj nepředávají žádné závislosti.

Editoval Šaman (2. 1. 2016 13:23)

F.Vesely
Člen | 369
+
0
-

Jiří Nápravník napsal(a):
A jak se tváříte ke Kdyby\Autowired – nemyslím ted obdobu inject, ale myslím konkrétně ty továrničky na komponenty.

Ja osobne Kdyby\Autowired pouzivam, protoze vsechno jedu pres komponenty, kde uz mam vse v konstruktoru. Pouzivam jeste k tomu live templates v PhPStormu, takze se mi to i pekne pise.

David Matějka
Moderator | 6445
+
0
-

Edit: A navíc u presenteru konstruktor není prázdný, jen se přes něj nepředávají žádné závislosti.

Nette s tim pocita, ze se to nezavola: https://api.nette.org/…ter.php.html#170

asi u vseho, u ceho se pocita s dedicnosti (presenter, control, component…), se prakticky nemusi volat rodicovsky konstroktor. spravne by se ale volat mel.