Yii 1.1: Manage application configuration in different modes

15 followers

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
Chinese Version

Total 5 comments

#15199 report it
Harman at 2013/10/17 03:37am
Another Alternative?

Hey qiang,

Thanks for this article. I had a requirement of keeping a single entry script (index.php) and came up with the following solution. What do you PHP Guru's think of this:

$config=dirname(__FILE__).'/protected/config/main.php';
 
// Overwrite with dev file if the dev file exists
if (file_exists(dirname(__FILE__).'/protected/config/dev.php')) {
    $config=dirname(__FILE__).'/protected/config/dev.php';
}
#2039 report it
Nayjest at 2010/11/04 08:20am
Predefining of server-specific path to config & other constatns by Apache (ZF-way)
<VirtualHost *:80>
           ....
          SetEnv APPLICATION_ENV "production"
          SetEnv YII_INCLUDE_PATH "/path/too/framework/yii.php"
           ....
</VirtualHost>

index.php:

# Used only for including corresponding 
# server configuration file,
# can be predefined by Apache 
# (SetEnv APPLICATION_ENV "production")
# ['local' | 'dev' | 'production']                                                                                                                  
defined('APPLICATION_ENV') or define('APPLICATION_ENV','local'); 
 
 
$configMain = include dirname(__FILE__).'/protected/config/main.php';
#must be included before Yii to define YII_DEBUG, YII_TRACE_LEVEL
$configServer = include dirname(__FILE__) 
                .'/protected/config/server.'
                .APPLICATION_ENV
                .'.php';
 
require_once(YII_INCLUDE_PATH); # defined in config/server.*.php or via Apache
$config = CMap::mergeArray($configMain,$configServer);
...
#1068 report it
Mike at 2009/12/19 06:55am
Setting YII_DEBUG in dev.php

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.

#1082 report it
TigerMunky at 2009/12/14 07:07am
Another alternate with external files

--- 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',

...

#1692 report it
will at 2009/05/08 11:11pm
An anterlavtive flavor

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:)

Leave a comment

Please to leave your comment.

Write new article