One of the challenges of this is where and how to store the connection information for all these services. We have done several things in the past. The most common thing is to store this information in a PHP file. It may be per server or there could be one big file like:
<?php
if(DEV){
$server = "localhost";
} else {
$server = "10.1.1.25";
}
?>
This gets messy quickly. Option two is to deploy a single file that has the settings in a PHP array. And that is a good option. But, we have taken that one step further using some PHP ini trickeration. We use ini files that are loaded at PHP's startup and therefore the information is kept in PHP's memory at all times.
When compiling PHP, you can specify the --with-config-file-scan-dir to tell PHP to look in that directory for additional ini files. Any it finds will be parsed when PHP starts up. Some distros (Gentoo I know) use this for enabling/disabling PHP extensions via configuration. For our uses we put our custom configuration files in this directory. FWIW, you could just put the above settings into php.ini, but that is quite messy, IMO.
To get to this information, you can't use ini_get() as you might think. No, you have to use get_cfg_var() instead. get_cfg_var returns you the setting, in php.ini or any other .ini file when PHP was started. ini_get will only return values that are registered by an extension or the PHP core. Likewise, you can't use ini_set on these variables. Also, get_cfg_var will always reflect the initial value from the ini file and not anything changed with ini_set.
So, lets look at an example.
; db.ini
[myconfig]
myconfig.db.mydb.db = mydb
myconfig.db.mydb.user = user
myconfig.db.mydb.pass = pass
myconfig.db.mydb.server = host
This is our ini file. the group in the braces is just for looks. It has no impact on our usage. Because this is parsed along with the rest of our php.ini, it needs a unique namespace within the ini scope. That is what myconfig is for. We could have used a DSN style here, but it would have required more parsing in our PHP code.
<?php
/**
* Creates a MySQLi instance using the settings from ini files
*
* @author Brian Moon <brianm@dealnews.com>
* @copyright 1997-Present dealnews.com, Inc.
*
*/
class MyDB {
/**
* Namespace for my settings in the ini file
*/
const INI_NAMESPACE = "dealnews";
/**
* Creates a MySQLi instance using the settings from ini files
*
* @param string $group The group of settings to load.
* @return object
*
*/
public static function init($group) {
static $dbs = array();
if(!is_string($group)) {
throw new Exception("Invalid group requested");
}
if(empty($dbs["group"])){
$prefix = MyDB::INI_NAMESPACE.".db.$group";
$db = get_cfg_var("$prefix.db");
$host = get_cfg_var("$prefix.server");
$user = get_cfg_var("$prefix.user");
$pass = get_cfg_var("$prefix.pass");
$port = get_cfg_var("$prefix.port");
if(empty($port)){
$port = null;
}
$sock = get_cfg_var("$prefix.socket");
if(empty($sock)){
$sock = null;
}
$dbs[$group] = new MySQLi($host, $user, $pass, $db, $port, $sock);
if(!$dbs[$group] || $dbs[$group]->connect_errno){
throw new Exception("Invalid MySQL parameters for $group");
}
}
return $dbs[$group];
}
}
?>
We can now call DB::init("myconfig") and get a mysqli object that is connected to the database we want. No file IO was needed to load these settings except when the PHP process started initially. They are truly constant and will not change while this process is running.
Once this was working, we created separate ini files for our different datacenters. That is now simply configuration information just like routing or networking configuration. No more worrying in code about where we are.
We extended this to all our services like memcached, gearman or whatever. We keep all our configuration in one file rather than having lots of them. It just makes administration easier. For us it is not an issue as each location has a unique setting, but every server in that location will have the same configuration.
Here is a more real example of how we set up our files.
[myconfig.db]
myconfig.db.db1.db = db1
myconfig.db.db1.server = db1hostname
myconfig.db.db1.user = db1username
myconfig.db.db1.pass = db1password
myconfig.db.db2.db = db2
myconfig.db.db2.server = db2hostname
myconfig.db.db2.user = db2username
myconfig.db.db2.pass = db2password
[myconfig.memcache]
myconfig.memcache.app.servers = 10.1.20.1,10.1.20.2,10.1.20.3
myconfig.memcache.proxy.servers = 10.1.20.4,10.1.20.5,10.1.20.6
[myconfig.gearman]
myconfig.gearman.workload1.servers = 10.1.20.20
myconfig.gearman.workload2.servers = 10.1.20.21
OnyxRaven Says:
very nice usage of the internal parser. Downsides are that a php restart is required to reload settings, and that PHP ini parsing can be somewhat odd at times. Be careful about how strings look and are escaped if you're doing anything too complicated.
Also, the 'dotted notation' in ini is really easy to mess up if you're configuring something with lots of config items.
I prefer yml (syck) just because its easier to read. Downside there is that you dont get the performance boost from the internal on-startup-parser like the post has.