Custom Apps: Some Strategies For Easy Configuration Files

« »

One of the decisions that has to be made each time an application is written for distribution is how best to set up the configuration files. There are a number of different approaches taken to this: some opt to use the define() function and define constants, while others use large arrays. The purpose of this post is to discuss a couple of configuration options that sometimes are overlooked by developers (including myself until last year).

Class Constants
One of the biggest struggles in OOP programming is how to go about setting up the default values and configuration values. For example, if you have a database class, how do you define the connection parameters?

One solution that I like is to use class constants. Class constants are just like constants you define with the define() construct; however, they adhere to more object-oriented patterns, and help keep you from having to draw the constants from outside the class. There are some drawbacks, though: while constants can be defined at runtime with a variable, you have to define class constants in the code base itself. That is, you have to use a string or an outside constant to set the constant’s value.

Let’s take a look at how this works:

Example 1: A typical database constructor

// Assuming a MySQL connection and
// not addressing database interoperability...
public function __construct($username = 'user', $password = 'pass', $host = 'host', $database = 'mydb')
{
	$dsn = 'mysql:host=' . $host . ';dbname=' . $database;
	
	try {
		$this->dbh = new PDO($dsn, $username, $password);
	} 
	catch (PDOException $e) {
		throw new DBException('Unable to connect to PDO: ' . $e->getMessage);
	}
}

Example 2: A database constructor using constants

// Assuming a MySQL connection and
// not addressing database interoperability...
public function __construct($username = Config::DBUSER, $password = Config::DBPASS, $host = Config::DBHOST, $database = Config::DATABASE)
{
	$dsn = 'mysql:host=' . $host . ';dbname=' . $database;
	
	try {
		$this->dbh = new PDO($dsn, $username, $password);
	} 
	catch (PDOException $e) {
		throw new DBException('Unable to connect to PDO: ' . $e->getMessage);
	}
}

It might look initially as though I’ve simply replaced the default arguments with another set of default arguments. But look again. I’ve done more than that. While the arguments would likely be stored in the Config class and be identical to the ones we were giving the constructor before, by placing all the arguments in a class (and presume that we place a large number of other arguments in that class as well regarding our configuration), we’ve simplified distribution. Imagine if you distributed your code to six different servers, all with different hosts. That would be insane to change the code every time!

The use of class constants also allows us to access values in another class through PHP’s object model, rather than relying on the “hacks” in PHP (like the fact that constants defined with define() are in all scopes). It also means that we don’t have to pass in the connection arguments each time we connect – unless we want to connect to a non-standard database.

This model is fairly well used by lots of projects, including Zend Framework and Propel, but often trips up first time developers of object-oriented code.

INI Files
Every professional PHP developer is familiar with the php.ini syntax, and has probably edited their php.ini file on occasion. What many developers don’t know (and I didn’t even know until DrupalCon 2009) is that PHP includes a function for parsing a configuration file in the INI style. The function is called parse_ini_file() and it parses an INI file into an array.

Example 3: Sample INI file

; Application configuration script.

[database]
host = localhost
database = mydb
username = user
password = password

[paths]
uploaddir = /var/www/uploads
includedir = /var/www/includes
publicdir = /var/www/public

By using the parse_ini_file() function, we get an array that looks like this (output using print_r()):

Example 2: Parsed INI file as array in PHP

Array
(
    [database] => Array
        (
            [host] => localhost
            [database] => mydb
            [username] => user
            [password] => password
        )

    [paths] => Array
        (
            [uploaddir] => /var/www/uploads
            [includedir] => /var/www/includes
            [publicdir] => /var/www/public
        )

)

It’s that simple. The function returns an array of the items we put in our INI file, and we can use that array naively inside PHP to do whatever we like.

There are some gotchas. The first one is that in order to get parse_ini_file() to honor the sections, we have to pass it a second, optional argument of true. The default is false, and it will produce just a straight array of the values. Also, while we can create a three-level-deep array by adding brackets [] to the end of a key in the INI file, you can’t go deeper than that (making multi-level configuration files impossible). Also, you cannot have dynamic variables in the INI file, so this applies the same rules as the class constants (you have to spell out your configuration options before runtime). Still, this system allows a great amount of flexibility and provides a commonly understood INI-style file that can be edited by most systems administrators who understand what the values mean.

In Closing…

Hopefully these tips will make releasing code easier and faster for you as a developer, and provide techniques that will improve your configuration options. These techniques are by no means the only way to solve the problem of providing a configurable system, but should provide some additional options that seem to be often overlooked.

Additional Reading:
PHP Manual: parse_ini_file()
PHP Manual: define()
PHP Manual: PDO
PHP Manual: Classes and Objects (PHP 5)
PHP Manual: Class Constants

Brandon Savage is the author of Mastering Object Oriented PHP and Practical Design Patterns in PHP

Posted on 9/16/2009 at 1:00 am
Categories: PHP 5, Best Practices, System Architecture
Tags: , ,

Samuel Folkes (@SamuelFolkes) wrote at 9/16/2009 1:58 am:

Informative article Brandon. This is a topic that I gave a lot of thought once I made the switch to OOP and realized that using define() to set up configuration constants wasn’t always a viable option. An alternative method to avoid the caveats of using INI files if you don’t mind sacrificing a bit of readability is to use an XML configuration file in tandem with the SimpleXML extension. Its a very flexible solution that has the added advantage that most PHP developers are somewhat familiar with XML.

Jiri Fornous wrote at 9/16/2009 3:35 am:

define constants
– has a potential problem with namespaces and autoload in Php 5.3
– wouldn’t recommend for configuration

class constants
– has disadvantage of only simple types – no string concat, no function assign, is final
– wouldn’t recommend for configuration

ini or xml file
– unless you cache the file in cache (APC, Memcache) it’s slow solution because of everytime disc usage
– would recommend only with cache

class properties
– using magic __get method with main switch of (hash map for speed) defined properties is good solution. It works well with namespaces, no additional cache handling necessary when using opcode cache, easy for autoload, you may decide whether accept or deny value change using __set magic method.
– would recommend

Rob (@roryoung) wrote at 9/16/2009 4:03 am:

Great post, thanks Brandon. I agree, if all your developers are comfortable with PHP then PHP for config files can be viable solution. An alternative to using default constructor arguments is to use named constructors. Static methods that clearly describe how the object is going to be created.

public static function createFromDefaultConfig()
{
return self::createFromConfig(Config::getInstance());
}

public static function createFromConfig(Config $config)
{
return new self($config->get(‘db:username’)…);
}

roy simkes wrote at 9/16/2009 4:39 am:

You might want to check out Zend Framework’s Zend_Config_Ini class for ini parsing too with some simple differences.

http://framework.zend.com/manual/en/zend.config.adapters.ini.html

What it does is basically the same as parse_ini_file, however it gives you the benefit to get deeper than 3rd level. By something like this:

webhost = http://www.example.com
database.adapter = pdo_mysql
database.params.host = db.example.com
database.params.username = dbuser

you can reach the username parameter by something like this:

$config = new Zend_Config_Ini(‘config.ini’);
echo $config->databaser->params->username;

You can also define different configuration options for different environments like production and testing.

Alan Pinstein (@apinstein) wrote at 9/16/2009 10:11 am:

Nice post!

I just had to deal with a similar, but different problem, of managing *multiple* config files for projects. When all project components aren’t in your control, or even all in the same language, config files proliferate and updating them to work properly on different environments is a painful process.

I wrote and open-sourced an app called config-magic that lets you easily manage multiple config files from a single “profile” config, thus allowing you to regenerate N config files for any box with a single command. You can store multiple profiles and thus easily re-configure any project checkout to target any environment in a matter of seconds.

Check it out: http://github.com/apinstein/config-magic

Hervé wrote at 9/16/2009 12:41 pm:

You forgotten json (stored in files) which is also a good way to keep parameters

Zilvinas wrote at 9/17/2009 9:22 am:

If you *CAN* allways pass or inject using DI the configuration object fully or partially to objects that require it. Constants, class constants, static class access is the same as using global state. And global state is not a very heatlhy way of living. More on this:

http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/

XML was not created for human beings to read so if you are not using *TOOLS* to modify your configuration do not use XML. There is no reason to. Where INI has limitations YAML doesn’t. Which is extremely readable and very powerful. Or best if only developers will be changing the configuration it may be as well left as a PHP array which allows to have dynamic values.

Michael Sinclair (@mpsinclair) wrote at 9/17/2009 12:09 pm:

Just be sure that if you use an INI file that you keep your INI file in a non-public directory, and protect it with htaccess or some other method.

Voyteck wrote at 9/19/2009 5:41 pm:

Nice article – but quite easy one. Once you have more sophisticated needs, the topics showed are not enough.
Just wanted to add some three pennies with my article:
http://linkedphpers.blogspot.com/2009/04/webapplications-part-2-standard.html
You can find there several additional ways of storing and managing system configuration…

Jeff Dickey (@jeff_dickey) wrote at 12/1/2009 12:14 am:

I haven’t written new code using INI files in years… up until early 2008, I had a set of configuration classes whose data storage format was XML; that allowed me to express arbitrary types and nesting in a portable manner. That has been almost completely superseded by YAML and Spyc, which give me the flexibility I need with a much more terse, human-readable data format. If you have a need for persisting configuration or other such information, you should at least poke around with it.

http://spyc.sourceforge.net
http://www.yaml.org/
http://en.wikipedia.org/wiki/YAML

JasonDavis wrote at 1/26/2010 6:48 pm:

I would like to add to the users who recommend using YAML, I do’t have the link but I have read a couple benchmark test based on using pure PHP, ini file, json file, xml file, and YAML file and YAML came in last place for performamce. Where ini was number 2. I beleive the test showed the ini file to do around 3,000 reads per seconds and the YAML file was around 400 just to give you an idea of how much slower it is.

« »

Copyright © 2023 by Brandon Savage. All rights reserved.