Dependency Injection v Nette 2.1

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 8227
+
0
-

Pár návrhů, kam posunout DI v další verzi frameworku.

Environment & $context

I v dalších verzích chci zachovat podporu statického a objektového service locatoru. Jen možná by se $context nepředával do presenteru automaticky, ale kdo by ho chtěl, předal by si ho pomocí konstruktoru či metody inject (tj. samotný framework by ho nevyužíval).

Jelikož běžící aplikace má jediný composition root a jediný DI kontejner, nic nebrání tomu zachovat i statický přístupový bod Environment. Pochopitelně to neznamená, že jeho používání je doporučované a v dokumentaci bych se o něm vůbec nezmiňoval.

Částečné sloučení Nette\DI a Nette\Config

Třídy Compiler a CompilerExtension natolik úzce souvisí s DI kontejnerem, že bych je přesunul pod Nette\DI. (See https://forum.nette.org/…fig-nette-di)

(Ne)dynamický DI kontejner

Nette\DI\Container má podporu pro dynamické přidávání a rušení služeb, což koliduje se statickou kompilací. Mám pocit, že tolik dynamicky ve skutečnosti není vůbec potřeba a úplně by stačilo mít pouze možnost zaměnit dosud neinstancovanou službu za jinou. Tj. addService a removeService nahradit za setService().

Expandování parametrů v extensions (Implementováno)

CompilerExtensions pracují s neexpandovanými proměnnými, tj. v parametrech vidí řetězce jako třeba %tempDir%/abc a stejně tak podobné řetězce mohou používat v definicích služeb. Expanze se provádí až při generování DI kontejneru.

Zpětně si nejsem jist, jestli to jen nekomplikuje situaci. Rozšíření má sice možnost expandovat parametry jiným způsobem nebo změnit hodnoty parametrů, jen nevím, jestli to má přínos. Nevýhodou je, že se na to musí myslet, tj vše před „použitím“ expandovat a při vkládání hodnot do definic je escapovat (!). Asi bych to změnil.

Nepojmenované služby (Implementováno)

Služby, u kterých nezáleží na názvu, protože se využívá autowiring, se zapisují tímto způsobem:

services:
	App\Authorizator: self

Zjistil jsem, že tento zápis je pro mnohé nesrozumitelný, proto bych jako preferovaný způsob navrhoval

services:
	- App\Authorizator

Příklad mixu:

services:
	user: Nette\Security\User
	db: DibiConnection(...)
	- App\Authorizator
	- MyOAuth
	-
		class: Panel
		inject: true

S tím souvisí i úvaha, jestli u pojmenovaných služeb automaticky nenastavovat autowired na false (s možnost globálně vypnout kvůli kompatibilitě).

Klíč factory v konfigurátoru (Implementováno)

Podobně nesrozumitelný se jeví klíč factory v definici služeb, ještě hůře v definici továrniček:

services:
	authorizator:
		factory: App\Authorizator(...)

factories:
	latte:
		factory: Nette\Latte\Engine
		implement: ILatteFactory

Navrhoval bych jej změnit třeba na create: (tj. držet se popisu pomocí sloves create, setup, implement namísto podstatným jmen factory, methods*, interface).

services:
	authorizator:
		create: App\Authorizator(...)

factories:
	latte:
		create: Nette\Latte\Engine
		implement: ILatteFactory

Validační schéma pro rozšíření

Pokud se překlepnu v identifikátoru v konfiguračním souboru nebo neznám jeho přesnou strukturu, často to nezpůsobí žádnou chybu a klíč se ignoruje. Validační schéma by mělo zajistit striktní kontrolu jeho obsahu. Alespoň pro rozšíření nette, pokud se nestihne obecný validátor.

Patrik Votoček
Člen | 2221
+
0
-

David Grudl napsal(a):

Environment & $context …

Jsem pro mělo by se ale ze samotného frameworku odstranit veškeré volání contextu / Environmentu (formuláře, Control::createTemplate, …).

Částečné sloučení Nette\DI a Nette\Config …

To dává smysl celkem často přemýšlím jestli je to či ono v Config nebo v DI.

(Ne)dynamický DI kontejner …

Nikdy jsem nevyužil…

Expandování parametrů v extensions …

Vyhovuje mě současný stav. Občas potřebuju neexandovanou podobu. (protože teprve přidámvám parametr).

Nepojmenované služby …

Jde nějak zapsat složitější nastavení anonymní služby? (tj. včetně create a setup).

Klíč factory v konfigurátoru …

u služeb mě moc nesrozumilený nepřipadá u továrniček je to hodně wtf…

Validační schéma pro rozšíření …

tady hodně záleží na API jinak to lidi nebudou používat

autowire služeb s více instancemi

Věc která mě ohledně DI aktuálně trápí nejvíce je více než jedna služba implementující stejné rozhraní. HosipLan tady navrhuje nějaké řešení.

Mám totiž v aplikaci 30+ repositories (všechno to jsou instance Nella\Doctrine\Repository). Liší se jenom tím pro kterou entitu jsou určeny. A potřeboval bych tyhle služby nějak elegantně dostat do presenteru. Což aktuálně nejde (bez hackování DIc a PresenterFactory).

Dělat pro každou entitu „prázdnou“ třídu repository mě připadá více než zvrhlé.

Filip Procházka
Moderator | 4668
+
0
-

S tím souvisí i úvaha, jestli u pojmenovaných služeb automaticky nenastavovat autowired na false (s možnost globálně vypnout kvůli kompatibilitě).

Tak jak se to chová teď tak je to maximálně pohodlné. Tohle bych neměnil.

Editoval HosipLan (21. 11. 2012 13:06)

David Grudl
Nette Core | 8227
+
0
-

Protože jsem nahodil hodně témat, abychom udrželi diskusi, nepřidávejte prosím žádné další.

Pavel Kouřil
Člen | 128
+
0
-
services:
    authorizator:
        factory: App\Authorizator(...)

mi přijde srozumitelnější než varianta s „create“ … slouží snad klíčové slovo „factory“ k tomu, aby vytvořilo danou službu, ne? tím pádem by adekvátní protějšek byl spíš něco jako „creator“, což je prostě divné (create je spíš, co to vytvoří, ne čím to bylo vytvořeno)

jinak u těch factories – nemělo by být spíše „implements“, než „implement“? třetí osoba jednotného čísla v angličtině končívá na „s“ :)

Editoval Pajka (21. 11. 2012 13:08)

David Grudl
Nette Core | 8227
+
0
-

V zápisu factory: App\Authorizator(...) není App\Authorizator(...) továrna, ale volání operátoru new a kontruktoru. Tedy továrna v meta-smyslu. Obecnější varianta factory by skutečně byla creator, ale jak píšeš, to je divné.

Srozumitelnější je mi proto jeví používat slovesa a celý popis brát jako návod. Proto create, setup, implement. (místo factory, methods, inteface).

Honza Marek
Člen | 1664
+
0
-

David Grudl napsal(a):

Expandování parametrů v extensions

U takových změn bych byl dost opatrný. Většina lidí to třeba nepotřebuje, ale pak se najednou najde někdo, kdo ano a pak bude mít problém.

Nepojmenované služby

Jak se bude míchat zápis pojmenovaných a nepojmenovaných služeb?

S tím souvisí i úvaha, jestli u pojmenovaných služeb automaticky nenastavovat autowired na false (s možnost globálně vypnout kvůli kompatibilitě).

Tohle považuju za neočekávanou nekonzistenci.

Klíč factory v konfigurátoru

U služeb je to naprosto v pohodě. U továrniček ne až tolik. Ale to nevadí, beztak se nedaj používat bez $contextu, takže se používat nedaj. Nebo se pletu?

Validační schéma pro rozšíření

Symfony to má a je to moc fajn. Na chybové hlášky o chybějících či přebývajících parametrech je radost se koukat.

Pavel Kouřil
Člen | 128
+
0
-

A pro klasické továrny by nadále zůstalo slovo „factory“ při vytváření služby?

Jinak minimálně proti „implement“ jsem docela dost zásadním způsobem – přijde mi, že pro každého PHP programátora bude vrozenější psát „implements“ (píšou to dost často v samotném PHP kódu) a i jako sloveso pro tu konkrétní továrnu vždy dává mnohem větší smysl s tím -s.

David Grudl
Nette Core | 8227
+
0
-

Úvodní post jsem doplnil.

Vojtěch Dobeš
Gold Partner | 1316
+
0
-

Nešlo by sjednotit class a create do výsledného create? Teď nevím, jestli nenavrhuju pitomost, ale vím, že se někdy přistihnu při rozmýšlení jestli použít to nebo ono.

implements mi přijde naopak zavádějící (a dlouhé), buď implement nebo ještě něco geniálnějšího.

Editoval vojtech.dobes (21. 11. 2012 13:32)

Pavel Kouřil
Člen | 128
+
0
-

Vymyslet něco popisnějšího klidně … i když tady to dává smysl imho perfektní. Ta továrnička implementuje nějaké rozhraní – další možnost je už imho jen interface, popř. type (v podstatě je to typed factory).

Ale … to je vám fakt jedno, že implement je v podstatě gramaticky špatně?

Zkuste si to říct jako větu „Nette\Latte\Engine factory (implement|implements) ILatteFactory“ – jen jedna z těch dvou možností je správně. :)

Patrik Votoček
Člen | 2221
+
0
-

v případě factory: Foo::create(...) mě dává větší smysl factory v případě factory: Foo(...) nějak nechápu proč se nepoužívá rovnou class: Foo(...).

David Grudl
Nette Core | 8227
+
0
-

vojtech.dobes napsal(a):

Nešlo by sjednotit class a create do výsledného create?

Class není potřeba vůbec používat, jen jako typehint, pokud továrna nemá anotaci @return. V podstatě by se to mohlo jmenovat typehint.

David Grudl
Nette Core | 8227
+
0
-

Implement je gramaticky zcela v pořádku, znamená to implementuj.

Pavel Kouřil
Člen | 128
+
0
-

Ahhh, takže ty to chceš jako _příkazy_ pro tu továrnu, ne jako její _popis_? (trochu se to sice rozchází s „tj. držet se popisu pomocí sloves“, ale jako rozkaz pro tu továrnu, že má něco implementovat, už to gramaticky smysl dává).

Sme si akorát nerozuměli asi, já v tom furt hledal popisování chování/vlastností té továrničky. :)

[Nicméně za argumentem, že je IMHO pro většinu programátorů „přirozenější“ psát implements a mít to nekonzistentní, si pořád ještě stojím. ;)]

pekelnik
Člen | 462
+
0
-

Environment & $context

Volitelne injektovani kontejneru asi ano, protoze jinak by mohl byt upgrade existujicich aplikaci extremne bolestivy. Environment bych zrusil – pokud vsak zustane zachovano zily mi to trhat nebude.

Částečné sloučení Nette\DI a Nette\Config

Jsem pro – jak pise Patrik obcas nevim co je kde…

(Ne)dynamický DI kontejner

Nepouzivam.

Expandování parametrů v extensions

Rozhodne zachovat neexpandovane parametry!

Tohle povazuju za jednu z killer feature. Pristup k %appDir%, %tempDir% a moznost definovat si vlastni %tempDir%/whatever a podobne.

Urcite to nic nekomplikuje narozdil od situace kdy by parametry uz byly expandovane. Nezaznamenal jsem ani ze by to nekoho matlo.

Nepojmenované služby

services:
	App\Authorizator: self

Tento zápis je skutecne ani ne tak nesrozumitelný jako spis neintuitivni a tedy spatny z hlediska UX ;)

services:
	- App\Authorizator

Tento zapis se mi libi a pripada mi zaroven nejlepsi mozny, protoze obsahuje 0 znaku navic :)

…nastavovat autowired na false s možnosti globálně vypnout…

Toto bych urcite nemenil. Stavajici system musi zustat zpetne kompatibilni. Takze pokud ano tak s moznosti globalne zapnout toto nove chovani. (Me osobne by vubec navadilo umazat vsechny nazvy)

S tim take lehce souvisi Hosiplanovy genericke sluzby. Poslouchaj, Hosiplan, podobny pristup by mohl byt pouzit u konfigurace tech sluzeb:

services:
	-
		class: App\Repository
		generic: App\Entity

Klíč factory v konfigurátoru

Klic factory v sekci services mi pripada v poradku. Libilo by se mi pouzit create pro soucasnou „meta-factory“:

services:
	-
		create: `App\Service(...)`

Soucasne bych ale urcite zachoval factory pro oznaceni tovarny. To by navic mohlo akceptovat tovarni tridy, ktere by mohly implementovat nejake to rozhrani:

interface IFactory
{
	function create();
}
services:
	-
		factory: App\ServiceFactory

Jako oznaceni tovarny se me osobne se nejvic libi class, proste proto, ze je to jasne nejkratsi slovo – prosim nebrat na lehkou vahu. Kazdy programator vi, ze slova interface natoz implements jsou mnohem horsi na psani.

Pokud jde o semantiku – ta trida se vytvori – takze to je trida – oznacena rozhranim.

factories:
	latte:
		create: Latte\Engine
		class: Latte\IFactory

Validační schéma pro rozšíření

Pokud by to vypadalo takhle nejak – jsem vsema deseti pro.

$rootNode
    ->arrayNode('connection')
        ->children()
            ->scalarNode('driver')
                ->isRequired()
                ->cannotBeEmpty()
            ->end()
            ->scalarNode('host')
                ->defaultValue('localhost')
            ->end()
            ->scalarNode('username')->end()
            ->scalarNode('password')->end()
            ->booleanNode('memory')
                ->defaultFalse()
            ->end()
        ->end()
    ->end()
;

Syntax se jiste necha vymyslet inteligentnejsi…

Editoval pekelnik (21. 11. 2012 19:31)

Filip Procházka
Moderator | 4668
+
0
-

Validační schémata by asi byla podobná těm co má symfony.

pekelnik
Člen | 462
+
0
-

Dik za odkaz :) Doplnil jsem svuj prispevek o ukazku.

Editoval pekelnik (21. 11. 2012 19:30)

mishak
Člen | 94
+
0
-

U továrniček bych rozhodně nechal

factories:
    latte:
        create: Latte\Engine
        implement: Latte\IFactory

Jde o popis továrny do lidského jazyka přeloženo jako: „Factory latte will implement interface Latte\IFactory and will create class Latte\Engine.“
Implement je tu správně, protože jde o návrh budoucí továrničky, ne popis existující továrničky.

Class nepřipadá v úvahu už z principu – třídy přinášejí restrikce jen kvůli lenosti implementovat správná rozhraní. Navíc je to trochu redundantní protože třídu a její rodiče lze vytáhnout z kódu a nemá smysl jednu speciální znovu zapisovat. (z více pohledů to není dobrý nápad)

Editoval mishak (21. 11. 2012 20:02)

pekelnik
Člen | 462
+
0
-

„Factory latte will implement interface Latte\IFactory and will create class Latte\Engine.“

Asi jo no… ale stejne se to blbe pise ;)

Pavel Kouřil
Člen | 128
+
0
-

mishak napsal(a):
Jde o popis továrny do lidského jazyka přeloženo jako: „Factory latte will implement interface Latte\IFactory and will create class Latte\Engine.“
Implement je tu správně, protože jde o návrh budoucí továrničky, ne popis existující továrničky.

  1. Proč bys zbytečně „zaváděl“ jiný termín, než používá PHPčko (implements)? Bude to působit zmatečně.
  2. Může to být i popis existující továrničky (záleží, jeslti ji jako za vytvořenou považuješ už v době napsání do configu nebo ne – já třeba ano) ve stylu „Latte factory implements IFactory and is going to create instance of Latte\Engine“.

A to create vypadá kostrbatě taky jaksi. Ne nadarmo se říká, že pojmenovávání věcí je jedna z nejhorších věcí ohledně programování. :)

David Grudl
Nette Core | 8227
+
0
-

Nepojmenované služby pomocí odrážky a klíč create jsou už v dev verzi, můžete vyzkoušet.

David Grudl
Nette Core | 8227
+
0
-

Podpora pro typo implement vs implements tam klidně může být taky.

Filip Procházka
Moderator | 4668
+
0
-

To by mohly řešit ty schémata…

$config->scalar('implement')
	->commonTypos('implements', 'impl', ...);
paranoiq
Člen | 392
+
0
-

ta trčící odrážka je hrozné WTF. vypadá to jako typo

pekelnik
Člen | 462
+
0
-

paranoiq napsal(a):

ta trčící odrážka je hrozné WTF. vypadá to jako typo

Taky me to napadlo… jenze co s tim?

Filip Procházka
Moderator | 4668
+
0
-

Napíšeš tam jméno… :) Imho je vhodné to používat takhle

services:
	- NazevTridy

Pokud už autowiring a tenhle simple zápis nestačí, tak pojmenovat..

services:
	nazev:
		class: NazevTridy
		setup:
			- ...

nedalo by se ještě tohle?

services:
	Foo\NazevTridy(@httpRequest): #anonymni
		setup:
			- ...

Editoval HosipLan (22. 11. 2012 13:38)

Honza Marek
Člen | 1664
+
0
-

HosipLan napsal(a):

nedalo by se ještě tohle?

services:
	Foo\NazevTridy(@httpRequest): #anonymni
		setup:
			- ...

Něco takového používáme v Mediu v našem upraveném symfoním DI kontejneru.

services:
    Foo\NazevTridy:
        arguments: [@httpRequest]
David Grudl
Nette Core | 8227
+
0
-

paranoiq napsal(a):

ta trčící odrážka je hrozné WTF. vypadá to jako typo

Vim. Ale zatím jsem nenašel způsob, jak to řešit. Tedy zejména, pokud odsazuju tabulátory. (zlatý mezery!)

David Grudl
Nette Core | 8227
+
0
-

Honza Marek napsal(a):

Něco takového používáme v Mediu v našem upraveném symfoním DI kontejneru.

Ano, a to je právě ta problematická věc. Proč je jednou název třídy před dvojtečkou a jindy za dvojtečkou. Proto experimentuju s odrážkou jakožto náhradou.

pekelnik
Člen | 462
+
0
-

Něco takového používáme v Mediu v našem upraveném symfoním DI kontejneru.

services:
    Foo\NazevTridy:
        arguments: [@httpRequest]

Me se tohle libi.

Honza Marek
Člen | 1664
+
0
-

David Grudl napsal(a):

Proč je jednou název třídy před dvojtečkou a jindy za dvojtečkou.

Filozoficky se to dá vysvětlit tak, že to není služba bez názvu, ale služba s názvem stejným jako je název třídy. A když není definovaná třída služby, tak se vezme z názvu služby, protože předpokládáme, že tuhle konvenci bude dodržovat 95% služeb.

Proto experimentuju s odrážkou jakožto náhradou.

To je to samé. Proč je tam někdy odrážka a někdy ne?

Filip Procházka
Moderator | 4668
+
0
-

Mně se též líbí třída v názvu. Odpadá tím otravná otázka „jak službu pojmenovat?“

juzna.cz
Člen | 248
+
0
-

Moc se mi nelíbí používání sloves (create, implements, …) v konfiguračním souboru. Přijde mi, že konfigurace má být deklarativním stylem a popisovat tedy, co je v DIC a ne jak se to vytvoří. Když se použijí slovesa, začne se říkat jak, tak vzniká nový programovací jazyk. Nevím, zda to chceme?

hrach
Člen | 1838
+
0
-

juzna.cz: +1 souhlasím s tebou :/ a jsem z toho tez nervozni

mkoubik
Člen | 728
+
0
-

Podle mě je naopak implements dostatečně deklarativní (služba … implementuje rozhraní …), narozdíl od imperativního implement (službo …, implementuj rozhraní …). Kvůli typos by nette stejně muselo podporovat obě možnosti, takže si může vybrat každý.

Tomáš Jablonický
Člen | 115
+
0
-

Context a Enviroment určitě ponechat kvůli zpětné kompabilitě. Context bych defaultně nenastavoval a umožnil bych ho nastavit v neon.

David Grudl
Nette Core | 8227
+
0
-

Ještě jednou bych se vrátil k expandování parametrů v konfiguraci.

CompilerExtensions pracují s neexpandovanými (ale místy i expandovanými) proměnnými, tj. kdekoliv v definicích lze používat řetězce jako třeba %tempDir%/abc a expanze se provádí až při generování DI kontejneru.

Tohle bych rád změnil: expanze by se provedla hned po načtení souboru a dále už by se pracovalo s „ostrými“ daty.

Důvod je především ten, že když do definice předáváme nějakou obecnou proměnnou (například heslo), vůbec nás nenapadne, že bychom ho měli nejprve escapovat funkcí Nette\DI\Helpers::escape, protože pokud heslo bude například a%bc%d, dojde u něj k nechtěné expanzi.

Pravděpodobnost výskytu takového řetězce je malá, takže se na tento problém nenaráží, nicméně je to principiální problém.

Taktéž je potřeba myslet na to, která proměnná je expandovaná a která ne, když s ní chceme nějak pracovat (např. ověření existence adresáře apod.).

Rád bych proto, aby vše bylo od začátku expandované. Pokud bych escapovat chtěl, explicitně bych to uvedl. Tj. třeba na řádku https://github.com/…xtension.php#L114 by se

		$container->addDefinition($this->prefix('cacheJournal'))
			->setClass('Nette\Caching\Storages\FileJournal', array('%tempDir%'));

nahradilo za

		$container->addDefinition($this->prefix('cacheJournal'))
			->setClass('Nette\Caching\Storages\FileJournal', array($container->expand('%tempDir%')));

Pochopitelně v tomto případě musí už při volání metody expand() proměnná %tempDir% existovat. Soudím, že tohle by neměl být v praxi problém.

David Grudl
Nette Core | 8227
+
0
-

Návrh úpravy jsem dal na https://github.com/…te/pull/1015. Prosím o připomínky.

castamir
Člen | 629
+
0
-

Chci se ujistit, že jsem to správně pochopil. Dejme tomu, že chci nastavit cestu do %tempDir%/abc, mám pak použít něco jako

$container->addDefinition($this->prefix('cacheJournal'))
    ->setClass('Nette\Caching\Storages\FileJournal', array($container->expand('%tempDir%') . "/abc"));

Ještě mě napadá – když už dělám v metodě loadConfiguration

	$config = $this->getConfig($this->defaults);

tak tam nejsou parametry jako productionMode apod, ale ostatní explicitně uvedené v configu tam jsou. Co takhle to sjednotit?

$container->addDefinition($this->prefix('cacheJournal'))
    ->setClass('Nette\Caching\Storages\FileJournal', array($config['tempDir'] . "/abc"));
Filip Procházka
Moderator | 4668
+
0
-

castamir napsal(a):

Ještě mě napadá – když už dělám v metodě loadConfiguration

	$config = $this->getConfig($this->defaults);

tak tam nejsou parametry jako productionMode apod, ale ostatní explicitně uvedené v configu tam jsou. Co takhle to sjednotit?

To není dobrý nápad. Všichni počítají s tím, že v tom dostanou pouze a jenom parametry té konkrétní extension.

castamir
Člen | 629
+
0
-

Jenže to ani teď neplatí. Dostaneš tam totiž i globální parametry, které si explicitně uvedeš v sekci parameters, které ti dokonce přepíšou konfliktní parametry v dané extension.

Editoval castamir (2. 4. 2013 15:24)

David Grudl
Nette Core | 8227
+
0
-

Globální parametry tam určitě nejsou, jen to, co je uvedeno v té konkrétní sekci. Globální najdeš v $this->getContainerBuilder()->parameters.

David Grudl
Nette Core | 8227
+
0
-

Boží, muj pull request byl schválen. To otevírá možnost vytvořit schema pro validaci konfiguráků. Už žádné překlepy v config.neon!