Различные конфиги для режима production и режима отладки. Два в одном

    Привет, уважаемые хабралюди.
    Из собственного опыта вы, наверное, уже знаете, как важно свести к минимуму те изменения, которые привносит в код вашего приложения режим отладки. Если же вы сопровождаете довольно крупный и уже запущенный проект, то тем более понимаете важность быстрой отладки, при которой за собой не придеться подчищать все сделанные для удобства разработки изменения.
    Исследовав возможности Yii, мой друг не нашел функционала, реализующего эту возможность, и задался целью это исправить. В данном топике мы узнаем о том, как быстро и неопасно для вашего приложения изменять конфиг (его рабочую версию) под нужды разработчика. Подробности — под катом.
    Только что стало известно, что подобное есть в кукбуке Yii.

    Проблема


    Вы не хотите иметь несколько конфигов. Это неудобно, потому что при дописывании проекта вам каждый раз придется менять в новом конфиге все параметры, измененные для большего удобства на этапе разработки. Гораздо удобнее иметь одну рабочую версию конфига, и менять ее на время разработки.

    Решение


    А решение очень простое: нам не нужно создавать копию конфига, если мы можем воспользоваться наследованием, применив его к массиву, возвращаемому файлом конфига. Каждый экземпляр приложения, вызванный с локального компьютера, будет создаваться с локальной версией конфига, а екземпляр, созданный по запросу с удаленного компьютера будет запускаться с параметрами из рабочей версии.
    Перейдем к примерам. Вот обновленный входной файл нашего приложения — index.php:
    <?php
    
    $yii=dirname(__FILE__).'/../yii/framework/yii.php';
    
    if($_SERVER['REMOTE_ADDR']=='127.0.0.1') { 
        // если зашли локально, 
        defined('YII_DEBUG') or define('YII_DEBUG',true);
        defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);
        //то использовать локальную версию конфига
        $config=dirname(__FILE__).'/protected/config/develop.php';
    } else { 
        // если обращение происходит к рабочей версии приложения
        $config=dirname(__FILE__).'/protected/config/main.php'; 
        
    }
    
    require_once($yii);
    Yii::createWebApplication($config)->run();
    

    Теперь рассмотрим упомянутый выше файл '/protected/config/develop.php'. Если вы внимательно посмотрите на его логику, то поймете, что никакого изобретения велосипеда тут нет, а вся идея очень проста и заключается в наследовании массивов:
    <?php
    //функция для наследования массива
    //ниже мы передадим в нее массив, возвращаемый файлом рабочей версии конфига
    function array_extends(array $array1, array $array2) {
        foreach($array2 as $key=>$value) {
            if(is_int($key))
                $array1[]=$value;
            else if(empty($array1[$key]) || !is_array($value))
                $array1[$key]=$value;
            else if(is_array($value))
                $array1[$key]=array_extends($array1[$key], $value);
        }
        return $array1;
    }
    //наследуем в нашей локальной версии рабочую версию конфига
    //параметры, задынные ниже, будут применены для локального вызова приложения
    //не заданные здесь параметры будут унаследованы от рабочей версии
    return array_extends(include('main.php'), array(
        // перезапишется значение
        'name'=>'Local version', 
        'import'=>array(
            // добавится еще один путь в import
            'application.extensions.yiidebugtb.*' 
            'application.extensions.yiidebugtb.*
        ),
        'modules'=>array(
            // добавится модуль gii к остальным модулям? подключенным в главном конфиге
            'gii'=>array(
                'class'=>'system.gii.GiiModule',
                'password'=>'password',
                ...
            ),
        ),
        'components'=>array(
    		'db'=>array(
                            // меняем connectionString для отладки на локальной машине
    			'connectionString' => 'mysql:host=localhost;dbname=mycms', 
                            // изменяем логин для отладки базы данных на локальной машине.
    			'username' => 'root',
                            // изменяем пароль для отладки базы данных на локальной машине.
    			'password' => '', 
                // включаем профилирование запросов
                'enableProfiling'=>true, 
                /* 'tablePrefix' => 'tbl_', определять не нужно поскольку он определен в дочернем массиве */
    		),
            'log'=>array(
                // подключаем еще два дебагера в файл для удобной разработки
                'routes'=>array( 
                    array(
                        'class'=>'XWebDebugRouter',
                        ...
                    ),
                    array(
                        'class'=>'CWebLogRoute',
                        ...
                    ),
                )
            ),
        ),
    ));

    Я думаю все, кто сталкивался с Yii Framework, непременно имели дело и с файлом protected/config/main.php, но я все же поясню: этот файл возвращает массив параметров, которые в итоге применяются к запущенному экземпляру приложения. Для наглядности я предоставлю очень сокращенный пример кода из этого файла:
    <?php
    return array(
    	'name'=>'Working Version',
        ...
    	'import'=>array(
    		'application.models.*',
    		'application.components.*',
        ),
    	'modules'=>array(
    		// uncomment the following to enable the Gii tool
            'admin',
    	),
        ...
    	// application components
    	'components'=>array(
    		'db'=>array(
    			'connectionString' => 'mysql:host=localhost;dbname=host_db',
    			'emulatePrepare' => true,
    			'username' => 'hosh_login',
    			'password' => 'hosh_password',
    			'charset' => 'utf8',
                'tablePrefix' => 'tbl_',
    		),
    		'log'=>array(
    			'class'=>'CLogRouter',
    			'routes'=>array(
    				array(
    					'class'=>'CFileLogRoute',
    					'levels'=>'error, warning',
    				),
    			),
    		),
    	),
        ...
    );

    Как видите, идея действительно очень проста, но может быть очень полезной, особенно что касается тех проектов, которые уже запущены.

    TheAlien подсказал что это решение уже реализовано ссылка

    Спасибо за внимание.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 21

      0
      Можно исходники привести в нормальный вид? А то я что-то потерялся тута :)
        +3
        Кстати. Не знаю как вы, но даже однострочные ифы не перевериваю без скобок :)
          0
          Такой стиль кода у Yii и видимо автор его придерживается.
          0
          прошу прощения. сейчас)
          +1
            0
            Да, мне уже сообщили.
            –2
            В ZF это делается простым наследованием секций к примеру в ini-файлах, очень удобно… Можно ведь, как вариант, использовать Zend_Config в Yii проектах.
              0
              Давайте подождем… Сейчас кто-нибудь рельсы вспомнить :) Хоть посмеемся…
                0
                Чьорт. Теги sarcasm съелись :) Но суть думаю понятна :)
                –1
                а в симфони можно вообще сколько угодно энвайрментов создавать, из коробки три идут — прод, дев, тест. И конфиги там куда приятнее выглядят.
                –1
                У меня немного другое решение, к тому же оно более расширенное :)

                <?php
                // Config base dir
                $base = dirname(__FILE__).DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.
                    'config'.DIRECTORY_SEPARATOR;
                
                // Get the domain
                $domain = $_SERVER['SERVER_NAME'];
                if (strpos($domain, 'www.') !== false) {
                	$domain = str_replace('www.', '', $domain);
                }
                
                if (!file_exists($base.'sites'.DIRECTORY_SEPARATOR.$domain.'.php')) {
                	$domain = 'dev.example.com';
                }
                
                $main = include($base.'main.php');
                $domain_config = include($base.DIRECTORY_SEPARATOR.'sites'.DIRECTORY_SEPARATOR.$domain.'.php');
                
                // Load the Yii framework
                $yii=dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'yii'
                    .DIRECTORY_SEPARATOR.'framework'.DIRECTORY_SEPARATOR
                    .(defined('YII_DEBUG') ? 'yii.php' : 'yiilite.php');
                require_once($yii);
                
                // Merge configs
                $config = CMap::mergeArray($main, $domain_config);
                // Init the application
                /** @var $app CWebApplication */
                $app = Yii::createWebApplication($config);
                unset($domain, $base, $yii, $config, $main, $domain_config);
                
                Yii::import("ext.yiiext.components.zendAutoloader.EZendAutoloader", true);
                
                // you are able to load custom code that is using Zend class naming convention
                // with different prefix
                EZendAutoloader::$prefixes = array('Zend', 'Custom');
                Yii::registerAutoloader(array("EZendAutoloader", "loadClass"));
                
                $app->run();
                


                Конфиг main.php содержит в себе все основные настройки, а в подпапке sites лежат все дополнительные настройки, спечифичные для каждого конкретного домена — в простейшем варианте это local, dev и production. У меня 1 проект висит на 5 доменах, так что это ещё и удобный multi-domain вариант, когда каждому домену нужно различающиеся настройки.

                Прелесть CMap::mergeArray в том, что он обходит всё рекурсивно и добавляет (если элемента нету) или перекрывает (если элемент есть) элементы первого массива элементами из второго массива. Т.е. это позволяет мне держатьвсе настройки в main.php и переопределить отдельные настройки уже в специфическом для домена в его конфиге. Примерно вот так:

                main.php
                <?php
                return array(
                	'basePath' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '..',
                	'defaultController' => 'site',
                	'components' => array(
                		'urlManager' => array(
                			'urlFormat' => 'path',
                			'showScriptName' => false,
                			'urlSuffix'=> '.html',
                			'rules' => array(
                				'<lang:\w{2}>/<controller:\w+>/<id:\d+>' => '<controller>/view',
                				'<lang:\w{2}>/<controller:\w+>/<action:\w+>/<id:\d+>' => '<controller>/<action>',
                				'<lang:\w{2}>/<controller:\w+>/<action:\w+>' => '<controller>/<action>',
                				'<lang:\w{2}>/<controller:\w+>' => '<controller>/index',
                			),
                		),
                
                		'db' => array(
                			'class' => 'system.db.CDbConnection',
                			'charset' => 'utf8',
                			'schemaCachingDuration'=>3600,
                			'emulatePrepare' => true,
                		),
                	),
                	'params' => array(
                		'mail' => array(
                			'smtp_from' => 'example@example.com',
                			'pass' => '12345',
                			'ip' => '192.168.0.1',
                			'port' => '25',
                		),
                	),
                );
                


                example.com.php — production site config
                <?php
                return array(
                	'components' => array(
                		'db' => array(
                			'connectionString' => 'mysql:host=mysql_host;dbname=project_db',
                			'username' => 'production',
                			'password' => 'production password',
                		),
                	),
                );
                


                dev.example.com.php — dev config
                <?php
                defined('YII_DEBUG') or define('YII_DEBUG',true);
                defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);
                return array(
                	'components' => array(
                		'db' => array(
                			'connectionString' => 'mysql:host=localhost;dbname=project_db',
                			'username' => 'devuser',
                			'password' => 'dev password',
                			'enableProfiling' => true,
                			'enableParamLogging' => true,
                			'schemaCachingDuration' => 0,
                		),
                	),
                	'params' => array(
                		'mail' => array(
                			'smtp_from' => 'test@example.com',
                			'pass' => 'localtest',
                			'ip' => 'localhost',
                			'port' => '25',
                		),
                	),
                );
                


                Надеюсь общая концепция понятна :)
                  0
                  А вам не кажется что по количеству буков данное решение является хумусом?
                    +1
                    Непростому проекту непростые конфиги. Когда у вас на одной кодовой базе крутится 8 сайтов (и скажу далеко не визитки), отличающихся друг от друга дизайном, работающие с двумя базами — одной общей и по cms базе на каждый сайт — это ещё очень простое и элегантное решение на мой вкус. К тому же я же не выкладывал в примеры конфиги целиком — там гораздо больше опций, которые переопределяются для каждого сайта.

                    Это решение мне позволяет спокойно настраивать каждый сайт прямо на локальной машине, сохранять в репозиторий и выкладывать на рабочие сервера, при этом делать с моей локальной копией всё что хочу. И вообще каждый разработчик в комманде может спокойно иметь свой конфиг и спокойно переносить настройки между работой и домом.
                    В общем получается абсолютно прозрачная система.

                    Я всего лишь поделился своим решением. Использовать или нет — дело каждого лично. Не нравится — не пользуйтесь :)
                      0
                      > Когда у вас на одной кодовой базе крутится 8 сайтов

                      Лично для меня это уже фейл. :)
                        0
                        Вы же не знаете что и как, так что судить зарание явно не стоит.
                  –1
                  А чем функция array_merge_recursive не угодила вместо самописной array_extends?
                  // конфиг в виде масива выглядит ужасно, ini или yml удобнее
                    0
                    Работа функции array_merge_recursive отличается от работы самописной функции array_extends, и она не подходила для данной задачи. Конфиг php не выглядит ужасно. Это обычный код. Зато в отличии от ini или yml в нем можно использовать php конструкции и объявлять переменные. Хотя в целом это дело вкуса как хранить настройки
                      +1
                      В symfony в yaml файлах тоже можно использовать php-код :) Не исключаю, что и в ini.
                    0
                    У нас тоже стояла задача разделения конфигов в Yii, но мы её решили по-другому, — двумя ветками в git, серверной и девелоперской. После окончания работ в серверную ветку мержится девелоперская. Единожды разрешив конфликт мы больше с ним не сталкиваемся.
                      0
                      мы используем другой подход code.google.com/p/pha-yii-author/ — авторская конфигурация.
                      плюсы:
                      — каждый разработчик/инсталяция может имет собственный конфиг (со всеми штатными возможностями наследования)
                      — для подключения нет необходимости вносить изменения в код (index.php) — нужный конфиг подключается автоматически, стоит создать соответствующий пустой файл (соответствующая директория добавляется в игнор в системе контроля версий).
                      — поскольку конфиг у кажого свой разработчки может не бояться поломать общий файл, подключать в свем конфиге различные экспериментальные фичи
                      как ставить/подключать описано на форуме www.yiiframework.ru/forum/viewtopic.php?f=9&t=2479&p=15258
                        0
                        Идея хорошая, но зачем такие сложности? можно реализовать всё гораздо проще, без расширений.

                        В корне создаём файлик ".author" и в нем пишем ник автора.

                        В index.php добавляем:
                        $webRoot = dirname(__FILE__);
                        $author = null;
                        $authorFile = $webRoot . '/.author';
                        if (is_file($authorFile)) {
                            $author = trim(file_get_contents($authorFile));
                        }
                        


                        ну и дальше как в вашем примере:
                        if (!empty($author)) {
                            $config = $webRoot . '/protected/config/' . $author . '.php';
                            if (!file_exists($config)) {
                                $config = $webRoot . '/protected/config/main.php';
                            }
                            defined('YII_DEBUG') or define('YII_DEBUG',true);
                            defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);
                        
                        } else {
                            $config = $webRoot . '/protected/config/production.php';
                        }
                        


                        Авторские конфиги и «prodaction» мержим с «main», как описано в «рецептах»:
                        <?php
                        return CMap::mergeArray(
                            require(dirname(__FILE__) . '/main.php'),
                            array(
                                'components'=>array(
                                    'db' => array(
                                    ...
                                    ),
                                ),
                            )
                        ); 
                        

                      Only users with full accounts can post comments. Log in, please.