Dependency Injection – Symfony inspired & Nella implementation – porting to Nette
- Patrik Votoček
- Člen | 2221
Již poměrně dlouhou dobu používám(e) v Nella Frameworku
pokročilejší implementaci Dependency
Injection (dále DI). Nicméně toto DI bylo naroubované na stávajíci
Nette a to bez jediného zásahu přímo do Nette. V tomto důsledku mělo k dokonalosti hodně daleko,
kvůli nejrůznějším omezením (hlavně nutnosti implementovat
Nette\DI\IContext
a dědit Nette\DI\Configurator
).
Poměrně dlouhou dobu mě v hlavě ležel nápad s pár vylepšeními a hlavně myšlenka portace do Nette. A tak jsem se do toho konečně pustil. Už je to rozdělané. Budu se snažit to průběžně házet na github (testovatelné řešení by mohlo být v průběhu dneška – zatím je tam pracovní verze – budou i testy (až se mě je podaří odbugovat)!).
Implementace je inspirovaná Symfony 2. Několik měsíců v praxi testována a vylaďována v „ostrém“ prostředí (pominuli několik málo změn).
Seznam nejdůležitějších změn a novinek
- sloučení config & variables (nyní už pouze jako variables)
- přesun toho všeho do
Nette\DI\Context
- aliasy pro služby (jmenné)
- lepší konfigurovatelnost služeb díky „univerzální“ továrničce na služby
Nette\DI\Configurator
předělán na kompilátory + detekce prostředí přesunuta doNette\Environment
Jak to celé funguje
Environment::loadConfig();
spustí
Nette\DI\ContextBuilder
který má za úkol vytvořit a naplnit
Nette\DI\Context
. Provede to za pomoci dvou kompilátorů a to
Nette\DI\DefaultServicesCompilator
(inicializace základních
služeb) a Nette\DI\ConfigCompilator
(zpracování
config.neon
/ config.ini
). Pokud pak chcete
z Environmentu vytáhnout službu / proměnnou prostředí tak ve skutečnosti
voláte context.
Změny samotného Nette\DI\Context
Pro celkově lepší práci se službami vznikl contextův pomocník
Nette\DI\ServiceFactory
. Ten má nastarosti právě vytváření
služeb a držení informací o tom jak to má dělat.
Praxe
Simple class
PHP:
Neon:
Simple factory
PHP:
Neon:
Class s parametry konstruktoru
PHP:
NEON:
Továrnička s parametry
PHP:
NEON:
Vstřikování pomocí metod
PHP:
NEON:
Metod můžeme volat samozřejmě kolik chceme.
Konfigurátor
PHP:
NEON:
Specialitky argumentů:
Argumenty konstruktorů, továrniček a metod nemusejí být jenom skalární data ale i něco lepšího.
Environment variable
Jednoduže řečeno cokoli mezi dvěma znaky % bude nahrazeno za data z proměnné prostředí se stejným názvem.
Service
Argument začínající zavináčem bude nahrazen za instanci služby se stejnám názvem.
Editoval Patrik Votoček (25. 4. 2011 1:43)
- Filip Procházka
- Moderator | 4668
Subjektivní názory z používání upravené Patrikovy implementace:
Lepší argumenty
Jsem pro doplnit ještě možnost, zadat klíč assoc pole, jako je vidět v konfiguraci: https://github.com/…actories.php#L11
třeba
Výhody jsou zcela zřejmé, není potřeba dělat factory, jenom proto, aby se předaly jednoduché argumenty třídě.
Mám to implementováno, sice to není možné nejefektivnější, ale funguje to podle očekávání a spolehlivě.
Taky @internal
metoda expandParameter
, podle tvého
původního návrhu se mi moc nelíbila. Tak jsem ji rozdělil a upravil tak,
jak podle mě dává smysl.
Config a proměnné prostředí
Vyloženě se nabízí implementace ArrayAccess
pro
ServiceContainer pro přístup k těmto proměnným, jako zkratka
Aliasy
Myslím si, že implementace aliasů, vytvářením odkazů v rámci
Context, jenom pod jiným názvem je zbytečné. Naopak bych aliasy
implementoval jenom jako zkratku, která bude přístupná přes
magický __get()
Má to naprosto zřejmou výhodu, že pokud se doplní phpDoc (alespoň defaultních) služeb. Práce s Contextem je pak vyloženě radost.
Prosím,
rád se nechám přesvědčit o užitečnosti jedné instance služby pod dvěma názvy
A taky bych chtěl vědět co se ti nelíbí, na ArrayAccesu
pro proměnné (afaik jsi to měl jako zkratku pro služby a jejich aliasy)
PS: Skoro se bojím i napsat, že Symfony to má tak, jak jsem si to upravil já (alespoň ty proměnné určitě), protože vždycky když to někdo napíše, tak David začne stávkovat :)
- Filip Procházka
- Moderator | 4668
Když teď na to koukám, tak je vidět, že mají význam ty aliasy, kvůli
zpětné kompatibilitě. To ale nebrání mít ServiceAlias
a
k tomu ještě zkratku přes magický __get()
.
- Ondřej Mirtes
- Člen | 1536
HosipLan: Nesouhlasím s ArrayAccess (je to WTF).
Líbí se mi, co Honza Marek udělal u nás v Mediu – sestavuje pomocí DI kontejneru i presentery. Do Presenterů tedy není vstřikován kontejner, ale už konkrétní servisy, které daný presenter potřebuje. Chce to pouze vlastní jednoduchou implementaci PresenterFactory.
Samotný kontejner je pak přístupný pouze na pár speciálních místech – v bootstrapu, v testech a v cron skriptech. Zbytek aplikace o něm neví.
- Filip Procházka
- Moderator | 4668
Co se týče těch zkratek, je mi to v podstatě fuk. Stejně si nad tím budu dělat vrstvu, abych tam tohle chování dostal. Ale ty aliasy a klíče v konfiguraci mi tam vyloženě chybí.
- Ondřej Mirtes
- Člen | 1536
Vždyť si svoje služby můžeš pojmenovávat jakkoli, nemusíš je mít pod dvěma různými názvy.
- Patrik Votoček
- Člen | 2221
Ad ArrayAccess
není tam účelně (je to WTF). Nějak nechápu
jak myslíš aliasy … v konfiguraci mi tam vyloženě chybí
.
A ad %foo[bar]%
spíš bych použil tečkovou notaci
%foo.bar%
Editoval Patrik Votoček (25. 4. 2011 19:23)
- kravčo
- Člen | 721
Slovo „compilator“ zdá sa existuje, ale po anglicky/americky sa tomu vo vačšine nadáva „compiler“ :)
Namiesto zavináča by bolo oveľa prirodzenejšie použiť zrozumiteľné
{ service: Nette\Caching\ICacheStorage }
, na druhej strane je to aj
možný zápis pre
(object) array('service' => '\Nette\Caching\ICacheStorage')
,
neviem či sa s týmto neráta…
… alebo žeby sa nám zavináčová mágia vrátila :)
Pri pohľade na addParameter()
a addFunctionCall()
rozmýšľam, … načo? Kložúry to zvládnu za nás, nie?
Nevidím prínos toho istého kódu v konfiguráku, ktorý mi pripadá o dosť ukecanejší a menej čitateľný a zrozumiteľný (je to môj subjektívny pocit)…
common:
services:
\FW\CustomMemcache:
class: \FW\CustomMemcache
setMembers: { foo: bar }
callMethods: { expire: [ volatile ] }
cache:
class: \Nette\Cache
args: [ { service: \FW\CustomMemcache } ]
A furt a stále je to len factory, len zapísaná iným spôsobom. Tak prečo jej nenechať flexibilitu PHP? Naozaj ma zaujímajú vaše dôvody.
- Patrik Votoček
- Člen | 2221
kravčo napsal(a):
A furt a stále je to len factory, len zapísaná iným spôsobom. Tak prečo jej nenechať flexibilitu PHP? Naozaj ma zaujímajú vaše dôvody.
Jde o to že nemusíš vůbec žádnou továrničku tvořit. (nemusíš zasahovat do kódu ) + dává ti to flexibilitu kdy chceš třeba v nějaké instanci své aplikace něco malinko jinak nastavit.
- Filip Procházka
- Moderator | 4668
Hmm a jak tu tečku odlišíš od teček v konfiguraci pro php? Přece nechceš zavádět, že v jedné části configu by tečka byla součást názvu a v druhé části configu bude tečka znamenat cestu.
- Filip Procházka
- Moderator | 4668
kravčo napsal(a):
Pri pohľade na
addParameter()
aaddFunctionCall()
rozmýšľam, … načo? Kložúry to zvládnu za nás, nie?
…
A furt a stále je to len factory, len zapísaná iným spôsobom. Tak prečo jej nenechať flexibilitu PHP? Naozaj ma zaujímajú vaše dôvody.
Protože addService
nemůžeš použít v configu. To chceš
mít v bootstrap (v compileru) 100× addService
?
// edit: po ránu pomalejší, jsem to mohl napsat do jednoho…
Editoval HosipLan (26. 4. 2011 7:51)
- Honza Marek
- Člen | 1664
To run je taky docela šílená věc, kterou by bylo záhodno zakázat.
- Vytvoření služby by nemělo ovlivňovat globální prostředí. Továrnička by mi jen měla poskytnout objekt, se kterým nějak naložím.
- Další, co mi na run vadí je šílená jednoúčelovost. Přitom by se to dalo krásně zobecnit na nějaké tagování služeb, že bych si na jednom místě vytáhl služby otagované jako autoload a v cyklu je naházel do spl_autoload_register. Na jiném místě bych si zas vytáhl služby otagované jako helpery pro šablony třeba a registroval je v šabloně. Využití tagů je širší než mít k dispozici jen run.
- kravčo
- Člen | 721
run bol zjavne blbý príklad… upravil som pôvodný príspevok
Editoval kravčo (26. 4. 2011 13:19)