Jak dostat do BaseControl službu, aniž by se jí museli potomci zabývat?
- Šaman
- Člen | 2661
(navazuje na diskuzi v Jak předávat závislosti do presenterů, komponent a jiných služeb?)
Jediné, co se mi ve stávajícím Nette nedaří uspokojivě vyřešit je inject služby templateFactory do komponent (BaseControl) tak, aby se o ni potomci nemuseli starat.
- použití konstruktoru: moc se mi to nelíbí, protože chci nechat konstruktor čistý pro závislosti potomků. Navíc tato závislost nemusí být povinná.
- inject metoda: bohužel nefunguje, pokud každému potomkovi nenastavíme příznak inject: true
- anotace @inject: nefunguje a ani nechci, tady už by mi porušení zapouzdření vadilo
- přímý přístup do containeru: funguje, ale je to podle mě horší řešení, než injectMethod(). Až tak tu závislost skrývat zase nechci.
- vyprdnoutsenato a prostě ten kód, který vytváří šablonu, nacpat do BaseControl i do BasePresenteru.
Jestli se něco změnilo, nebo jsem to pochopil špatně, tak se omlouvám a
prosím o nakopnutí (správným směrem, nikoliv do zadku).
Pro mě by byla ideální inject metoda. Když už ne automaticky zapnutá
i pro komponenty (které jsou presenterům velmi podobné, jen u nich nebývá
vícenásobná dědická struktura), tak mít možnost nastavit inject: true
buď globálně, ale raději pro danou rodičovskou třídu (s platností
i pro potomky), nebo dokonce u každé inject metody.Globálně
stačí, protože tam, kde nechci použít autowire, tam si napíšu obyčejný
setter a nikoliv inject metodu.
V modelu už si tohle vyřeším sám a tam je automatické injectováni asi opravdu zbytečné, ale viděl jsem i projekty, kde se automaticky všechny modelové třidy vytvářely s příznakem inject: true. Globální nastavení by to opět řešilo.
Dodatek: Jinde jsem na tento problém nenarazil, takže by to vyřešilo i kdyby Nette použilo externí služby pro vytvoření šablony jak komponent, tak presenterů. (Upravil bych chování jen těchto služeb a vůbec bych nepotřeboval BaseComponent). To, co chci přepsat jsou pravidla pro umístění šablon a chci je mít stejná pro presentery i komponenty (osobně chápu presenter jen jako lepší komponentu, která je schopná být adresována).
Editoval Šaman (15. 5. 2014 15:32)
- David Grudl
- Nette Core | 8227
Mělo by fungovat asi tohle:
services:
BaseControl:
create: My\BaseControl
setup:
- setTemplateFactory(@myFactory)
AbcControl < BaseControl: My\AbcControl(...)
Ale moc mě nenapadá, jaký má smysl nechávat volbu template factory na konfiguraci. Bude ta komponenta fungovat dobře i s jinou template factory, třeba výchozí?
- Šaman
- Člen | 2661
Ok, napíšu to jinak. Chci vyřešit:
- Jak na jednom místě upravit pravidla pro dohledávání šablon jak presenterů, tak komponent (a případně registraci vlastních maker)?
- Aniž by to znamenalo nutnost změnit způsob vytváření komponent v configu.
Když o tom tak přemýšlím, tak je to stejný problém, jako jak dostat
do komponent třeba Translator (je nepovinný a nechci aby s ním měli potomci
jakoukoliv práci). Nette tyto vazby řeší skrytě vytažením
z kontejneru.
Akademická otázka – daly by se vlastně presentery a komponenty přepsat
tak, aby měly jen inject metody s konkrétními závislostmi a neměly
přímou závislost na kontejneru? A pokud ano, je to správná cesta, nebo je
dobrý důvod k tomu používat stávající řešení?
- David Grudl
- Nette Core | 8227
Jak na jednom místě upravit pravidla pro dohledávání šablon a registraci vlastních maker
Umístění šablony komponenty nebo makra v ní použitá jsou zcela jistě pevně daná, jaký má smysl z toho dělat konfigurovatelnou závislost? Výsledkem je pak komponenta, do níž je nutné injektovat jeden velmi specifický objekt, obdobný service locatoru, bež něhož nebude fungovat.
Komponenta si musí makra, která používá, registrovat sama (ať už jakkoliv).
Btw Nette 2.1 si z kontejneru vytahovalo jen Latte, což vyřešil refactoring a od 2.2 si nevytahuje už nic.
- Jan Mikeš
- Člen | 771
Ja to resim bodem 2.:
https://bitbucket.org/…xtension.php?…
Pak maji vsechny factories automaticky inject: true
- Šaman
- Člen | 2661
Dořešení tohoto problému (resp. našeho konkrétního příkladu
s TemplateFactory) je v tomto
vlákně.
Ve zkratce – pokud nám jde jen o funkci, která z názvu (nebo
umístění) třídy vrátí umístění souboru šablony, nebo pokud chceme jen
registrovat vlastní makra, nejedná se o skutečnou službu a není problém
tyto deterministické funkce volat staticky. Tedy není důvod injectovat
závislosti.
- Zax
- Člen | 370
Je tedy možné použít anotaci @inject v komponentě nebo ne? Nebo se to musí nějak explicitně zapnout (pokud možno nějak globálně, abych nepřišel o ten krásný krátký způsob definice služby v configu)? Nebo je to až tak moc „špinavé“, že se to za žádných okolností nedoporučuje (navzdory tomu, že to řeší constructor hell)?
Přiznám se, že jsem si zatím ani netroufl to zkusit, ale rád využiji každou možnost jak si ušetřit psaní, mám různé druhy BaseControlů, které jsou na sobě různě závislé a vyžadují různé služby a už se v těch konstruktorech začínám trochu topit. Anotace @inject v presenterech je fajn a co se tak dívám kolem sebe, tak je to i doporučovaný způsob injectování, jenže já to využiji akorát na továrny pro komponenty. Komponenty na druhou stranu mnohdy závisí i na deseti službách a jejich konstruktory pak nepříjemně bobtnají.
Osobně bych @inject v komponentách vůbec nezatracoval. Naopak, kdyby se to stalo běžnou praktikou, všichni bychom si ušetřili psaní a časem by nad tím většina přestala kroutit hlavou, stejně jako v případě @inject v presenterech. (IMO)
Editoval Zax (22. 5. 2014 2:03)
- Šaman
- Člen | 2661
Anotace @inject
je špatná. Property, které
se tato anotace nastaví musí být public
, tedy porušovat
zapouzdření. Anotaci nepoužívej nikde jinde, než v presenterech.
To, za co já lobuju, je umožnit použití inject
metod. Ty
jsou z hlediska OOP návrhu v pořádku, jsou to vlastně obyčejé settery,
ale v Nette (a jen v něm) je uměl DI kontejner zpracovat tak, že je
automaticky naplnil vhodnou službou. Teď to ale automaticky dělá jen
v presenterech a nikde jinde.
Zapnout se to v aktuálním Nette dá pomocí
tempCacheStorage:
class: Nette\Caching\Storages\DevNullStorage
autowired: yes
Doporučovaný způsob, jak dostat závislosti do komponent a do tříd modelu je ale použít konstruktor.
- Zax
- Člen | 370
Každý ví, že služby v presenteru nepřepisujeme, navzdory tomu že jsou public, tak snad každýmu automaticky docvakne, že to samé bude platit i pro komponenty. V modelu nebo kdekoliv jinde nemám problém použít konstruktor, ale komponenty prostě mají tendenci bobtnat, kromě modelových služeb v nich často potřebuji User, Cache, Session, různé další továrny pro subkomponenty a formuláře a ve finále je v tom hroznej bordel. To je pak zábava přidávat nebo odebírat službu.
Proč je vlastně pro presentery doporučená anotace @inject a ne metody inject, když jsou vlastně čistší? A když to porušení zapouzdření nevadí u presenterů, proč by to mělo vadit u komponent? Není to snad právě kvůli tomu, že s anotacemi se pracuje podstatně jednodušeji než s inject metodami?
Chápu tedy dobře, že anotace @inject je špatná jen z akademického/dogmatického hlediska a jinak je vše v pořádku, pokud se služby nepřepisují? V tom případě jsem ochoten toto oželet, v zájmu pohodlnějšího a rychlejšího vývoje. Nebo lépe řečeno: pro mě by to byla killer feature ;-)
Editoval Zax (22. 5. 2014 4:00)
- Šaman
- Člen | 2661
S tím presenterem je to trochu složitější, on má dost specifické postavení. Jinak ano, anotace @inject (ještě nedávno nepodporovaná) vznikla jen jako zkrácení zápisu namísto inject metod. A ne, zapouzdření není jen akademická záležitost. Imho jeho systematické porušovaní není o nic menší prohřešek, než si ty služby potichu vytáhnout z kontejneru.
Je mnoho dobrých důvodů používat konstruktor injection kdykoliv to jde. Ale občas se myslím hodí mít možnost injectovat autowiringem bez nutnosti zahlcovat konstruktor, typicky pokud očekávám, že se konstruktor bude hodit potomkům a chci jim ho nechat čistý. Nicméně autowiring v tuto chvíli podporuje mimo presentery jen konstruktor injection a nejde to nějak globálně zapnout. Tedy ani inject metody, ani anotace. Při dobrém návrhu to většinou stačí. Pro konkrétní třídy si ten rozšířený autowiring můžes zapnout v neonu. Jestli se ti to nelíbí, zkus napsat sem, protože David je asi jediný, kdo s tím může realně něco dělat. Mě by se ta možnost globálního zapnutí taky líbila, pak je na programátorovi jak moc to bude využívat/zneužívat.
A.nebo zkus tohle.
Editoval Šaman (22. 5. 2014 6:07)
- Zax
- Člen | 370
Po důkladném prohledání tohoto fóra jsem dospěl k následující extension, která mi zapne @inject anotace pro komponenty generované továrničkami, které mají v názvu třídy „Components“. Jsem spokojený :-)
final class ComponentInjectEnabler extends CompilerExtension {
public function beforeCompile() {
$builder = $this->getContainerBuilder();
foreach ($builder->definitions as $definition) {
if ($definition->implement && strpos($definition->implement, 'Components') > 0 && method_exists($definition->implement, 'create')) {
$definition->setInject(TRUE);
}
}
}
}
- Zax
- Člen | 370
Omlouvám se za necro a wall of text.
Objevilo se tu nové téma a to odkazuje sem, proto bych si chtěl trošku spravit svědomí – docela jsem tu totiž lobboval za něco, co už dneska ani moc nechci, zato chci něco jiného (né že bych se neuměl rozhodovat, ale prostě jsem dnes o trošku chytřejší, než před třemi měsíci).
Také bych rád opět na toto téma vyvolal diskuzi, jelikož mi přijde, že
to pořád není úplně domyšlené a pokud si chce člověk ušetřit do
budoucna s*aní práci, musí bohužel používat různé hacky.
Úplně nejradši bych byl, kdyby mi k tomuto příspěvku napsal názor
přímo sám @DavidGrudl (pokud se mu vůbec chce tohle číst), co ho
vedlo k tomu, že jsou pravidla nastavená tak, jak jsou, a ne jinak.
Samozřejmě jsem četl všechny jeho články na téma DI, ale pořád mi pár
věci asi uniká.
Ono se mi to za ty tři měsíce pěkně rozleželo v hlavě, ozkoušel jsem si různé způsoby a dnes bych řekl, že ani jedno není úplně vždycky špatně nebo správně, záleží hodně na kontextu, v jakém se tvoří. Rád bych zde tedy shrnul své poznatky, navrhl nějaká vylepšení a obhájil je na nějakých real-life příkladech. Budu rád, když mě někdo doplní, opraví, nebo třeba řekne, že jsem úplný kretén (ale se zdůvodněním, prosím :D), protože se rád něčemu dalšímu přiučím.
Způsoby injektování:
1. Anotace: Prototypuji, nebo prostě potřebuji mít co nejrychleji hotovo a na čistotě zrovna nezáleží → povolím si všude inject a použiju anotace. Dalo by se argumentovat, že tím člověk nic moc neušetří, inject metody se přece dají generovat pomocí IDE, nicméně někdy i pár ušetřených vteřin (krát počet závislostí) může být dost.
Tento způsob se mi u jednoho webu osvědčil, protože jsem měl velmi rychle hotovo a pokud vím a respektuji, že na tyto public proměnné se nemá sahat, tak v tom ani nevidím žádný větší problém. Ještě dodám, že jde o web, u kterého vím, že se v budoucnu moc rozšiřovat nebo měnit nebude a rozhodně nehrozí, že by kód po mně převzal někdo jiný.
Je ale třeba fakt důsledně respektovat to, že zvenku se na tyto proměnné nesahá a zrovna právě u presenterů (kde je anotace @inject doporučovaný způsob) to k tomu někdy vyloženě svádí – člověk má komponentu a z ní si sáhne do presenteru pro nějakou službu, která tam je takto nainjektovaná. Přitom komponenta by měla vědět prd o tom, co je v presenteru.
Anotace jsou tedy výborný nástroj, pokud si člověk jen tak něco matlá pro sebe, ale pro serious business bych se anotacím vyhnul úplně, jak v presenterech, tak v komponentách.
2. Inject metody: Normálně tvořím, nechci se však babrat se závislostmi předků v potomcích → povolím si všude inject a v předcích použiju inject* metody (v potomcích pak klasicky konstruktor). To mi přijde jako hezký kompromis. Je třeba mít na paměti to, že v konstruktoru potomka nemám k dispozici závislosti předka, zatím jsem ale nenarazil na situaci, kde by tohle vadilo (ale neříkám, že taková situace nemůže vzniknout).
3. Konstruktor/klasické settery: Snažím se tvořit co nejčistěji, třeba dělám nějakou knihovnu, která by měla být použitelná i samostatně, bez DI kontejneru → nepřichází v úvahu ani anotace, ani inject* metody, protože obojí je záležitost konkrétního frameworku a asi málokdo se při ručním instancování dívá, jestli ta třída má vlastnosti s anotací @inject nebo metody inject, které je potřeba naplnit/zavolat. Nicméně v takovém případě většinou ani není potřeba hledat nějaké berličky, protože se dá dědičnosti vyhnout.
To bohužel ale neplatí u presenterů, ani u komponent. A zde bych chtěl zdůraznit – ani u komponent. Pořád si stojím za tím, že z tohoto hlediska se komponenty a presentery neliší absolutně v ničem. Obojí se tvoří tak, že si podědím nějakou base class a vyhnout se dědičnosti prostě dost dobře nejde. A to mi přijde, že je kámen úrazu.
Moje návrhy:
1. Kromě presenterů bych defaultně povolil injecty i u komponent. Potřeba nějaké base komponenty se závislostí na Nette\Security\User (třeba kvůli zabezpečení/oprávnění) je reálná a jen málokomu by se chtělo psát pořád dokola kód typu:
class MojeKomponenta extends MojeBaseKomponenta {
protected $sluzba;
public function __construct(User $user, NejakaSluzba $sluzba) {
parent::__construct($user);
$this->sluzba = $sluzba;
}
}
Takto si vytvořím třeba sto komponent a pak najednou zjistím, že chci třeba místo User použít nějakou svou třídu (např. můžu řešit oprávnění přes anotace – kód, který čte anotace mám napráskaný v base komponentě a rozhodnu se jej vyčlenit do samostatné třídy), jenže člověk si to velmi rychle rozmyslí, má-li najednou přepsat kód na sto místech.
Zrovna u komponent je toto mnohonásobně palčivější, než u presenterů, protože na jeden presenter může připadat klidně dvacet komponent, proto pořád dodnes úplně nechápu ten myšlenkový pochod typu „u presenterů je to v poho, u komponent je to fuj“. Vždyť u presenterů si použitím injectů ušetřím práci jen minimálně, u komponent ale naopak můžu ušetřit desítky či stovky hodin práce a spotřebuju míň kafe a ibalginů na bolest hlavy.
Zde se píše, že "Komponenty jsou typicky vytvářeny přímo v kódu presenteru, nebo prostřednictvím továren, které jsou specifické pro danou aplikaci. V těchto případech Nette nemůže závislosti automaticky předat a není možné použít metody inject*, ani anotaci @inject." – jenže to dnes, kdy máme automaticky generované továrničky (které jsou doporučeným způsobem vytváření komponent), přece neplatí! Takže jaký je vlastně ten pravý (aktuální) důvod inject* metody v komponentách nepoužívat? Čistota? Opravdu chceme na sto místech opakovat stejný kód jen kvůli tomu, abychom za každou cenu udrželi nějakou akademickou čistotu?
2. Rozdělil bych anotace a inject metody – v současnosti jde buď zapnout obojí, nebo nechat obojí vypnuté. Není možné povolit jen inject metody a anotace nechat zakázané. By default bych klidně zakázal anotace a nechal povolené jen inject metody, člověk pak nebude strkat @inject do presenterů a nebude na tyto služby sahat zvenku. Samozřejmě chápu, že by to byl BC break a asi se nikomu nebude chtít přepisovat anotace na metody, tak to berte spíš jako takovou myšlenku do budoucna.
3. Asi by bylo fajn neuvádět anotace jako doporučovaný způsob vkládání závislostí do presenterů. Konstruktor přeci funguje úplně normálně a na base bych opět použil inject metodu. Zde se argumentuje tím, že anotace @inject nekomplikuje kód, což je sice fakt, ale to přece ani konstruktor. Možná si ten argument vykládám blbě, ale konstruktoru rozumí každý a presenter je ve finále zapouzdřený – opět to bráni tomu, aby si člověk z komponenty sahal na služby presenteru. Těch pár řádků navíc (které se dají vygenerovat pomocí IDE) nikoho nezabije. A pokud člověk všechno píše do komponent, tak i s inject metodami presenter obvykle nenaroste na víc, jak sto řádků (víc řádků určitě zaberou createComponent* metody, kde komponenty kromě instancování typicky i nějak nastavuji, než inject metody).
Tak, pusťte se do mě!
- Šaman
- Člen | 2661
Za mě souhlasím. Mám podobný názor, který se přes několik diskuzí
nezměnil. Osobně by se mi inject medody líbily povolené všude, anotace
nikde. Na prototypy je ale taky používám (při refaktoringu je přepíšu),
takže imho povolit všechno všude a je jen na programátorovi, co použije a
proč. Inject metody se hodí třeba na nepovinné závislosti a často se mi
takové nepovinné závislosti objebují v base třídách.
A určitě souhlasím s tím, že presenter a komponenty jsou
skoro totéž.
- Jiří Nápravník
- Člen | 710
Mě se zase líbí anotace. Argument, že je někdo líný a navádí ho to k tomu sahat si pro tyhle atributy z presnteru například v komponentách považuji za lichý. Pokud je člověk prase a chce to tak dělat, tak je to jeho problém. To bychom pak mohli přemýšlet i nad tím, že někdo je líný si předávat závislosti, tak si předá celý DIC a tahá s to z něj…
Ale souhlasím s tím, že by měly být injecty povoleny i v komponentách, nevidím v nich taky žádný větší rozdíl. V ostatních třídách moc nevidím rozumný důvod…
- Vojtěch Dobeš
- Gold Partner | 1316
Závislosti do komponent lze velmi snadno předávat přes konstruktor – v kombinaci s generovanou továrničkou se to chová jako jakákoliv jiná služba.
- David Grudl
- Nette Core | 8227
Zax napsal(a):
Úplně nejradši bych byl, kdyby mi k tomuto příspěvku napsal názor přímo sám @DavidGrudl
Ono k tomu není moc co dodat, napsal jsi to perfektně :-)
…Je ale třeba fakt důsledně respektovat to, že zvenku se na tyto proměnné nesahá a zrovna právě u presenterů (kde je anotace @inject doporučovaný způsob) to k tomu někdy vyloženě svádí – člověk má komponentu a z ní si sáhne do presenteru pro nějakou službu, která tam je takto nainjektovaná. Přitom komponenta by měla vědět prd o tom, co je v presenteru.
Doporučovaný způsob je používat konstruktor. Anotace jsou spíš tolerované. Sahat si pro něco z komponenty do presenteru je problém sám o sobě, ať jde o proměnnou nebo metodu.
2. Inject metody: … Je třeba mít na paměti to, že v konstruktoru potomka nemám k dispozici závislosti předka.
Kdyby to náhodou bylo potřeba, dá se to řešit tak, že se v potomkovi vytvoří taky metoda inject. DI kontejner zaručuje, že ji zavolá jako poslední.
3. Konstruktor/klasické settery: Snažím se tvořit co nejčistěji, třeba dělám nějakou knihovnu, která by měla být použitelná i samostatně, bez DI kontejneru
Tady musím doplnit, že všechny způsoby injektování, které Nette podporuje, jsou použitelné i bez DI kontejneru. Použitelnost bez DI kontejneru je základním rysem DI. I metoda inject je klasický setter, neboť není podmínka, že setter musí začínat prefixem set.
Moje návrhy:
1. Kromě presenterů bych defaultně povolil injecty i u komponent.
Ono to vede k tomu, že pak programátoři místo konstruktorů píšou metody inject. Tedy myslím tam, kde není žádný důvod inject používat.
Inject metody – …úplně nechápu ten myšlenkový pochod typu „u presenterů je to v poho, u komponent je to fuj“.
Inject je úplně korektní věc všude, kde nelze použít konstruktor. Ať už jde o presenter, komponentu nebo jakoukoliv jinou třídu. Že je to automaticky povolené jen v presenterů je dáno spíš tím, že šlo o experimentální řešení a v presenterech to nejvíc pálilo.
Zde se píše, že "Komponenty jsou typicky vytvářeny přímo v kódu presenteru, nebo prostřednictvím továren, které jsou specifické pro danou aplikaci. V těchto případech Nette nemůže závislosti automaticky předat a není možné použít metody inject*, ani anotaci @inject." – jenže to dnes, kdy máme automaticky generované továrničky (které jsou doporučeným způsobem vytváření komponent), přece neplatí!
Ten text je napsaný nešťastně. Jednak obojí používat lze, s tím, že je to potřeba ručně naplnit, a nebo použít továrničku. Pošleš opravu?
2. Rozdělil bych anotace a inject metody
To zbytečně věc komplikuje.
3. Asi by bylo fajn neuvádět anotace jako doporučovaný způsob vkládání závislostí do presenterů. Konstruktor přeci funguje úplně normálně a na base bych opět použil inject metodu. Zde se argumentuje tím, že anotace @inject nekomplikuje kód, což je sice fakt, ale to přece ani konstruktor.
Ano, opět by to chtělo text vylepšit (btw, jsem vděčný, že @Panda tu kapitolu vůbec sepsal, nerodila se snadno a pochopitelně jsou tam věci na vylepšení.)
- Zax
- Člen | 370
@DavidGrudl Díky moc za reakci!
Tady musím doplnit, že všechny způsoby injektování, které Nette podporuje, jsou použitelné i bez DI kontejneru. Použitelnost bez DI kontejneru je základním rysem DI. I metoda inject je klasický setter, neboť není podmínka, že setter musí začínat prefixem set.
To je určitě pravda, nicméně když chci použít knihovnu samostatně, tak nejspíš nebudu zkoumat zdrojáky, jestli tam náhodou není nějaká property s anotací, kterou musím před použitím ručně naplnit. U inject metod to nejspíš i neznalému docvakne, ale u properties spíš ne ;-) Ale uznávám, že by člověk asi musel být blázen, aby tvořil knihovnu tímto stylem.
Inject je úplně korektní věc všude, kde nelze použít konstruktor. Ať už jde o presenter, komponentu nebo jakoukoliv jinou třídu. Že je to automaticky povolené jen v presenterů je dáno spíš tím, že šlo o experimentální řešení a v presenterech to nejvíc pálilo.
Nešlo by to tedy defaultně povolit i u komponent tvořených pomocí generovaných továren? Já už koukal do zdrojáků s tím, že bych to zkusil naimplementovat a poslat pull, ale nějak si ještě nejsem stopro jistý (asi to bude patřit někam sem, ale potřebuji víc času, abych celou tu metodu pochopil ;-)). A taky mám trochu tušení, že to bude BC break, třeba takové Kdyby (teď nevím, jestli zlobí Doctrine nebo Translation) se s mým hackem moc nekamarádí. Ale asi by nebylo špatné to zahrnout do nějaké příští major verze, hm?
Třeba mě osobně to mnohem víc pálí v komponentách než v presenterech, ale zároveň mi nepřijde moc cool, aby moje komponenty závisely na nějakém hacku. Jo, můžu si tam sice závislosti vložit ručně, ale účelem DI kontejneru je, abych to ručně dělat nemusel. :-)
Ten text je napsaný nešťastně. Jednak obojí používat lze, s tím, že je to potřeba ručně naplnit, a nebo použít továrničku. Pošleš opravu?
Pokusím se, ale nebude to hned. Ještě jsem nikdy žádný pull neposílal
(a forkování na mě působí tak nějak divně, jako kdybych „kradl“
veškerý obsah :D), no, všechno je jednou poprvé..
EDIT: Poslal jsem pull, nebylo to tak složité, jak jsem čekal :-) Upravil
jsem jenom předávání závislostí do presenterů, já si až teď všiml,
že generované továrničky jsou popsány níže.
(btw, jsem vděčný, že @Panda tu kapitolu vůbec sepsal, nerodila se snadno a pochopitelně jsou tam věci na vylepšení.)
To věřím, psát dokumentaci (a hlavně ji udržovat, vzhledem k neustálým změnám) určitě není žádná prdel! Smekám před všemi, kdo nějak přispěli.
Editoval Zax (8. 8. 2014 18:25)
- Filip Procházka
- Moderator | 4668
A taky mám trochu tušení, že to bude BC break, třeba takové Kdyby (teď nevím, jestli zlobí Doctrine nebo Translation) se s mým hackem moc nekamarádí. Ale asi by nebylo špatné to zahrnout do nějaké příští major verze, hm?
To proto, že na spoustě tříd inject natvrdo vypínám, protože jeden čas byl zaplý i na službách a já chtěl aby se to chovalo jinak. To že to u některých služeb, na které ty v podstatě sahat nepotřebuješ, potřebuji jinak, je naprosto legitimní.
Zapnout to úplně pro všechny služby úplně automaticky není úplně nejšťastnější řešení.
- David Grudl
- Nette Core | 8227
Filip Procházka napsal(a):
…já chtěl aby se to chovalo jinak. To že to u některých služeb, na které ty v podstatě sahat nepotřebuješ, potřebuji jinak, je naprosto legitimní.
Můžeš to rozvést?
- Šaman
- Člen | 2661
To je přesně ten důvod, proč ten hack nepoužívám. Ignoruje přepínač
inject: no
.
@DavidGrudl: Opravdu bys nemohl přidat konfigurační možnost, kde by
se dalo upravit které třídy se budou autoinjectovat, prosím? S tím, že
výchozí chování bude jak se to chová dnes [Presenter]. Extension, která
natvrdo zapíná inject pro některé třídy na to jdou příliš natvrdo.
- Filip Procházka
- Moderator | 4668
@David Grudl: tak například v kdyby/console máš
commandy, které si uživatel píše sám. Protože při vytvoření
Console\Application
potřebuju, aby všechny commandy měly
instance, abych je mohl předat, tak musím u všech natvrdo nastavit ->setInject(FALSE),
aby se mi na nich nevolaly injecty. Injecty totiž nad commandem zavolám až
v momentě, kdy ho někdo chce použít. Díky tomu uživatel může používat
public @inject
nad commandy, ale neinicializuje to 50 objektů (a
celý graf jejich závislostí), jenom abych vypsal seznam registrovaných
commandů. Až v momentě kdy ho pustím, tak injecty
invoknu ručně a všechno funguje.
Tohle je příklad, kde to nepřestane fungovat když se zapnout injecty, ale bude to jen neoptimální.
Příklad kde mi to rozbijí injecty jsou ty, kde mám službu s metodou co začíná na inject (protože takový název dává smysl a nevidím důvod ji pojmenovat jinak), ale nechci aby ji použil DI Container, protože je použita pro svázání dvou služeb až v runtime.
@Šaman ještě bys mohl udělat to, že v beforeCompile
,
kde zapínáš ty injecty, zkontroluješ že je zapneš jen pro služby co mají
v implement
interface.
- Zax
- Člen | 370
@FilipProcházka Dík, tohle to vysvětluje. A ano, povolovat to všude není úplně nejšťastnější, ale jak mám napsáno v komentáři, je to „because FREEEEEDOOOOM“ (byť zatím injektuji jen v komponentách).
Buď bych se klonil k tomu, co tu navrhuje @Šaman, nebo by možná nebylo úplně od věci zavést jinou konvenci. Navrhuji povolit inject všude, ale s tím, že (libovolně pojmenovaná) metoda musí mít anotaci @inject. Pak si může kdokoliv pojmenovat settery injectNeco a kontejner je bude ignorovat, pokud ta anotace u nich není. Nenapadá mě žádná nevýhoda, snad jen kromě nižšího výkonu při sestavování kontejneru a nutnosti si ty anotace dopsat. Výhodou je pochopitelně možnost injektovat kdekoliv (na tom není nic vyloženě špatného, ne?) a konzistentnější konvence (u vlastností se čte anotace @inject, u metod se čte název, to není úplně konzistentní).
Editoval Zax (9. 8. 2014 17:07)
- David Grudl
- Nette Core | 8227
Filip Procházka napsal(a):
Injecty totiž nad commandem zavolám až v momentě, kdy ho někdo chce použít. Díky tomu uživatel může používat
public @inject
nad commandy, ale neinicializuje to 50 objektů
Ale tohle je zneužívání injektů, v podstatě jako kdyby jsi objekt vytvořil pomocí ReflectionClass::newInstanceWithoutConstructor a teprve později zavolal konstruktor.
- Filip Procházka
- Moderator | 4668
@DavidGrudl je to implementační detail, který nemění chování a zlepšuje výkon ⇒ validní.
- Tomáš Votruba
- Moderator | 1114
Narazil jsem náhodou na InterfaceInjection. Symfony je využit pomocí ContainerAwareInterface (fuj doc), který dělá prakticky
to, že při kompilaci předá všem třídám, které jej imlementují,
$container
pomocí setteru. Jestli jsem to tedy správně
pochopil.
Pokud teď BaseControl
potřebuje změnu, musím buď
předělávat všechny konstruktory nebo přidávat rozšíření na volání
injectu na všech službách od něj dědících dědících.
Napadá mne řešit toto issue pomocí něčeho jako
InjectAwareInterface
. Všechny třídy jej implementující by na
sobě měly volány callInjects()
. Přidám inject metodu,
interface a je to.
Co si o tom myslíte?
Editoval Tomáš Votruba (15. 8. 2014 16:00)
- David Matějka
- Moderator | 6445
@TomášVotruba predevcirem jsem napsal librette/setup. ktery by melo resit problem base trid. Zatim tam neni zadny readme, ale muzes kouknout na par use case, ktery to podporuje (navic je to i rozsiritelne, ale to az jindy :))
- Šaman
- Člen | 2661
matej21 napsal(a):
@TomášVotruba predevcirem jsem napsal librette/setup. ktery by melo resit problem base trid. Zatim tam neni zadny readme, ale muzes kouknout na par use case, ktery to podporuje (navic je to i rozsiritelne, ale to az jindy :))
IMHO ta první use case to řeší velmi elegantně a úplně by mi stačilo, kdyby Nette něco takového podporovalo. Žádná magie, žádné automatické injecty, nebo dokonce anotace, ale nastavit nějak jednoduše, že všechny potomci dostanou nějakou závislost. Za sebe bych to používal asi i pro abstraktní Presentery. Všechny závislosti pak budou buď v konfigu, nebo v konstruktoru, nemusím procházet tři předky abych zjistitl kde se mi vzala/nevzala nějaká property.
- hrach
- Člen | 1838
Celou problematiku resim tak, ze si vsude v configu zapinam inject: on, at uz ho sluzba potrubuje, nebo ne, a mam po problemech. V kodu dane tridy jen min balastu ⇒ vice prehlednosti. To je pro me podstatnejsi, nez zapouzdrenost, ktera je stejne inject metodami porusena (tj. nechapu ty, co prskaj na public property, ale inject jim nevadi.)
Situaci chapu ale jinak u rozsireni, respektive trid, ktere se pouzivaji mimo projekt. Treba takove nextras/orm ma inject jedine pres contructory.
- hrach
- Člen | 1838
@matej21
- objekt se vyskytuje v nekonzistentnim stavu mezi zavolanim contructoru a inject metod, tj. objekt nemuze v contructoru pracovat se zavislostmi. (Korektnim resenim by bylo volat createNewInstanceWithoutContructor, nainjectovat zavislosti, a pak zavolat constructor.)
- jeste jsem ani jednou nevidel korektni implementaci, ktera by kontrolovala, zda neni zavislost jiz injectnuta. toto pri constructor injection standarnim api zmenit nejde. priklad, ze i spicka „nette“ to nedela odpovedne: https://github.com/…resenter.php#… je to totiz moc srani… a pokud si nekdo da tu praci, tak je to presne toho moc balastu, ktery zneprehlednuje kod.
- zavislosti jsou proste stejne skryte jako pri injektovani do public. jediny korektni verejny zapis zavislosti je pres constructor.
Takze, minus jednickovaci, delejte si co chcete, zijte si v omylu, ze pisete krasne ciste, ale proste prasite ;) Ja prasim sice uplne stejne jako vy, ale aspon mam hezci kod.
- David Grudl
- Nette Core | 8227
Uff, pleteš páté před deváté. Jednak nezavolání inject metody nemá nic společného se zapouzdřením. Inject property ho porušují, metody nikoliv.
Dále zda byly inject metody volány není třeba kontrolovat, protože musí být zavolány z definice. Obdobně, jako se nekontroluje, že byl volán konstruktor (jde to obejít), že byly dodrženy type hinty (jde to obejít) a že nebyl zavolán vícekrát (taky to jde).
- Šaman
- Člen | 2661
- Já na tom odkazovaném kódu vidím, že metoda
injectCronBase
akceptuje jen parametry konkrétní třídy. Do property mohu nacpat cokoliv. - Navíc název metody
inject*
mi napovídá, že na ni bych neměl po vytvoření objektu už sahat. Public proměnná$db
ti neřekne nic. - A do třetice, pokud bude někdy problém a budu potřebovat nějak kontrolovat, nebo upravit dodanou závislost, tak do metody to bez problémů dopíšu. U tebe by to znamenalo přepisovat kód, pokud někde vytvářiš objekt mimo DI kontejner.
- Napadá mě ještě jeden argument, ale ten už stoji na hliněných nohách. Někdo se buď v tomhle, nebo podobném vláknu přiznal, že zneužívá property injection i k tomu, že si z komponenty přímo sahá na závislosti presenteru. Při method injection jsou skryté. Pokud člověk ví, že prasí, tak budiž mu tak. Ale tenhle hack může objevit i nováček, který ještě neví, na jaké problémy si zadělává.
A to celé nijak nesouvisí se zapouzdřením :)
Editoval Šaman (15. 8. 2014 16:07)
- Tomáš Votruba
- Moderator | 1114
@matej21 Moc pěkné, díky.
@hrach Souhlasím s balastem a částečně zapouzdřeností. Pokud by sis chtěl ušetřit balast i v configu (tedy přibližně polovina obsahu screenshotu), můžeš si na inject vytvořit malé rozšíření.
- David Matějka
- Moderator | 6445
objekt se vyskytuje v nekonzistentnim stavu mezi zavolanim contructoru a inject metod, tj. objekt nemuze v contructoru pracovat se zavislostmi.
opravdu delas v konstruktoru neco, co vyzaduje zavislosti inject metod?
(Korektnim resenim by bylo volat createNewInstanceWithoutContructor, nainjectovat zavislosti, a pak zavolat constructor.)
a co kdyz bych chtel v inject metode pracovat se zavislostmi konstruktoru? (ano, je to asi stejna hloupost jako predchozi bod, ale v metodach tak nejak predpokladam, ze se volal konstruktor, naopak ne)
jeste jsem ani jednou nevidel korektni implementaci, ktera by kontrolovala, zda neni zavislost jiz injectnuta
neni treba chodit daleko https://api.nette.org/…ter.php.html#…
je to totiz moc srani…
To uz rovnou muzes rikat, ze vsechny property muzou byt public, protoze psat settery, ktery osetri vstup, je moc srani.
- David Grudl
- Nette Core | 8227
Doplnil jsem dnes do DI kontejneru řadu fíčůrek, které mi ležely v branchích, jednou z nich, která souvisí s tímto tématem, je TemplateExtension (pracovní název).
Jejím účelem je doplnit setup, tagy nebo změnit nastavení injection na základě typu služby. Třeba lze povolit inject pro všechny komponenty:
templates:
Nette\Application\UI\Control:
inject: on
Nebo zavolat určitou službu na BasePresenteru:
templates:
App\BasePresenter:
setup:
- setSomething(...)
Ovšem týká se to jen presenterů, které vytváří zkompilovaný container. To znamená, že takový presenter musí být uveden v services.
- Šaman
- Člen | 2661
Hurá! Ale… proč templates?
A nešlo by to sjednotit se samotným vytvářením služeb?
services:
- App\Presenters\BasePresenter
inject: on # já vím, že tohle u Presenterů nemá smysl
setup:
- setFoo(...) # ty tři tečky fungují? Jako že se doplní služba podle typu?
- App\Presenters\BarPresenter
inject: off # do všechn presenterů autoinjectovat, ale sem ne
Edit: Aha, asi nešlo. Nechci přece vytvářet instanci BasePresenteru.
A spoléhat se na to, že bude abstract
je moc magické.
Editoval Šaman (15. 8. 2014 18:00)
- David Matějka
- Moderator | 6445
@DavidGrudl good, tak nemusim pokracovat ve vyvoji toho meho extension :)
jen par poznamek:
- do tech sablon by se hodila podpora trait
- kdyz uz jsi u tech uprav, tak bych v ServiceDefinition zmenil defaultni
hodnotu inject na
null
– respektovaly by to pak ty „sablony“ a inject zapnuly pouze v pripade, ze$def->inject===null
– to by umoznilo explicitne vypnout inject i pres to, ze sablona by to mela zapnout - pokud se nepletu, tak tam nebude fungovat zkraceny zapis tagu bez argumentu,
tedy
tags: [foo]
edit:
jo a moc se mi nelibi ten zapis klic: sablona
a taky tam asi nepude setup bez parametru, tedy jen - injectFoo
(a predpokladat, ze se zbytek autowiruje)
Editoval matej21 (15. 8. 2014 17:59)
- David Grudl
- Nette Core | 8227
Šaman napsal(a):
Hurá! Ale… proč templates?
Historický název, původně to fungovalo fakt jako šablony. Teď bych rád použil jiný.
matej21 napsal(a):
- do tech sablon by se hodila podpora trait
S traity nevím, jednak by to chtělo řešit možné aliasy metod, ale hlavně si nejsem jistý, jestli to nepodporuje prasení…
- kdys uz jsi u tech uprav, tak bych v ServiceDefinition zmenil defaultni hodnotu inject na
null
Rozumím, ale systémové řešení to není, protože podobně by se mohlo hodit nastavovat i autowired, kde je výchozí TRUE. Zatím to nechám otevřené. (Možné řešení https://github.com/…72f6b8e31d99)
pokud se nepletu, tak tam nebude fungovat zkraceny zapis tagu bez argumentu, tedy
tags: [foo]
+ nepude setup bez parametru
Opraveno.
- hrach
- Člen | 1838
@dg:
Málem mě omylo, když čtu tvou milnou argumentaci, bez pochopeni principu zapouzdrenosti.
Uff, pleteš páté před deváté. Jednak nezavolání inject metody nemá nic společného se zapouzdřením
Pokud objekt ve svem rozhrani nabizi stav, jez je mozne dosahnout spatnou praci s api objektu, pak se jedna prave o prilis benevolentni zapouzdreni. Zapouzdreni neznamena, ze je vsechno private, zapouzdreni znamena, ze se neda porusit konzistence objektu. Ze to resime pomoci private property, to je dusledek, implementacni detail. Nezavolanim metody dostavame objekt do stavu, ktery z principu nikde v kodu nekontroluje a neosetruje. Tim tedy vznika problem nedostatecneho zapouzdreni.
Inject property ho porušují, metody nikoliv.
Porusit zapouzdreni lze metodami jednoduseji. Aho, property ho porusuji, ale metody tez.
Dále zda byly inject metody volány není třeba kontrolovat, protože musí být zavolány z definice.
Davide, jsi autorita, ale ze si jednoho dne napsal clanek, jeste neznamena, ze jsi stvoril nějakou definici jak pracovat se zavislostmi v daném jazyce. Definice není nikde napsaná, nikde zdokumentovana (u kodu, povina zavislost rozsireni jazyka, apod.). Nebo mi můžeš říct, kde si danou definici mám vycucat např. u UI\Presenteru? Prostě jsi tam plácnul kód a očekáváš, že ho všichni budou používat tak, jak nettí DIC.
Pěkný protipříklady jsou @FilipProcházka usecasy.
Přesně tento problém, že to není nikde zadefinované, řeší pěkně již zmíněné symfony, které vyžaduje interface. Je to sice slabý kontrakt, ale aspoň nějaký.
Obdobně, jako se nekontroluje, že byl volán konstruktor (jde to obejít)
Prosim, prestan s tou demagoii. Je snad jasne, ze se bavime o vychozich chovanich. Vsechno jde obejit. Vychozi chovani constructor injeciton netrpi vadami jako metod / property injection.
že byly dodrženy type hinty (jde to obejít) a že nebyl zavolán vícekrát (taky to jde)
Souhlasím, u obou to je velmi jednoduse porusit, aniz bych vyuzival nejake dalsi nastroje (reflexi).
@matej21
neni treba chodit daleko https://api.nette.org/…ter.php.html
ale no tak, moc dobre vis, ze to je prvni a posledni kod, ktery to tak ma.
a co kdyz bych chtel v inject metode pracovat se zavislostmi konstruktoru?
netrapci. vymyslis hovadiny, misto abys to podlozil normalnim usecasem. a to je napr. to, ze v potomkovi (dejme tomu treba basepresenteru) volam jeho API (protected, nebo i verejny!) v konstruktoru – kde si sam ziskavam zavislosti. jak mam proboha tusit, ze ten predek to api nabizi az po te, co jsou mu zavislosti nainjektovany pomoci inject metod? Toto je poruseni te zapozdrenosti, musis znaz vnitrni implementaci, abys to nezprasil.
mj. z toho duvodu ma presenter metodu startup, ktera je v podstate jenom odlozeny constructor. a implementovat si toto do vsech trid, ktere maji inject metody… hovadina. Tj. zopakuji, inject metody jsou velmi nedobre a polovicate reseni, casto staci – ale presne ve chvili kdy staci, pak pro me ma vyssi hodnotu public property injection.
jinak presne timto problemem trpi Control/BaseControl, ktery nema startup metodu, a kvuli kteremu se to tady rozjelo.
To uz rovnou muzes rikat, ze vsechny property muzou byt public, protoze psat settery, ktery osetri vstup, je moc srani.
vsak presne to si myslim. napr. https://github.com/…Metadata.php vyzaduju rychlost. je to z podstaty interni trida, resit tu gettery a settery je hloupost.
- David Grudl
- Nette Core | 8227
Inject metody vznikly jako velmi konkrétní řešení problému s dědičností a konstruktorem. Nic víc, nic míň. Jsou tedy přesně definovány. Pokud je Filip používá jinak, je to jeho věc, zároveň uvedl-li to jako důvod proti automatickému zapnutí inject, neměl jeho argument sebemenší váhu.
Inject metody nemohou v žádném případě dostat objekt do špatného stavu, stejně tak, jako to nemůže udělat konstruktor. Pokud poruším kontrakt, není to vadou inject metod, ale toho, kdo ten kontrakt porušil.
To, čemu říkáš odložený konstruktor, není nic jiného než … tradá … metoda inject.
To, že Presenter kontroluje dvojité volání inject, je naprostá zbytečnost a nemá v kódu co dělat.
BTW, zpětně bych místo prefixu inject použil construct.
- Climber007
- Člen | 105
Zajímavý čtení. Souhlasím s @hrach, že se objekt může dostat do nekonzistentního stavu, ale na druhou stranu – proč bych to proboha dělal, když vím, že to nejde? Ideální by byla v komponentách ta obdoba metody startup(), která může suplovat konstruktor, ve kterém ještě nejsou předány všechny závislosti. Občas je potřeba něco inicializovat hned na počátku.
Čím víc mám v aplikaci závislostí, tím horší je se v tom vyznat, to je jasné. Konstruktor na dva řádky už je hrozný bordel. Optimální cesta jsou podle mě buď inject* metody nebo public properties, ať si každý používá co chce. Když bude chtít někdo prasit a sahat kam nemá, jak už někdo psal, klidně si předá celý DIC, context a já nevím co ještě. Raději budu mít čistý a přehledný kód, než abych se v něm nevyznal a byl se v prsa, že používám teoreticky správné řešení.
- David Grudl
- Nette Core | 8227
@Climber007 opakuji, ta metoda startup tam je, jen se místo startup jmenuje inject.
- David Matějka
- Moderator | 6445
@Climber007 inicializaci komponenty doporucuju provadet v attached
use Nette\Application\UI;
class FooControl extends UI\Control
{
public function attached($presenter)
{
parent::attached($presenter); //nezapomenout zavolat
if($presenter instanceof UI\Presenter) { //jelikoz to muze byt i necho jinyho
//tady nejdriv muzes pracovat s presenterem - vytvaret linky, plnit sablonu....
}
}
}
- Climber007
- Člen | 105
@DavidGrudl Už mi to došlo, jak jsi to myslel.
@matej21 Jasný. Inicializací jsem myslel obecně společný kód
volaný při vytváření komponenty. Hlavně jde o to, že když už se
poruší zapouzdření, musíš si dát bacha. Když je dotyčný debil, tak je
asi jedno, jestli jsou property public nebo private… Používám
@inject v presenterech běžně a ani mě nenapadne po těch properties
sahat. To už ani není otázka Nette, ale spíš znasloti OOP a čistého
návrhu. Udělat to úplně blbuvzdorný na úkor krásy je hovadina.