Dependency Injection v Nette 2.1
- David Grudl
- Nette Core | 8227
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
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
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
Protože jsem nahodil hodně témat, abychom udrželi diskusi, nepřidávejte prosím žádné další.
- Pavel Kouřil
- Člen | 128
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
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
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
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.
- Vojtěch Dobeš
- Gold Partner | 1316
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
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
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
vojtech.dobes napsal(a):
Nešlo by sjednotit
class
acreate
do výslednéhocreate
?
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.
- Pavel Kouřil
- Člen | 128
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
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)
- mishak
- Člen | 94
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)
- Pavel Kouřil
- Člen | 128
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.
- Proč bys zbytečně „zaváděl“ jiný termín, než používá PHPčko (implements)? Bude to působit zmatečně.
- 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
Nepojmenované služby pomocí odrážky a klíč create
jsou
už v dev verzi, můžete vyzkoušet.
- Filip Procházka
- Moderator | 4668
To by mohly řešit ty schémata…
$config->scalar('implement')
->commonTypos('implements', 'impl', ...);
- Filip Procházka
- Moderator | 4668
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
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
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
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.
- Honza Marek
- Člen | 1664
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
Mně se též líbí třída v názvu. Odpadá tím otravná otázka „jak službu pojmenovat?“
- juzna.cz
- Člen | 248
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?
- Tomáš Jablonický
- Člen | 115
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
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
Návrh úpravy jsem dal na https://github.com/…te/pull/1015. Prosím o připomínky.
- castamir
- Člen | 629
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
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.
- David Grudl
- Nette Core | 8227
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
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!