Načtení více konfiguračních souborů
- Karel Klíma
- Člen | 31
Při práci s Nette se každý může dostat do situace, kdy mu jeden
konfigurační soubor config.ini
nebude stačit. Ať už chcete
mít pro každý použitý modul/plugin/komponentu vlastní oddělené
nastavení nebo chcete konfigurační soubor generovat automaticky (například
uložení konfigurace DB při instalaci aplikace), následující vychytávka se
vám bude hodit. Třída MultipleConfigurator
rozšiřuje
vestavěný Nette Configurator
a přidává podporu pro
načítání více konfiguračních souborů do jedné „proměnné“.
<?php
class MultipleConfigurator extends Configurator
{
private $_data;
/**
* Overwrites the original Configurator::loadConfig method
* Loads global configuration from file and process it.
* @param string|Nette\Config\Config file name or Config object
* @return Config
*/
public function loadConfig($file)
{
$name = Environment::getName();
if ($file instanceof Config) {
$config = $file;
$file = NULL;
} else {
if ($file === NULL) {
$file = $this->defaultConfigFile;
}
$file = Environment::expand($file);
$config = Config::fromFile($file, $name, 0);
}
// process environment variables
if ($config->variable instanceof Config) {
foreach ($config->variable as $key => $value) {
Environment::setVariable($key, $value);
}
}
$config->expand();
// process services
$runServices = array();
$locator = Environment::getServiceLocator();
if ($config->service instanceof Config) {
foreach ($config->service as $key => $value) {
$key = strtr($key, '-', '\\'); // limited INI chars
if (is_string($value)) {
$locator->addService($key, $value);
} else {
if ($value->factory) {
$locator->addService($key, $value->factory, isset($value->singleton) ? $value->singleton : TRUE, (array) $value->option);
}
if ($value->run) {
$runServices[] = $key;
}
}
}
}
if ($config->php instanceof Config) {
if (PATH_SEPARATOR !== ';' && isset($config->php->include_path)) {
$config->php->include_path = str_replace(';', PATH_SEPARATOR, $config->php->include_path);
}
foreach ($config->php as $key => $value) { // flatten INI dots
if ($value instanceof Config) {
unset($config->php->$key);
foreach ($value as $k => $v) {
$config->php->{"$key.$k"} = $v;
}
}
}
foreach ($config->php as $key => $value) {
$key = strtr($key, '-', '.'); // backcompatibility
if (!is_scalar($value)) {
throw new InvalidStateException("Configuration value for directive '$key' is not scalar.");
}
if (function_exists('ini_set')) {
ini_set($key, $value);
} else {
switch ($key) {
case 'include_path':
set_include_path($value);
break;
case 'iconv.internal_encoding':
iconv_set_encoding('internal_encoding', $value);
break;
case 'mbstring.internal_encoding':
mb_internal_encoding($value);
break;
case 'date.timezone':
date_default_timezone_set($value);
break;
case 'error_reporting':
error_reporting($value);
break;
case 'ignore_user_abort':
ignore_user_abort($value);
break;
case 'max_execution_time':
set_time_limit($value);
break;
default:
if (ini_get($key) != $value) { // intentionally ==
throw new NotSupportedException('Required function ini_set() is disabled.');
}
}
}
}
}
// define constants
if ($config->const instanceof Config) {
foreach ($config->const as $key => $value) {
define($key, $value);
}
}
// set modes
if (isset($config->mode)) {
foreach($config->mode as $mode => $state) {
Environment::setMode($mode, $state);
}
}
// auto-start services
foreach ($runServices as $name) {
$locator->getService($name);
}
// let's make the config object editable/expandable
//$config->freeze();
if ($this->_data instanceof Config) {
$this->_data = $this->merge($this->_data, $config);
} else {
$this->_data = $config;
}
return $this->_data;
}
public function merge($master, $merge)
{
foreach($merge as $key => $item) {
if(array_key_exists($key, $this->_data)) {
if($item instanceof Config && $master->$key instanceof Config) {
$master->$key = $this->merge($master->$key, new Config((array) $item, null));
} else {
$master->$key = $item;
}
} else {
if($item instanceof Config) {
$master->$key = new Config((array) $item, null);
} elseif (!count($master->$key)) {
$master->$key = $item;
}
}
}
return $master;
}
public function getConfig()
{
return $this->_data;
}
public function truncate()
{
$this->_data = null;
}
}
?>
Metoda MultipleConfigurator::loadConfig()
se liší od té
původní pouze v jedné věci – nezavolá funkci
Config::freeze()
, a tak můžeme objekt dále upravovat a
rozšiřovat. Samotné načítání konfigurace do existujícího objektu
obsluhuje metoda MultipleConfigurator::merge()
.
Pro zakomponování do aplikace stačí přidat do bootstrap.php
následující kód:
<?php
// nepoužíváme-li robot loader, musíme třídu nejprve načíst
require LIBS_DIR . '/My/MultipleConfigurator.php';
Environment::setConfigurator(new MultipleConfigurator());
?>
Použití pak vypadá následovně:
<?php
Environment::loadConfig(APP_DIR . '/config.ini');
Environment::loadConfig(APP_DIR . '/modules.ini');
?>
Výsledkem bude konfigurační proměnná, která bude obsahovat direktivy z obou souborů. Pokud dojde ke konfliktu názvů, použije se vždy poslední hodnota konfigurační direktivy.
Co na to říkáte? Uvítám jakékoliv poznámky a nápady.
- Jan Tvrdík
- Nette guru | 2595
Čím se to liší od zápisu
Environment::loadConfig(APP_DIR . '/config.ini');
Environment::loadConfig(APP_DIR . '/modules.ini');
bez použití MultipleConfiguratoru
?
- Karel Klíma
- Člen | 31
Jan Tvrdík napsal(a):
Čím se to liší od zápisu
Environment::loadConfig(APP_DIR . '/config.ini'); Environment::loadConfig(APP_DIR . '/modules.ini');
bez použití
MultipleConfiguratoru
?
Pokud se dvakrát za sebou zavolá Environment::loadConfig()
za
použítí standardního Configuratoru
, pak
Environment::getConfig()
vrátí pouze konfigurační direktivy
z druhého souboru.
- Cifro
- Člen | 245
Karel Klíma napsal(a):
Pokud se dvakrát za sebou zavoláEnvironment::loadConfig()
za použítí standardníhoConfiguratoru
, pakEnvironment::getConfig()
vrátí pouze konfigurační direktivy z druhého souboru.
Joo a presne toto teraz potrebujem :)
Edit: Ale nefunguje dobre, resp. „dedičnosť“ nefunguje dobre. Ak
nastavim v config.ini
php.date.timezone = "Europe/Bratislava"
, a v
modules.ini
nie je nastavené tak to vráti potom „php“ ⇒
null. Čo by správne mala byť predchadzajúca hodnota.
Editoval Cifro (15. 1. 2010 21:41)
- norbe
- Backer | 405
Nešla by podpora nahrávání více konfiguračních souborů přidat přímo do nette? Chtěl jsem využít této třídy, ale pak mi došlo, že se připravím o výhodu nastavit robotloader až v konfigu, případně musím dát natvrdo require.
Přidáním parametru $append do Environment::loadConfig by se vyřešila případná zpětná nekompatibilita
Environment::loadConfig();
Environment::loadConfig("anotherConf.ini", $append /* TRUE/FALSE */);
- fenix
- Člen | 16
Moje reseni:
<?php
$envName = Environment::getName();
$appConfig = Config::fromFile(APP_DIR . '/config.ini', $envName)->toArray();
$coreConfig = Config::fromFile(CORE_DIR . '/config.ini', $envName)->toArray();
$arrayConfig = array_merge_recursive($coreConfig, $appConfig);
$config = new Config($arrayConfig, null, 0);
Environment::loadConfig($config);
?>
- Karel Klíma
- Člen | 31
fenix napsal(a):
Moje reseni:
<?php $envName = Environment::getName(); $appConfig = Config::fromFile(APP_DIR . '/config.ini', $envName)->toArray(); $coreConfig = Config::fromFile(CORE_DIR . '/config.ini', $envName)->toArray(); $arrayConfig = array_merge_recursive($coreConfig, $appConfig); $config = new Config($arrayConfig, null, 0); Environment::loadConfig($config); ?>
Tohohle lze využít, pokud se načítají všechny konfigurační soubory najednou. Já jsem ovšem řešil následující situaci: vyvíjel jsem aplikaci složenou z několika samostatných a nezávislých modulů; nejdříve jsem načetl všeobecný konfigurační soubor, který obsahoval i požadované názvy modulů – ty se pak v inicializační fázi mohli nebo nemuseli přidat další konfigurační soubor. Hlavní požadavek tedy byl, aby bylo možné načítat soubory dynamicky. Samozřejmě by to šlo mergeovat i později, ale už by to nebylo tak pohodlné.
- srigi
- Nette Blogger | 558
Chalani neslo by tie vase volne zasuvatelne moduly (o to sa tu predsa snazite) vyriesit pomocou bootstrapovania modulov? Tak ako to ma ZendFramework – je hlavny bootstrap – bootstrapuje alikaciu a nasledne, v nom (alebo v logike frameworku) sa dohladaju v aplikacii moduly a tie su nasledne bootstrapovane svojimi bootstrap subormi. Tie uz si obstaraju konfig modulu same.
Umoznuje to naozaj volne zasuvanie modulov – stiahnes z netu archiv, ten staci rozbalit do zlozky pre moduly a app si ho automaticky natiahne. Ked sa modul natiahne, sam, pomocou svojho bootsreappera sa runtime nakonfiguruje
Editoval srigi (20. 2. 2010 20:02)
- Karel Klíma
- Člen | 31
srigi napsal(a):
Chalani neslo by tie vase volne zasuvatelne moduly (o to sa tu predsa snazite) vyriesit pomocou bootstrapovania modulov? Tak ako to ma ZendFramework – je hlavny bootstrap – bootstrapuje alikaciu a nasledne, v nom (alebo v logike frameworku) sa dohladaju v aplikacii moduly a tie su nasledne bootstrapovane svojimi bootstrap subormi. Tie uz si obstaraju konfig modulu same.
Umoznuje to naozaj volne zasuvanie modulov – stiahnes z netu archiv, ten staci rozbalit do zlozky pre moduly a app si ho automaticky natiahne. Ked sa modul natiahne, sam, pomocou svojho bootsreappera sa runtime nakonfiguruje
Přesně takhle funguje i ta mnou vyvíjená aplikace. Problém je ten, že Config je v podstatě singleton – v Nette je to dělané tak, že se to jednou načte ze souboru a pak se z toho hodnoty akorát získávají. Tento krok tedy lze udělat jen jednou, zatím co mám-li víc modulů, potřebuji konfigurační soubory načítat víckrát. Samozřejmě by se to dalo řešit tak, že by měl každý modul svůj unikátní Config, to má jistě své výhody (obzvlášť z bezpečnostního hlediska), ale někdy se to prostě nemusí hodit.
- Karel Klíma
- Člen | 31
honzakuchar napsal(a):
Ahoj, nechceš to přidat do extras?
Má to cenu? Přeci jenom, jedná se pouze o zakomentování jednoho řádku…