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.
Setting YII_DEBUG in dev.php
As i prefer to set
YII_DEBUG
in my localdev.php
i had to change the above method a little. The problem is thatYII_DEBUG
has to be set beforeyii.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 formain.php
and optionally setsYII_DEBUG
.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',
...
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:)
Predefining of server-specific path to config & other constatns by Apache (ZF-way)
[html] <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); ...
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'; }
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.