Manage application configuration in different modes

Info: This tutorial references the idea presented in Jonah's excellent blog article.

Application configuration determines how an Yii application should behave, because it is the only parameter passed in the entry script. An application, on the other hand, needs to behave differently under different circumstances. For example, an application may need different configurations when running in production mode, development mode and testing mode; In a team development environment, each developer may have his own database connection and thus require a customized application configuration. In this tutorial, we describe how to manage application configurations to fulfill all the above needs.

Before we start, we should note that an application configuration is stored as a PHP script. As a result, we can place any valid PHP code in it, which may make the configuration more 'intelligent' than simply returning an array of name-value pairs.

First, we create the main application configuration and store it in the file main.php. This file should contain all necessary configurations that are needed when the application is running in the production mode.

Second, we create the development application configuration and store it in the file dev.php. Because the development and main configurations are mostly the same, we create the former by inheriting it from the latter. We use CMap::mergeArray to implement the desired inheritance:

<?php
return CMap::mergeArray(
    require(dirname(__FILE__).'/main.php'), 
    array(
        'components'=>array(
            'db'=>array(
                // define DB connection used for development
            ),
        ),
    )
);

In the above, we first include main.php, and define the customized configuration array (the example shows defining a customized DB connection configuration). We then return the merged result of the two as the final development configuration. Note that we should not use PHP's array_merge() or array_merge_recursive() functions, as neither of them would merge the two arrays in the way we want.

We can similarly define the testing application configuration and store it in the file test.php.

In order to run the application in different modes (production, development, or testing), we should use the corresponding configuration in the entry script. To save the trouble of constantly modifying the entry script to switch the mode, we can create an entry script for each mode. For example, we can have index.php, index-dev.php and index-test.php, corresponding to production, development and testing mode, respectively. In production, we use index.php in the browser URL to access the application; in development, we use index-dev.php and in testing, we use index-test.php.

In a team development environment managed with some source control system (e.g. SVN, CVS, GIT), each developer may want to have his own application configuration (e.g. because he may have a different DB connection setting). In this case, we should only store main.php in the repository. The rest of the configuration files should only be kept in each developer's file system to avoid conflict of development configuration changes.

Tip: The same technique may also be applied to other PHP-based configurations. For example, if we store application parameters (accessed via Yii::app()->params) in a PHP file, we can use the above technique to customize parameters in different modes.

Links

Russian Version

Total 5 comments:

#269
:)
by jonah at 5:00pm on May 8, 2009.

made me smile when I read the first part

I admit I never tested my own idea, I'll update my post to use CMap::mergeArray()

#270
?
by jonah at 5:07pm on May 8, 2009.

or maybe in my case I want to leave it as array_merge_recursive()? In my writing, it is set up a little differently.

#271
An anterlavtive flavor
by will at 5:11pm on May 8, 2009.

I did a similar way but only have one config file with arrays defined as

$_config'dev' = array( ... );

$_config'staging' = array( ... );

$_config'prod' = array( ... );

then do a switch: switch(MODE){ case 'staging': return array_merge_keys($_config'staging',$_config'dev'); break;

case 'prod':
    return array_merge_keys($_config['prod'],$_config['dev']);
break;

default:
    return $_config['dev'];
break;

}

using customized function: function array_merge_keys($arr1, $arr2) { foreach($arr2 as $k=>$v) { if (!array_key_exists($k, $arr1)) { //K DOESN'T EXISTS // $arr1$k=$v; } else { // K EXISTS // if (is_array($v)) { // K IS AN ARRAY // $arr1$k=array_merge_keys($arr1$k, $arr2$k); } } } return $arr1; }

So that you use it in index.php in this way: defined('MODE') or define('MODE','dev');// 'dev' , 'staging', or 'prod' $config=dirname(FILE).'/protected/config/main.php';

Just an alternative flavor:)

#898
Another alternate with external files
by tigermunky at 12:07am on December 14, 2009.

--- main.php ---

<?php

$MODE = 'prod';

switch($MODE) {

case 'dev': return require(dirname(__FILE__).'/dev.php');
    break;
case 'qa': return require(dirname(__FILE__).'/qa.php');
    break;
case 'prod': return require(dirname(__FILE__).'/prod.php');
    break;
default : return require(dirname(__FILE__).'/dev.php');
    break;

}

--- dev.php --- in same dir as main.php

<?php

return array(

'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
'name'=>'My App Name Dev',

...

--- prod.php --- in same dir as main.php... was thinkin that its not good to allow devs access to prod (incase they delete huge chunks of data by accident), so probably only add the prod script at deployment time.

<?php

return array(

'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
'name'=>'My App Name',

...

--- qa.php --- in same dir as main.php

<?php

return array(

'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
'name'=>'My App Name QA',

...

#912
Setting YII_DEBUG in dev.php
by mikl at 12:55am on December 19, 2009.

As i prefer to set YII_DEBUG in my local dev.php i had to change the above method a little. The problem is that YII_DEBUG has to be set before yii.php is included.

So my index.php looks like this:

$dir=dirname(__FILE__);
$localconf=require($dir.'/protected/config/dev.php');
 
// Yii is in my php include path...
require_once('yii-1.0.10/yii.php'); 
require_once($dir.'/protected/helpers/globals.php');
 
$config=CMap::mergeArray(require($dir.'/protected/config/main.php'),$localconf);
Yii::createWebApplication($config)->run();

No merging is required in dev.php anymore. It only returns an array with overrides for main.php and optionally sets YII_DEBUG.

Your Comment:

You may enter comment using Markdown syntax.

Please login with your forum account.
Note: you must have at least ONE forum post with your account.