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

před 10 lety

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.

před 10 lety

Jan Tvrdík
Nette guru | 2559
+
0
-

Čím se to liší od zápisu

Environment::loadConfig(APP_DIR . '/config.ini');
Environment::loadConfig(APP_DIR . '/modules.ini');

bez použití MultipleConfiguratoru?

před 10 lety

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.

před 10 lety

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)

před 10 lety

norbe
Backer | 404
+
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 */);

před 9 lety

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

před 9 lety

Cifro
Člen | 245
+
0
-

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

před 9 lety

Honza Kuchař
Backer | 1650
+
0
-

Ahoj, nechceš to přidat do extras?

před 9 lety

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);
?>

před 9 lety

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é.

před 9 lety

srigi
Člen | 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)

před 9 lety

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.

před 9 lety

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…