Načtení více konfiguračních souborů

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Karel Klíma
Člen | 31
+
0
-

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

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

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

Karel Klíma napsal(a):
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.

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

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 */);
PetrP
Člen | 587
+
0
-

norbe napsal(a):

Nešla by podpora nahrávání více konfiguračních souborů přidat přímo do nette?

Už jsem o to prosil kdysi dávno

Cifro
Člen | 245
+
0
-

I ja som za to aby sa dalo načítavať viac configov.

Honza Kuchař
Člen | 1662
+
0
-

Ahoj, nechceš to přidat do extras?

fenix
Člen | 16
+
0
-

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

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

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

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

honzakuchar napsal(a):

Ahoj, nechceš to přidat do extras?

Má to cenu? Přeci jenom, jedná se pouze o zakomentování jednoho řádku…