Porto

Application Configuration

Application Configuration

By Kevin Waterson

Contents

Abstract

PHP applications come in many shapes and sizes. Some used locally from command line, and more commonly, for web based applications. More often than not, regardless of size or type, some form of configuration variables will be stored for global access. Usually these configuration options are held in one of these four types of containers.

  • ini file
  • XML file
  • PHP file
  • Database

Each options has its pros and cons. Here each of these options is explored to see which method is right for your application.

The ini File

The ini(tialization) file has been around for many years and has come to be a pseudo standard among application developers for initializing data variables. Indeed, even PHP uses the php.ini file to initialize many configuration options within the PHP itself. The easy of having a single file to edit proves a great advantage, however, shell access is required to do so, or edit locally and upload to the server. More advanced component libraries such as eZ Components have a built in ini file class to help read and write ini files.

Most applications that use an ini file for configuration use the PHP parse_ini_file() function to get values from the ini file. Many classes and abstractions have been written to provide a Object Oriented approach, but the core remains the single PHP function to gain access to the values within.

The simple syntax within the ini file makes it simple to edit and is easy to read. The reason it has been around so long is due to this simplicity. Here is an example of an ini file.

;
; This is a comment
;
[database]
db_username = username
db_password = password
db_host = localhost
db_port = 1234

[mail]
mail_username = username
mail_password = password
mail_host = mail.example.com

[blog]
blog_title = "My Blog"
blog_moderate = 1
blog_images = 1

To parse the example ini file, simply save it as config.ini and to parse, as mentioned earlier, the PHP parse_ini_file() function is used.


<?php
    
/*** turn on errors ***/
    
error_reporting(E_ALL);

    
/*** parse the ini file ***/
    
$config parse_ini_file("config.ini"1);

    
/*** print the array ***/
    
print_r($config);
?>

This simple script above reads the ini file int a multi-dimensional array for easy retrieval. The array looks like this.

Array
	(
		[database] => Array
		(
			[db_username] => username
			[db_password] => password
			[db_host] => localhost
			[db_port] => 1234
		)

		[mail] => Array
		(
			[mail_username] => username
			[mail_password] => password
			[mail_host] => mail.example.com
		)

		[blog] => Array
		(
			[blog_title] => My Blog
			[blog_moderate] => 1
			[blog_images] => 1
		)
	)

To retrieve a single value from the array is now a simple matter of reading the array as with any PHP array. In the following script, a single value is retrieved from the ini file, and a timer is added to see how long it takes.


<?php
    
/*** turn on errors ***/
    
error_reporting(E_ALL);

    
/*** start a timer ***/
    
$start microtime(true);

    
/*** parse the ini file ***/
    
$config parse_ini_file("config.ini"1);

    
/*** get the database user name ***/
    
echo $config['database']['db_username']."\n";

    
/*** end timer ***/
    
$end microtime(true);

    
/*** show duration ***/
    
echo round$end $start6) . "\n";
?>

The above script echos the database db_username configuration option and calculates the time in seconds, rounded to six decimal places. The results from this test averaged around 0.000421

username
0.000425

XML File

Too often the mantra "XML IS SLOW" is bellowed around. The reading of XML files has a reputation for slowness, but is it deserved? If there is a penalty to pay, just how large is it?

XML files are an excellent choice for config files as they provide more dimensions to the resulting configuration array. This means greater options for applications. Here is an example config.xml file example.

<?xml version="1.0" encoding="utf-8"?>
<application>
<database>
<db_username>username</db_username>
<db_password>password</db_password>
<db_host>localhost</db_host>
<db_port>1234</db_port>
</database>

<mail>
<mail_username>username</mail_username>
<mail_password>password</mail_password>
<mail_host>mail.example.com</mail_host>
</mail>

<blog>
<blog_title>My Blog</blog_title>
<blog_moderate>1</blog_moderate>
<blog_images>1</blog_images>
</blog>
</application>

The second most complained about issue with XML files is they are difficult to write and edit, however, many XML editors are now available for the task. Reading an XML config file is easy with SimpleXML. This demonstration script shows how.


<?php
    
/*** load the xml file ***/
    
$xml simplexml_load_file('config.xml');

    
/*** print the values ***/
    
print_r($xml);
?>
SimpleXMLElement Object
	(
		[database] => SimpleXMLElement Object
		(
			[db_username] => username
			[db_password] => password
			[db_host] => localhost
			[db_port] => 1234
		)

		[mail] => SimpleXMLElement Object
		(
			[mail_username] => username
			[mail_password] => password
			[mail_host] => mail.example.com
		)

		[blog] => SimpleXMLElement Object
		(
			[blog_title] => My Blog
			[blog_moderate] => 1
			[blog_images] => 1
		)

	)

The results bear a striking resemblance to the previous results using parse_ini_file. Here a single value is retrieved from the XML file using SimpleXML and a timer is added for comparison.


<?php
    
/*** turn on errors ***/
    
error_reporting(E_ALL);

    
/*** start a timer ***/
    
$start microtime(true);

    
/*** load the xml file ***/
    
$xml simplexml_load_file('config.xml');

    
/*** get a value ***/
    
echo $xml->mail->mail_username."\n";

    
/*** end timer ***/
    
$end microtime(true);

    
/*** show duration ***/
    
echo round$end $start6) . "\n";
?>

The above script averaged around 0.000717 seconds, which is a stark contrast to the previous methods.

username
0.000719

PHP File

A commonly used method of configuration is using a simple PHP file and a multi dimensional array. This method has the benefits of a language that is familiar to the developer, and easy to read. However, this method lacks any simple method of editing, other than remote log in, or editing the file locally and upload to server, as with the config.ini file.


<?php
    
/*** include the php file ***/
    
include 'config.php';

    
/*** get a value ***/
    
print_r($config);

?>
	Array
	(
		[database] => Array
		(
			[db_username] => username
			[db_password] => password
			[db_host] => localhost
			[db_port] => 1234
		)

		[mail] => Array
		(
			[mail_username] => username
			[mail_password] => password
			[mail_host] => mail.example.com
		)

		[blog] => Array
		(
			[blog_title] => My Blog
			[blog_moderate] => 1
			[blog_images] => 1
		)
	)

Once again an easy to access multi-dimensional array is produced. this of course makes it easy to access with simple PHP syntax. Here a single value is retrieved from the array, and a timer added for comparison.


<?php
    
/*** turn on errors ***/
    
error_reporting(E_ALL);

    
/*** start a timer ***/
    
$start microtime(true);

    
/*** include the php file ***/
    
include 'config.php';

    
/*** get a value ***/
    
echo $config['mail']['mail_username']."\n";

    
/*** end timer ***/
    
$end microtime(true);

    
/*** show duration ***/
    
echo round$end $start6) . "\n";
?>

In this test, the average was about 0.00045 seconds in duration. The speed is easily comparable with the ini file method, and much faster than the XML also, however, lacks ease of maintenance.

username
0.000455

Database

Storing config options in a database is nothing new. Many applications prefer the ease of maintenance, and are happy to sacrifice a performance hit when adding the overhead of a database connection to the script. But how much of a hit does it take? In the following example, the database section is replaced by a template option, as it would not make sense, to keep database access options, in the database.

The Database Schema

CREATE TABLE config(
template_name VARCHAR(20) NOT NULL,
template_directory VARCHAR(100) NOT NULL,
template_cache INT(1) NOT NULL,
cache_lifetime INT(11) NOT NULL,
mail_username VARCHAR(20) NOT NULL,
mail_password VARCHAR(20) NOT NULL,
mail_host VARCHAR(20) NOT NULL,
blog_title VARCHAR(20) NOT NULL,
blog_moderate INT(1) NOT NULL,
blog_images INT(1) NOT NULL
);

INSERT INTO config VALUES ('index', 'templates', 1, 3600, 'mail_username', 'mail_password', 'mail.example.com', 'My Blog', 1, 1);

<?php
    
/*** turn on errors ***/
    
error_reporting(E_ALL);

    
/*** start a timer ***/
    
$start microtime(true);

    
/*** connect to database ***/
    
$db = new PDO("mysql:host=localhost;port=3306;dbname=config"'username''password');
    
$stmt $db->prepare("SELECT * FROM config");
    
$stmt->execute();
    
$result $stmt->fetchAll(PDO::FETCH_ASSOC);

    
/*** get mail username from db results ***/
    
echo $result[0]['mail_username']."\n";

    
/*** end timer ***/
    
$end microtime(true);

    
/*** show duration ***/
    
echo round$end $start6) . "\n";

?>

This method averaged around 0.005533 seconds per access. This makes the database the slowest of the four options, however, provides the greatest accessability when ease of manipulation is a priority. The ability to configure site wide variables from an administration panel has great appeal.

Scaling

With these tests done, some sort of indicative performance ratings can be made, but how do they scale? In the following tests, the bounds of sanity are tested to see how well each method scales when given one thousand configuration sections, each with three settings. Of course, this would not done in a production environment, but will serve to test with.

ini File

The ini file method performed quite well in the initial tests, but having one thousand sections with three entries for each should test it. Rather than show the ini file that is used, the script to generate it is provied along with a small extract to show what is happening.


<?php
    
/*** create test ini file ***/
    
$config '';
    foreach( 
range(11000) as $i)
    {
        
$config .= "[section$i]\n";
        for(
$j=1$j<4$j++)
        {
            
$config .= "variable$j=$j\n";
        }
        
$config .= "\n";
    }
    
file_put_contents'config.ini'$config);
?>
[section1]
variable1=1
variable2=2
variable3=3

[section2]
variable1=1
variable2=2
variable3=3

........

[section999]
variable1=1
variable2=2
variable3=3

[section1000]
variable1=1
variable2=2
variable3=3

With the config file generated, a similar script to the first script above is used to read the ini file using the PHP functionparse_ini_file().


<?php
    
/*** turn on errors ***/
    
error_reporting(E_ALL);

    
/*** start a timer ***/
    
$start microtime(true);

    
/*** parse the ini file ***/
    
$config parse_ini_file("config.ini"1);

    
/*** get the database user name ***/
    
echo $config['section500']['variable2']."\n";

    
/*** end timer ***/
    
$end microtime(true);

    
/*** show duration ***/
    
echo round$end $start6) . "\n";
?>

This time the script results in the following:

2
0.021174

In all the tests, the average came in at about 0.021381 which is still kind of fast given the vast array that was provided.

XML File

The XML file reading with SimpleXML was on the slower end of the tests in the first run. This time with the added file size, io becomes an issue as the file is read from disk. Lets see how it scales.

Once again, the script to generate the XML is provided, rather than displaying a three thousand line file.


<?php
    $config 
'<?xml version="1.0" encoding="utf-8"?>'."\n";
    
$config .= '<application>'."\n";
    foreach( 
range(11000) as $i)
    {
        
$config .= "\t<section$i>\n";
        for(
$j=1$j<4$j++)
        {
            
$config .= "\t\t<variable$j>$j</variable$j>\n";
        }
        
$config .= "\t</section$i>\n";
    }
    
$config .= '</application>';
    
file_put_contents'config.xml'$config);
?>

A sample of the resulting XML file produced from the script is provided below.

<?xml version="1.0" encoding="utf-8"?>
<application>
	<section1>
		<variable1>1</variable1>
		<variable2>2</variable2>
		<variable3>3</variable3>
	</section1>
	<section2>
		<variable1>1</variable1>
		<variable2>2</variable2>
		<variable3>3</variable3>
	</section2>

	................

	<section999>
		<variable1>1</variable1>
		<variable2>2</variable2>
		<variable3>3</variable3>
	</section999>
	<section1000>
		<variable1>1</variable1>
		<variable2>2</variable2>
		<variable3>3</variable3>
	</section1000>
</application>
The resulting XML File is about 104k so reading it will be a big task for SimpleXML, even though the schema is quite simple itself, the io required is much higher

2
0.011811

The average time taken was about 0.01231 for the tests compared to the ini file at 0.021381 making the XML parsing 0.009071 faster!

PHP Array

The PHP performed quite well in the first test, but this is quite a large, multi dimensional array and will take up some memory. The script to create the array is provided along with a short extract of the actual PHP array that is generated.


<?php
    $out 
.= '<?php'."\n";
    
$out .= "\t".'$config = array();'."\n";
    foreach( 
range(11000) as $i)
    {
        
$out .= "\t";
        
$out .= '$config[\'section'.$i.'\'] = array();'."\n";
        for(
$j=1$j<4$j++)
        {
            
$out .= "\t\t".'$config[\'section'.$i.'\'][\'variable'.$j.'\']='."$j;\n";
        }
    }
    
$out .= '?>';
    
file_put_contents'config.php'$out);
?>

<?php
    $config 
= array();
    
$config['section1'] = array();
    
$config['section1']['variable1']=1;
    
$config['section1']['variable2']=2;
    
$config['section1']['variable3']=3;
    
$config['section2'] = array();
    
$config['section2']['variable1']=1;
    
$config['section2']['variable2']=2;
    
$config['section2']['variable3']=3;

    .......................

    
$config['section999'] = array();
    
$config['section999']['variable1']=1;
    
$config['section999']['variable2']=2;
    
$config['section999']['variable3']=3;
    
$config['section1000'] = array();
    
$config['section1000']['variable1']=1;
    
$config['section1000']['variable2']=2;
    
$config['section1000']['variable3']=3;
?>

With the config.php file generated, it is now just a similar script as previously show to read the array and extract single variable.


<?php
    
/*** turn on errors ***/
    
error_reporting(E_ALL);

    
/*** start a timer ***/
    
$start microtime(true);

    
/*** include the php file ***/
    
include 'config.php';

    
/*** get a value ***/
    
echo $config['section500']['variable2']."\n";

    
/*** end timer ***/
    
$end microtime(true);

    
/*** show duration ***/
    
echo round$end $start6) . "\n";
?>
2
0.052647

When the script is run, the results are in. Averaging around 0.052640 mark, making this by far the slowest method so far.

The Database

As seen in the first demonstration, the database method of storing configuration settings was perhaps the most convenient, yet paid the greatest penalty in speed. Lets see if the speed performance propogates linearly. A sctipt is provided to generate the required SQL to create the tables to test with.


<?php
    $out 
'CREATE TABLE config_sections (
    config_section_id MEDIUMINT NOT NULL AUTO_INCREMENT,
    config_section_name varchar(20) NOT NULL,
    PRIMARY KEY (config_section_id));'
."\n\n";

    
$out .= 'CREATE TABLE config_options (
    config_option_id MEDIUMINT NOT NULL AUTO_INCREMENT,
    config_option_name varchar(20) NOT NULL,
    config_option_value varchar(20) NOT NULL,
    INDEX (config_option_name),
    config_section_id SMALLINT UNSIGNED NOT NULL REFERENCES cofig_sections(config_section_id),
    PRIMARY KEY (config_option_id));'
."\n\n";

    
$out .= 'INSERT INTO config_sections (config_section_name) VALUES '."\n";
    foreach( 
range(1,1000) as $num )
    {
        
$out .= "('section$num')";
        if( 
$num 1000 )
        {
            
$out.=',';
        }
        else
        {
            
$out.=';';
        }
        
$out .= "\n";
    }


        
$out .= 'INSERT INTO config_options (config_option_name, config_option_value, config_section_id) VALUES '."\n";
        foreach( 
range(11000) as $num )
        {
            for(
$i=1$i<4$i++)
            {
                
$out .= "('variable$i', '$i', '$num')";
                if(
$num==1000 && $i==3)
                {
                    
$out .= ';';
                }
                else
                {
                    
$out.=',';
                }
                
$out.="\n";
            }
        
$i=1;
        }
    
file_put_contents'config.sql'$out);

?>

The above script could use a little clean up, but will serve the need to generate the required SQL to load in the database (MySQL). With the database set up, the config script can be run.


<?php
    
/*** turn on errors ***/
    
error_reporting(E_ALL);

    
/*** start a timer ***/
    
$start microtime(true);

    
/*** connect to database ***/
    
$db = new PDO("mysql:host=localhost;port=3306;dbname=test"'username''password');
    
$stmt $db->prepare("
        SELECT config_option_value
        FROM config_options 
        WHERE config_option_name='variable2'
        AND config_section_id=500"
        
);
    
$stmt->execute();
    
$result $stmt->FetchColumn();

    
/*** get mail username from db results ***/
    
echo $result."\n";

    
/*** end timer ***/
    
$end microtime(true);

    
/*** show duration ***/
    
echo round$end $start6) . "\n";

?>
2
0.014124

With a score of about 0.014184 on average, the database proves to be the fastest method when scaled. The database has much to offer on larger applications, but perhaps is not suited to this type of use in smaller designs.

Conclusions

At a quick glance, each of the above methods has its strengths and weaknesses. Depend on the application, and the developer, each will present a different solution for varying needs. If remote access is required without shell access, then the database option is a good solution. If speed is the primary concern then other options may be better suited.

When put the test of scaling, the results change a little. This is not to say any given type is a better solution for all cases, but developers should keep in mind the changing needs of the applications, or web sites they develop. Could it be possible that future additions will need larger configuration options? This table shows the results from all the tests above. It should be noted, that these tests are indicative only and YMMV given different circumstances.

TypeFirst RunSecond Run
Ini File0.0004210.021381
XML File0.0007170.012310
PHP File0.0004500.052640
Database0.0055330.014184

Credits

Special thanks to Captain James Cook for discovering Australia.