Pouzitie inject v extends modelu
- vladimir.biro
- Člen | 163
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 :)
- vladimir.biro
- Člen | 163
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 | 661
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 | 9
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 | 163
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 | 518
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:
- 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 - místo viditelnosti
private
budeš chtít použítprotected
, abys k property mohl přistupovat v potomcích
Nicméně preferoval bych řešení, které nastínil JiriSlischka.
- vladimir.biro
- Člen | 163
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 | 9
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 | 2663
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 | 163
Š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/…dependencies#…) 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 | 6445
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 | 2663
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 | 163
Š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 uinjectFoo()
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 | 163
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?