Jak dostat do BaseControl službu, aniž by se jí museli potomci zabývat?

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

(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.

  1. 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á.
  2. inject metoda: bohužel nefunguje, pokud každému potomkovi nenastavíme příznak inject: true
  3. anotace @inject: nefunguje a ani nechci, tady už by mi porušení zapouzdření vadilo
  4. 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.
  5. 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
+
0
-

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
+
0
-

Ok, napíšu to jinak. Chci vyřešit:

  1. Jak na jednom místě upravit pravidla pro dohledávání šablon jak presenterů, tak komponent (a případně registraci vlastních maker)?
  2. 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í?

mkoubik
Člen | 728
+
0
-

David Grudl napsal(a):

Bude ta komponenta fungovat dobře i s jinou template factory, třeba výchozí?

Metoda setTemplateFactory může mít typehint na My\TemplateFactory.

David Grudl
Nette Core | 8227
+
0
-

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.

Eda
Backer | 220
+
0
-

Já problém s předáním translatoru řeším přepsáním metody attached() a vytažením potřebných věcí z presenteru. Neřešilo by to i ten tvůj use-case, Šamane? Ale pokud přijdete na něco elegantnějšího, budu jen rád :-)

Jan Mikeš
Člen | 771
+
+1
-

Ja to resim bodem 2.:

https://bitbucket.org/…xtension.php?…

Pak maji vsechny factories automaticky inject: true

Šaman
Člen | 2661
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
+7
-

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
+
0
-

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
+
+1
-

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
+
+3
-

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.

Šaman
Člen | 2661
+
+3
-

To do presenterů taky.
Bavíme se tady o závislostí BaseControl, aniž bychom se tim museli zabývat v konstruktoru potomka.

David Grudl
Nette Core | 8227
+
+1
-

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
+
0
-

@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
+
0
-

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
+
0
-

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
+
+1
-

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
+
0
-

@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.

enumag
Člen | 2118
+
0
-

@FilipProcházka, @Šaman Možná bych se u toho interface ještě podíval na @return anotaci a dal inject jen pokud to opravdu vyrábí komponentu a ne něco jiného.

Šaman
Člen | 2661
+
0
-

Právě že by bylo ideální moci si nastavit třídy (včetně abstraktních), u kterých by se injectovalo. Pokud vytvořím BaseClass a nechci nějakou implicitní závislostí zatěžovat potomky, tak bych si tu třídu přidal do seznamu a její potomci by měli autoinject zapnutý.

Zax
Člen | 370
+
0
-

@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
+
0
-

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
+
0
-

@DavidGrudl je to implementační detail, který nemění chování a zlepšuje výkon ⇒ validní.

Tomáš Votruba
Moderator | 1114
+
+3
-

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
+
+5
-

@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
+
+2
-

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.

enumag
Člen | 2118
+
+2
-

@matej21 Tomášův nápad s injecty se mi líbil a tvé extension navíc velmi elegantně řeší i tagy. Myslím, že tohle budu používat, díky! :-)

hrach
Člen | 1838
+
-5
-

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.

David Matějka
Moderator | 6445
+
+2
-

@hrach cim inject metody porusuji zapouzdrenost?

hrach
Člen | 1838
+
0
-

@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
+
+3
-

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
+
+1
-
  1. Já na tom odkazovaném kódu vidím, že metoda injectCronBase akceptuje jen parametry konkrétní třídy. Do property mohu nacpat cokoliv.
  2. 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.
  3. 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.
  4. 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
+
0
-

@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
+
0
-

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
+
+9
-

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
+
0
-

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
+
0
-

@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
+
0
-

Š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
+
-2
-

@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
+
0
-

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
+
0
-

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
+
+1
-

@Climber007 opakuji, ta metoda startup tam je, jen se místo startup jmenuje inject.

David Matějka
Moderator | 6445
+
+2
-

@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
+
0
-

@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.