Pouzitie inject v extends modelu

vladimir.biro
Člen | 136
+
0
-

Ahojte.

Trochu filozofujem nad modelmi projektu a chcel by som nad nimi vytvorit extends, nejaky BaseManager.
Kedze v potomkoch (modeloch) chcem samozrejme pouzivat konstruktor a aby som sa vyhol vieme comu, tak v BaseManageri
nepouzitam konstruktor, ale @inject

V konfigu mam nasledne modely nakonfigurovane takto:

services:
	router: App\Router\RouterFactory::createRouter

	# Models
	-
	    class: App\Model\ContractsManager
	    inject: true

Cize kazdy manager, aj ked samotny injecty neobsahuje, ma inject: true a tak mi to funguje.

Chcem sa opytat, ci je to koser, alebo by ste mi odporucili nieco lepise?

Dakujem za nazor do diskusie :)

CZechBoY
Člen | 3598
+
+2
-

Na co potrebujes ten BaseManager?

MajklNajt
Člen | 371
+
0
-

ak to chceš robiť košér, rob to košér, čiže predávaj závislosti cez konštruktor :) a ako píše CZechBoY (kua dalo mi zabrať napísať to upPeRcAse správne :D ), zváž, či ten BaseManager vôbec potrebuješ

Editoval MajklNajt (25. 10. 2020 11:48)

vladimir.biro
Člen | 136
+
0
-

No tak technicky vzate nepotrebujem kopu veci a mozem to pisat rovno v PHP :)

Dostal som sa do situacie, kedy v modeloch pouzivam rozne kontroly, ktore mi prislo lepsie zbalit do jednej funkcie (aby som neduplikoval kod) a hodit ju do nadradenej classy. Viem, ze som si na to mohol vyrobit sluzbu a tu nasledne volat v kazdom modeli, ale to mi pride tiez take … meh.

Co sa tyka predavania zavislosti cez konstruktor v tomto pripade mi to pride ako pliest si na seba bic, pretoze ked v case, ked budem mat 20 modelov a budem chciet pridat zavislost do Base, tak to bude peklicko a myslim, ze presne pre tieto pripady bol dany @inject vymysleny.

Takze potrebujem Base?

  • ma mi odlahcit od pisania toho isteko kodu stale dookola
  • zbavi ma duplicitneho kodu
  • prispeje k istej automatizacii pri vyvoji
  • zabrani, ze by som na iste veci pri pisani noveho modelu zabudol
Martk
Člen | 576
+
0
-

Vyhni se anotacím @inject mimo presenter, udělej to takto:

class BaseManager {

	private EntityManagerInterface $em;

	final public function injectBaseManager(EntityManagerInterface $em): void
	{
		$this->em = $em;
	}

}
decorator:
	BaseManager:
		setup:
			- injectBaseManager

Editoval Martk (26. 10. 2020 20:06)

JiriSlischka
Člen | 8
+
+7
-

Za mě můžu doporučit jednu věc. Nemít žádný BaseModel, hlavně pokud tam budeš řešit business logiku. My tohle máme a přídávání jakýchkoliv závislostí je peklo (postupně se toho zbavuju, ale je to fuška).
Navíc se stavají případy, kdy potom polovinu nebo třeba ani jednu z těch závislostí co má ten BaseModel nepotřebuješ.
Měli jsme takhle udělané i Nette formuláře, kdy všechny formuláře dědily od nějakého BaseFormu. A když bylo potřeba něco urpavit tak se to muselo strašně hackovat.
Takže bych byl opravdu s děděním a BaseCokoliv opatrný (Většinou se Base potom hodí tak asi na 40% a zbytek věci jsou více specific než Base…).
Jinak u Presentrů máme BasePresenter, který v constructoru nepřijmá nic a má jenom public property které se nainjectují. A všechny poděděné Presentry potom použivají constructor injection. Tohle mi přijde asi jako nejhezčí přístup.

Co se týká například nějakého repozitáže. Tak máme třídu, která zašťíťuje obecné funkce práce s DB nějaké filtry ukládání vyhatování. A tuto třidu si předávám do každého repozitáře kde jí používám. A zbavil jsem se tak BaseRepository.

vladimir.biro
Člen | 136
+
0
-

Martk napsal(a):

Vyhni se anotacím @inject mimo presenter, udělej to takto:

class BaseManager {

	private EntityManagerInterface $em;

	final public function injectBaseManager(EntityManagerInterface $em): void
	{
		$this->em = $em;
	}

}
decorator:
	BaseManager:
		setup:
			- injectBaseManager

Toto sa mi celkom paci, ale private EntityManagerInterface $em; nie je syntakticky spravne a teda mi to na tom pada. Funguje to ale, ked odstranim to EntityManagerInterface a zmenim na public.

Len neviem, ci to uz nie je to iste, ako pouzit anotaciu @inject

nightfish
Člen | 208
+
0
-

vladimir.biro napsal(a):
Toto sa mi celkom paci, ale private EntityManagerInterface $em; nie je syntakticky spravne a teda mi to na tom pada. Funguje to ale, ked odstranim to EntityManagerInterface a zmenim na public.

Jsou tam 2 věci:

  1. použití PHP 7.4 syntaxe typovaných property, takže pokud používáš starší verzi, budeš chtít EntityManagerInterface přesunout do komentáře
  2. místo viditelnosti private budeš chtít použít protected, abys k property mohl přistupovat v potomcích

Nicméně preferoval bych řešení, které nastínil JiriSlischka.

vladimir.biro
Člen | 136
+
0
-

JiriSlischka napsal(a):

Za mě můžu doporučit jednu věc. Nemít žádný BaseModel, hlavně pokud tam budeš řešit business logiku. My tohle máme a přídávání jakýchkoliv závislostí je peklo (postupně se toho zbavuju, ale je to fuška).
Navíc se stavají případy, kdy potom polovinu nebo třeba ani jednu z těch závislostí co má ten BaseModel nepotřebuješ.
Měli jsme takhle udělané i Nette formuláře, kdy všechny formuláře dědily od nějakého BaseFormu. A když bylo potřeba něco urpavit tak se to muselo strašně hackovat.
Takže bych byl opravdu s děděním a BaseCokoliv opatrný (Většinou se Base potom hodí tak asi na 40% a zbytek věci jsou více specific než Base…).
Jinak u Presentrů máme BasePresenter, který v constructoru nepřijmá nic a má jenom public property které se nainjectují. A všechny poděděné Presentry potom použivají constructor injection. Tohle mi přijde asi jako nejhezčí přístup.

Co se týká například nějakého repozitáže. Tak máme třídu, která zašťíťuje obecné funkce práce s DB nějaké filtry ukládání vyhatování. A tuto třidu si předávám do každého repozitáře kde jí používám. A zbavil jsem se tak BaseRepository.

Ja samozrejme nechcem pouzit klasicke predavanie zavislosti cez konstruktor. Dependency hell sa vvyhybam ako len viem.
Prave preto som chcel vyuzit moznost riesit to anotaciou @inject, co ale mimo presenter nie je odporucane, ale uz som sa nikde nezodvedel ze preco. V BaseManager nebude ziadna velka logika. Maju tam byt len funkcie, tkore pouzivam krizom vec vaic modelov a chcem sa vyhnut duplicite kodu v modeloch.

BasePresneter mam samozrejme rieseny preste tak isto a myslim, ze je to bezne zauzivany postup. Ja som nieco podobne chcel spravit v modeloch a zaujima ma, ci je to prijatelny kompromis, alebo som si tym vytvoril nejaku zasadnu bezpecnostnu dieru.

JiriSlischka
Člen | 8
+
+1
-

Opravdu záleží co v tom modelu budeš řešit.
Například takovou věc jako zda má daný uživatel práva získat dané data. Na to mám službu bokem, kterou si injectuju pokud ji opravdu potřebuju. Protože v další 30%, ta validace funguje jinak. Takže ji nepotřebuju ve všech Managerech.
Tahání dat z DB taky ne vždy potřebuješ metodu find, někdy jí vyloženě nechceš aby vůbec pro danou tabulku taková metoda existovala. Pokud to podědíš musíš jí přeimplementovat a místo nějaké entity vahazovat exception. Což kazí API.
Někdy metoda find nepřijmá int ale string a potom to musíš nějak zbastlit :/.
Nebo časem přesnuš logiku ze svojí DB do nějaké microslužby a potom Manager vůbec nebude potřebovat připojení do DB, ale nějakého HTTP klienta (to se nám stalo).

My dědičnost používáme pouze pro nette componenty.
Nikde jinde jí nemáme, a vůbec mi nechybí.

Každopádně záleží na tobě. Třeba tam máš legitimní věc, která opravdu dává smysl.

Šaman
Člen | 2528
+
+2
-

vladimir.biro napsal(a):

som chcel vyuzit moznost riesit to anotaciou @inject, co ale mimo presenter nie je odporucane, ale uz som sa nikde nezodvedel ze preco.

Doporučované to není nikdy. Je to hack porušující zapouzdření (je to zápis zvenku do public property). Ale v presenterech je to tiše tolerované, protože presentery jsou většinou už final, nikdy je nevytváříš ručně a protože dřív byla běžná struktura v extrému třeba BasePresenter > SecuredPresenter > BackendBasePresenter > final FooPresenter. A asi i proto, že presentery většinou nepřidáváš jako služby do configu. Třídy modelu do configu píšeš, tak už přidat jeden řádek s konfigurací není takový problém.

V modelu se doporučuje na tenhle hack úplně zapomenout. Můžeš použít decorator, tedy všem třídám dědících od nějakého BaseModel nastavit po vytvoření nějaky setter (který se může jmenovat injectFoo($foo))


A jinak, ptal ses

vladimir.biro napsal(a):

Chcem sa opytat, ci je to koser, alebo by ste mi odporucili nieco lepise?

Proto píšem, že to košer není. Použít se to dá. Ale na vlastní nebezpečí (protože mnoho z nás už se různými hacky spálilo, když se projekt rozrostl).

Editoval Šaman (27. 10. 2020 13:52)

vladimir.biro
Člen | 136
+
0
-

Šaman napsal(a):

vladimir.biro napsal(a):

som chcel vyuzit moznost riesit to anotaciou @inject, co ale mimo presenter nie je odporucane, ale uz som sa nikde nezodvedel ze preco.

Doporučované to není nikdy. Je to hack porušující zapouzdření (je to zápis zvenku do public property). Ale v presenterech je to tiše tolerované, protože presentery jsou většinou už final, nikdy je nevytváříš ručně a protože dřív byla běžná struktura v extrému třeba BasePresenter > SecuredPresenter > BackendBasePresenter > final FooPresenter. A asi i proto, že presentery většinou nepřidáváš jako služby do configu. Třídy modelu do configu píšeš, tak už přidat jeden řádek s konfigurací není takový problém.

V modelu se doporučuje na tenhle hack úplně zapomenout. Můžeš použít decorator, tedy všem třídám dědících od nějakého BaseModel nastavit po vytvoření nějaky setter (který se může jmenovat injectFoo($foo))


A jinak, ptal ses

vladimir.biro napsal(a):

Chcem sa opytat, ci je to koser, alebo by ste mi odporucili nieco lepise?

Proto píšem, že to košer není. Použít se to dá. Ale na vlastní nebezpečí (protože mnoho z nás už se různými hacky spálilo, když se projekt rozrostl).

Jen bych doplnil, ze zavolanim injectFoo($foo), resp vseobecne inject* se zavola inject metoda automaticky a neni potreba resit decorator. Podle dokumentace (https://doc.nette.org/cs/3.0/di-usage#…) pri tomto nedojde k poruseni zapouzdreni, takze by tohle misto anotace melo byt koser.

Taky bych to mohl resit pres setter, treba setFoo($foo) a v configu nastavit decorator, co by bylo asi stejne, jestli spravne chapu dokumentaci.

Je to tak nejak kombinace vseho, co se tu popsalo :D

Editoval vladimir.biro (27. 10. 2020 16:32)

David Matějka
Moderator | 6354
+
+4
-

Jen bych doplnil, ze zavolanim injectFoo($foo), resp vseobecne inject* se zavola inject metoda automaticky a neni potreba resit decorator.

metody se obecne nevolaji automaticky. jsou u nich stejna pravidla jako u inject properties. automaticky se volaji pouze u presenteru, pripadne i jinde, pokud to zapnem pres decorator extension

pri tomto nedojde k poruseni zapouzdreni, takze by tohle misto anotace melo byt koser.

je to z pohledu cistoty lepsi, ale porad to neni dobre, jelikoz je to do jiste miry skryvani zavislosti, respektive musis vedet, ze se museji ty metody zavolat, aby byl objekt v konzistentim stavu a fungoval dle ocekavani. kdyz mas zavislosti v konstruktoru, tak objekt nejde zkonstruovat bez predani techto zavislosti.

Taky bych to mohl resit pres setter, treba setFoo($foo) a v configu nastavit decorator

ano, to je v podstate stejny jako inject s tim rozdilem, ze inject metody umi nette volat automaticky, pokud to chovani zapneme.

Šaman
Člen | 2528
+
+1
-

Já ty pojmenovávám injectFoo() právě proto, že je pak ta závislost méně skrytá. Mám konvenci, že v inject setterech jsou povinné závislosti, které je nutné nastavit. Ať už ručně, nebo v configu.
Další je konvence nesahat na inject settery když s objektem pracuji. Zatímco přenastavovat nějaký setFoo() netrkne, tak u injectFoo() ano.

@vladimirbiro Zapínat ten autoinject nedoporučuji. Použij decorator, od toho je.


P.S. Tohle by ale mělo být výjimečné. Pro povinné závislosti je vždy první volbou konstruktor.
Tohle se občas hodí při nějakém hromadném nastavování třeba translatoru, nebo formRendereru. Ale i tam je lepší použít raději továrnu jako službu a nechat si od ní vracet třeba kostru formuláře kde je už nastavený renderer i translator.

Editoval Šaman (27. 10. 2020 23:50)

vladimir.biro
Člen | 136
+
0
-

Šaman napsal(a):

Já ty pojmenovávám injectFoo() právě proto, že je pak ta závislost méně skrytá. Mám konvenci, že v inject setterech jsou povinné závislosti, které je nutné nastavit. Ať už ručně, nebo v configu.
Další je konvence nesahat na inject settery když s objektem pracuji. Zatímco přenastavovat nějaký setFoo() netrkne, tak u injectFoo() ano.

@vladimirbiro Zapínat ten autoinject nedoporučuji. Použij decorator, od toho je.


P.S. Tohle by ale mělo být výjimečné. Pro povinné závislosti je vždy první volbou konstruktor.
Tohle se občas hodí při nějakém hromadném nastavování třeba translatoru, nebo formRendereru. Ale i tam je lepší použít raději továrnu jako službu a nechat si od ní vracet třeba kostru formuláře kde je už nastavený renderer i translator.

Ja, jak jsem se v tom vcera patlal, tak jsem si nevsiml, ze mam v configu u tech modelu zapnuty ty injecty, tak proto mi to slo. Takze jsem to predelal a nastavil pres decorator. Ta konvence s pouzivanim inject* se mi libi a z dovolenim si ji adoptuju :)

Jo a tohle pouzivani je u mne hodne vyjimecne. Uz mam za sebou par projektu a nikdy jsem inject mimo BasePresenter nepouzil (i proto mam ohledne predavani zavislosti dost mezery), jen ted mne spis zajimalo teoreticky, jak je to s tim predavanim zavislosti obecne. A tenhle BaseModel je jedine misto, kde mi to taky v tomhle projektu dava smysl. Chapu, ze to asi neni vhodne reseni pro projekt, na kterem pracuje treba 10 vyvojaru, ale na takovej jednocloveci projekt mi to prijde jako zjednoduseni mravenci prace.

Na formulare samozrejme pouzivat factory :)

vladimir.biro
Člen | 136
+
0
-

David Matějka napsal(a):

Jen bych doplnil, ze zavolanim injectFoo($foo), resp vseobecne inject* se zavola inject metoda automaticky a neni potreba resit decorator.

metody se obecne nevolaji automaticky. jsou u nich stejna pravidla jako u inject properties. automaticky se volaji pouze u presenteru, pripadne i jinde, pokud to zapnem pres decorator extension

Ja se v tom vcera trochu patlal, tak jsem si nevsiml, ze mam u tech modelu povolenej inject, proto mi to slo a nabral jsem mylny dojem, ze je to automaticky. Dik za opraveni.

pri tomto nedojde k poruseni zapouzdreni, takze by tohle misto anotace melo byt koser.

je to z pohledu cistoty lepsi, ale porad to neni dobre, jelikoz je to do jiste miry skryvani zavislosti, respektive musis vedet, ze se museji ty metody zavolat, aby byl objekt v konzistentim stavu a fungoval dle ocekavani. kdyz mas zavislosti v konstruktoru, tak objekt nejde zkonstruovat bez predani techto zavislosti.

Chapu to spravne, ze jde hlavne o to, ze pro vyvojare neni jasny odkud se dana metoda vola, resp kdyz presunu celej objekt, tak ze mi bez toho nasaveni v configu nebude fungovat?