Refactoring Environment & Configuration

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

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

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

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

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 extendnout DI\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
+
0
-

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

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

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

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

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

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

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.

Yrwein
Člen | 45
+
0
-

(ContainerAwareInterface je jen pro controllery (resp. minimálně byl), interface injection už Symfony DI neobsahuje, neb činil problémy — jen pro upřesnění. .])

Editoval Yrwein (17. 5. 2011 15:42)

David Grudl
Nette Core | 8218
+
0
-

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

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

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ů:

  1. Kešované php kódy jednotlivých konfiguračních souborů obsahují celkem zbytečně některé společné části víckrát.
  2. 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.
  3. 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
+
0
-

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

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

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

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

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

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

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.

Nox
Člen | 378
+
0
-

Omlouvám se… aktualizace Nette alfa z 21.4. na včerejší 19.5. (ten s „wtf?“ :) commitem)

Teyras
Člen | 81
+
0
-

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

To už ale není moc aktuální požadavek :)

Teyras
Člen | 81
+
0
-

Jako že žádám něco, co už je, a nebo něco, co není ideologicky přípustné? :)

Filip Procházka
Moderator | 4668
+
0
-

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

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.

bazo
Člen | 620
+
0
-

toto by malo fungovat v presenteri

<?php
$this->getHttpRequest()->getUrl()->baseUrl
?>