Refactoring Environment & Configuration
- David Grudl
- Nette Core | 8218
DI kontejner se přejmenoval a doznal určitých vylepšení a s ním souvisí i třídy Configurator a Environment, které bych rád také dostal do konečné podoby. Kvůli zmatkům, které tematiku DI nerozlučně doprovází, přidám raději ještě trošku teorie. Úvodem terminologie a nyní podrobněji:
Třída Nette\Configurator vytváří systémový kontejner obsahující továrny na všechny klíčové služby pro běh frameworku samotného + parametry. Podobu systémového kontejneru lze ovlivnit stejně jako jakýkoliv jiný dynamický konterjner, přičemž Nette\Configurator nabízí podporu načítání konfigurace ze souboru. A nejnověji i kešování do PHP.
Statická třída Nette\Environment tento systémový
kontejner udržuje ve statické proměnné (via
Nette\Environment::getContext()
) a disponuje metodami pro
syntakticky snadný přístup k němu. Představuje tedy Service Locator.
Vzhledem k tomu, že samotný framework tuto třídu nevyužívá, lze se bez
ní zcela obejít a v bootstrapu přímo pracovat s kontejnerem (ať už
vytvořeným třídou Configurator nebo jinak). Pro méně zdatné uživatele
však představuje mimořádné důležitý záchytný bod.
Příklad Environment-free bootstrap.php
Debugger::enable(false);
$configurator = new Nette\Configurator;
$configurator->loadConfig(__DIR__ . '/config.neon');
$application = $configurator->container->application;
$application->router[] = new SimpleRouter('Homepage:default');
$application->run();
A nyní k věci!
Myslím, že Configurator/Environment je právě tím místem, kde se budou nejvíce lišit vaše postupy i požadavky. Nerad bych redesignem těchto tříd připravil někoho o důležitou funkčnost nebo naopak mu očekávanou vlastnost odepřel. Otevírám proto tuto diskusi. Zkusme dát dohromady Nette-way, jak načítat konfigurace, modifikovat kontejner atd.
(a fakt bych ocenil velkorysý přístup, výkřiky jako „zrušit environment“ od programátora, který včera objevil DI, nejsou ku prospěchu věci)
- Filip Procházka
- Moderator | 4668
Ani mě snad nenapadá, co by se ještě dalo vylepšit (krom jemného refaktoringu configuratoru, protože některý nový konstrukce jsou dost psycho, ale zase věřím, že jsou opravdu efektivní, když se dostaly do Nette :)
Všechno na co jsme naříkali teď máme (nepíšu podle priority, ale jak mě to napadá)
- kompletní ignorance k Environment (je vůbec někde ve FW? nebo už je úplně pryč z vnitřností?)
- (vlastní)
DI\Container
(byť je potřeba ho dědit, za což bude Patrik asi ještě protestovat :) - (vlastní)
DI\ServiceBuilder
- načítání více configů
- konfigurovatelnost služeb (včetně volání metod a expandování %proměnných% a @služeb) i v configu
- „exporter“ configu do PHP do cache
a ještě pár dalších, co mě teď nenapadnou :)
(OT ?) PS.: ještě takový malý feature request, co si
myslíte o ArrayAccess
na Containeru pro přístup k
$params
? Zvážíš to, nebo je to hloupost a mám si jít
extendnout DI\Container
? :)
PS2.: je %database[user]%
(tečky vadí kvůli php.ini) za
hranicí přijatelnosti magie, nebo to spadá pod YAGNI?
- arron
- Člen | 464
Chtel bych se zatim vyjadrit k tem nazvum prostredi, jak psal Patrik na GitHubu. Nazvy prostredi, jsou VELMI (!) dulezite, protoze ve firemni praxi nemas jenom development a production, ale i dalsi prostredi (obvykle je to ‚svata trojce‘ development/stage/production). Zaroven je potreba mit na kazdem prostredi jinou konfiguraci a treba na stage se cas od casu meni nasteveni development/production rezimu. Kdyby tyto moznosti z Nette zmizely, tak by se pro me stalo relativne nepouzitelne ve firemnim prostredi…
- Honza Marek
- Člen | 1664
HosipLan napsal(a):
PS.: ještě takový malý feature request, co si myslíte o
ArrayAccess
na Containeru pro přístup k$params
? Zvážíš to, nebo je to hloupost a mám si jít extendnoutDI\Container
? :)
To pak bude vypadat jako NotORM. Na služby „object access“, na parametry array access. A nikdo neví, proč se co na co používá. A ještě jsme nevyužili __call, jistě k němu taky najdeš nějaké smysluplné využití ;)
- Honza Marek
- Člen | 1664
Přijde mi, že třída configurator má teď velmi široké pole působnosti. Určitě by měla být rozdělena na něco, co přidává ty defaultní služby a parametry do kontejneru (ContainerExtension / ContainerCompiler / ContainerLoader / ?), načítátko neon souboru (DI\NeonLoader?) a něco, co umí vygenerovat z nějakého ContainerBuilderu zdroják Containeru. Potom by byly jednotlivé DI součástky krásně vyměnitelné a z DI contianeru by šlo dostat cokoliv. Zvláště pokud by nějaký ten NeonLoader a PHPDumper byly implementovány tak, aby počítaly s rozšiřitelností.
Jinak prostředí bych ani nepotřeboval, vystačím si s načtením nějakého dalšího přebíjejícího config souboru.
// použití v bootstrapu (cca)
if (containerJeVCache()) {
$container = getContainerZCache();
} else {
$containerBuilder = new DI\ContainerBuilder;
$netteServices = new DI\Configurator();
$netteServices->load($containerBuilder);
$neonLoader = new DI\NeonLoader($containerBuilder);
$neonLoader->load(array(__DIR__ . '/config.neon', __DIR__ . '/config.local.neon'));
$dumper = DI\PHPDumper($containerBuilder);
$dumper->dump("někam");
$container = getContainerZCache();
}
Tohle vypadá složitě, ale určitě by šla vytvořit výchozí továrnička, která by těch 10 řádků zaobalila, hlavně to cachování, které psát do bootstrapu by bylo docela otravné.
- David Grudl
- Nette Core | 8218
HosipLan napsal(a):
(OT ?) PS.: ještě takový malý feature request, co si myslíte o
ArrayAccess
na Containeru pro přístup k$params
?
Magické gettery a ArrayAccess představují velký technický problém, pokud se pracuje s vícerozměrnými poli. Z uživatelského hlediska nevidím žádnou výhodu oproti přístupu k public proměnné.
PS2.: je
%database[user]%
(tečky vadí kvůli php.ini) za hranicí přijatelnosti magie, nebo to spadá pod YAGNI?
Hranaté závorky jsou divné, možná spíš přes tu tečku (v INI vadí?).
arron napsal(a):
Chtel bych se zatim vyjadrit k tem nazvum prostredi, jak psal Patrik na GitHubu. Nazvy prostredi, jsou VELMI (!) dulezite,
Názvy prostředí a módy byly vždy jen zdrojem nedorozumění, bylo je potřeba nějak předělat. Zatím jsem zkusil je smazat ;-) K čemu konkrétně je používáš? Pokud jde jen o určení sekce v načítaném konfiguráku, jeví se mi lepší explicitní:
Environment::loadConfig(__DIR__ . '/config.neon', 'stage');
- Filip Procházka
- Moderator | 4668
Mno já myslel, že tečka má speciální význam v ini (i v neonu?) Ale jestli to nevadí, tak lepší nějak než vůbec :)
- Filip Procházka
- Moderator | 4668
Teď mě napadl jeden use-case, který „není ošetřený“. Má někdo lepší nápad jak předat službě celý container? (když těch závislostí má opravdu hodně, já vím bad practices, smelly code, bla bla bla…)
public static function createServiceFoo(IContainer $container)
{
return new FooService($container); // god class
}
nebo trošku lépe
public static function createServiceFoo(IContainer $container)
{
$context = new Container;
$context->addService('bar', $container->bar);
// ...
return new FooService($context); // god class
}
V kódu je to OK, ale když už jde (prakticky všechno co je běžně potřeba) nastavit v configu, tak by možná nebylo na škodu, vymyslet řídící znak pro předání containeru službě?
Nebo se považuje za best-practices druhý snippet kódu?
PS: Symfony má na tohle interface IContainerAware
(služba co
ví o svém containeru, skoro jako námět na horor :D). Při vytváření
služby, která implementuje tohle interface (afaik jenom metoda
setContainer
) se jí během vytváření předá
Container
automaticky (protože interface).
- Lopo
- Člen | 277
David Grudl napsal(a):
Názvy prostředí a módy byly vždy jen zdrojem nedorozumění, bylo je potřeba nějak předělat. Zatím jsem zkusil je smazat ;-) K čemu konkrétně je používáš? Pokud jde jen o určení sekce v načítaném konfiguráku, jeví se mi lepší explicitní:Environment::loadConfig(__DIR__ . '/config.neon', 'stage');
ja osobne traz pracujem na projekte, kde cez ENV premennu servera sa urcuju napr hodnoty pripojenia k DB – kazdy z ludi co na tom makaju maju vlastnu hodnotu a tak nepotrebuje si nikdo zakazdym dany konfig prepisovat ani pri fungovani cez git nic v zdrojakoch menit
takze v konfigu je potom napr.:
common:
development < common:
production < common:
clovek1 < development:
clovek2 < development:
clovek3 < production:
a pri git pull/push nemusim myslet na to ze treba este v konfigu/bootstrape nieco prepisovat lebo ja tam mam nejake svoje vlastne special hodnoty
- Filip Procházka
- Moderator | 4668
My máme v aplikaci cca 5 těch sekcí na různé prostředí aplikace, podle toho v jakém stádiu vývoje/testování/produkce se zrovna nachází. Takže fakt to není zbytečné.
- Ondřej Mirtes
- Člen | 1536
Takže jak aktuálně v Nette funguje detekce prostředí a načítání jednotlivých sekcí configu? Jak teď zařídím, abych v configu měl sekce pro development/production a autodetekcí se rozhodovalo, která se použije?
Lepší možností mi každopádně přijde mít config.ini + config.local.ini, který není verzován a do toho nadřazeného se merguje. Jednotliví vývojáři pak nevidí hesla ostatních a hlavně nevidí hesla na ostrém serveru.
- David Grudl
- Nette Core | 8218
HosipLan napsal(a):
Teď mě napadl jeden use-case, který „není ošetřený“. Má někdo lepší nápad jak předat službě celý container? (když těch závislostí má opravdu hodně, já vím bad practices, smelly code, bla bla bla…)
Použij clone.
ALE PROSÍM ŽÁDNÁ OFF TOPIC TÉMATA, DI má tendenci se zvrhávat.
- David Grudl
- Nette Core | 8218
Ondřej Mirtes napsal(a):
Takže jak aktuálně v Nette funguje detekce prostředí a načítání jednotlivých sekcí configu? Jak teď zařídím, abych v configu měl sekce pro development/production a autodetekcí se rozhodovalo, která se použije?
// implicitne
Environment::loadConfig();
// explicitne
Environment::loadConfig(__DIR__ . '/config.neon', Environment::isProduction() ? 'production' : 'development');
Lepší možností mi každopádně přijde mít config.ini + config.local.ini, který není verzován a do toho nadřazeného se merguje. Jednotliví vývojáři pak nevidí hesla ostatních a hlavně nevidí hesla na ostrém serveru.
Environment::loadConfig(__DIR__ . '/config.ini');
Environment::loadConfig(__DIR__ . '/config.local.ini');
- xificurk
- Člen | 121
David Grudl napsal(a):
Environment::loadConfig(__DIR__ . '/config.ini'); Environment::loadConfig(__DIR__ . '/config.local.ini');
Současná implementace má ale pro tento případ několik nešťastných důsledků:
- Kešované php kódy jednotlivých konfiguračních souborů obsahují celkem zbytečně některé společné části víckrát.
- Není možné overridnout služby definovené v config.ini službami config.local.ini, protože se Configurator pokusí přidat znovu již existující službu.
- Služby definované v config.ini, které mají nastaven autorun jsou vytvořeny a spuštěny dříve než je načteno config.local.ini, takže jim člověk ani nemůže podstrčit jiné parametry.
(Disclaimer: přiznám se, že jsem to naživo netestoval, takže jsem možná něco přehlédl a úplně tu kecám z cesty.)
Poslední změny se mi moc líbí, až se podaří dotáhnout i tohle, tak budu skákat radostí :-)
--
Mě samotného by docela zajímalo, jak ostatní doteď používali jednotlivé názvy prostředí (a jejich konfigurační sekce). Nejlepší řešení, ke kterému jsem zatím dospěl je pořád takové krkolomné – v každém prostředí soubor obsahující jeho název, tento soubor se neverzuje a nepřenáší (narozdíl od zbytku) z development → stage → production. A když tak nad tím znovu přemýšlím, tak při vhodné implementaci mergování konfiguráků, bych se bez konfiguračních sekcí vlastně úplně obešel.
Editoval xificurk (17. 5. 2011 17:06)
- Patrik Votoček
- Člen | 2221
Honza Marek napsal(a):
Přijde mi, že třída configurator má teď velmi široké pole působnosti. Určitě by měla být rozdělena na něco, co přidává ty defaultní služby a parametry do kontejneru (ContainerExtension / ContainerCompiler / ContainerLoader / ?), načítátko neon souboru (DI\NeonLoader?) a něco, co umí vygenerovat z nějakého ContainerBuilderu zdroják Containeru. Potom by byly jednotlivé DI součástky krásně vyměnitelné a z DI contianeru by šlo dostat cokoliv.
Naprostý souhlas! Nejvíc mě stále a pořád (už jsem to zmiňoval
hodněkrát) vadí nutnost dědit Configurator
hlavně z důvodu
toho že obsahuje továrničky. Mám třeba v plánu vyhodit
RobotLoader
protože používám PSR-0 loader. Můžu to
udělat tak že vyhodím run
z configu (ale služba bude pořád
existovat). Můžu to udělat i takto
$configurator->removeService('robotLoader')
ale pořád se mě
to nezdá čisté protože existuje továrnička. Mnohem raději bych viděl
něco jako DefaultServicesConfigurator a ConfigServicesConfigurator či další
(jak už jsem také psal).
David Grudl napsal(a):
Názvy prostředí a módy byly vždy jen zdrojem nedorozumění, bylo je potřeba nějak předělat. Zatím jsem zkusil je smazat ;-) K čemu konkrétně je používáš? Pokud jde jen o určení sekce v načítaném konfiguráku, jeví se mi lepší explicitní:
Environment::loadConfig(__DIR__ . '/config.neon', 'stage');
Tohle řešení je pro mě dostačující. Ve firmě každý máme své prostředí a nastavovat ho potřebujeme.
Ondřej Mirtes napsal(a):
Lepší možností mi každopádně přijde mít config.ini + config.local.ini, který není verzován a do toho nadřazeného se merguje. Jednotliví vývojáři pak nevidí hesla ostatních a hlavně nevidí hesla na ostrém serveru.
Tohle je zajímavé řešení a určitě o něm popřemýšlím. Nicméně napadá mě jeden use case kde by mohl být problém. A tím je přepnutí aplikace do produkčního prostředí na development serveru. Dělá se to těsně před nahozením na ostrý server aby se otestovalo jak to pošlape.
- to co říká xificurk
<OT>BTW: davide kdy jsi sakra spal? :-D</OT>
Edit: a ještě jsem si vzpoměl že by se mě hodilo pár údálostí jako
třeba onBeforeLoadConfig
a onAfterLoadConfig
.
- xificurk
- Člen | 121
Patrik Votoček napsal(a):
Tohle je zajímavé řešení a určitě o něm popřemýšlím. Nicméně napadá mě jeden use case kde by mohl být problém. A tím je přepnutí aplikace do produkčního prostředí na development serveru. Dělá se to těsně před nahozením na ostrý server aby se otestovalo jak to pošlape.
Jestli jsem správně pochopil, tak tohle by mělo stále jít prostým
přidáním productionMode: TRUE
do variables v příslušné
sekci konfigurace.
- Ondřej Mirtes
- Člen | 1536
Patrik Votoček napsal(a):
Tohle je zajímavé řešení a určitě o něm popřemýšlím. Nicméně napadá mě jeden use case kde by mohl být problém. A tím je přepnutí aplikace do produkčního prostředí na development serveru. Dělá se to těsně před nahozením na ostrý server aby se otestovalo jak to pošlape.
Jak říká xificurk – my si v configu konfigurujeme celou Laděnku, abychom mohli ovlivnit, co přesně dělá.
parameters:
debug_strict_mode: false
debug_production_mode: true
debug_email: null
debug_email_snooze: 172800
debug_log_directory: null
application_error_presenter: "Front:Error"
application_catch_exceptions: true
Pozor, je nejdříve potřeba zavolat Debug::enable()
a až pak
donastavovat tyto parametry.
- David Grudl
- Nette Core | 8218
Ondřej Mirtes napsal(a):
Pozor, je nejdříve potřeba zavolat
Debug::enable()
a až pak donastavovat tyto parametry.
Debugger::enable() by měla být úplně první věc, kterou člověk po načtení frameworku udělá, proto taky držím Debugger jako statickou třídu a nechci z ní dělat service.
Všechny parametry lze poté dodatečně měnit, ale 100% to bude jen tehdy, pokud se nastartuje v režimu production (pro všechny případy), což obnáší mít i vhodně nastavený logDirectory.
- Ondřej Mirtes
- Člen | 1536
No ona to principiálně statická věc je, když pracuje s PHP funkcemi
jako set_exception_handler
apod.
Zkusím to u nás přehodnotit, jak se to chová, pokud Laděnku hned zapnu. Zatím jsem se tomu vyhýbal. Budu ji muset zapnout v produkčním módu, pro případ, že by se ta chyba stala na ostrém serveru. Ale bílá stránka na vývojářském stroji bude WTF. Chyba se nám stává často, protože Laděnku konfigurujeme až na základě parametrů kontejneru, které načítáme z YAMLu a ten je dost náchylný na překlepy.
- Nox
- Člen | 378
Chtěl bych se zeptat – má stále Nette teoreticky fungovat i jako dřív, nebo je komplet BC zásek? Zkusil jsem update a v podstatě přestala jet celá aplikace (po x hodinách oprav downgrade)…takže asi BC break…
Pak by bylo dobrý dát pro ne až tak zkušené programátory aspoň nějaké tipy jak upgradovat, je asi nutné přepsat výskyty Environment…set/get variable atd.
Hodně mátl vyskyt řady různých kontextů – environmentův, configurátora, aplikace, presenteru… Ve vláknu o DI je popsané v podstatě jak vytvářet containery, ale tuto oblast jsem z toho nějak nepochopil (kontexty a jakým způsobem je dobré je uchovávat/předávat).
Na některých místech (factory servicu) neměl ani žádný (aspoň jsem
nenašel) kontejner načteny komplet parametry z configu a tam jsem skončil
(ještě tedy změna options array ⇒ container, funkčnost nějakých
doplňků (NetteTranslator, UserBar…), neviditelnost konstant z indexu
v configu atd.)
(jinak tohle není stěžování si, jsem rád že se Nette hýbe kupředu…
jen aby člověk věděl jak udržet krok)
- Patrik Votoček
- Člen | 2221
A aktualizoval jsi z čeho na co?
Aktuální DI je v Nette hodně čerstvá věc a spousta lidí si ji teprve osahává a zkoumá jak nejlépe na to takže je těžké rovnou něco sepisovat.
Pak taky předpokládám že základní vysvětlení bude součástí nové české dokumentace která bude spuštěna 1.6.
- Teyras
- Člen | 81
Ty různý contexty mě taky dost matou, možná trochu naivně bych čekal, že když něco nadefinuju v configu, bude to v contextu dostupný… Mluvím hlavně o službách a parametrech, pokud je možnost nahradit třídu frameworku (Router, User, Application…) svojí implementací, chci, aby to tak bylo v celé aplikaci. Zkrátka, od configu čekám, že bude dostupný všude a bez výjimky.
- Filip Procházka
- Moderator | 4668
Jediné co má vlastní context je Application a ten bys „neměl“ používat, protože v presenteru máš „hlavní“ context, ve kterém je všechno.
- Pavel Kouřil
- Člen | 128
Nevím, jestli je to uplně ideální téma, kde položit tento dotaz …
každopádně, už mne přestalo bavit učení na maturitu a chtěl jsem si
zaprokrastinovat, tak jsem updatl u svého jednoho projektu Nette. Témata
o DI atd jsem sledoval jedním okem, každopádně – nyní mi hlásí nette
„InvalidStateException“, protože nezná environment variable „baseUri“
[dostával sem se k ní přes
Environment::getVariable("baseUri")
].
Jak se k této proměnné nyní dostat/čím byla nahrazena? Zkoušel jsem hledat a nenašel jsem.