This article illustrates several ways to create a configurable PHP application. The ideal configuration point in the application is also discussed and a balance is sought between the application being overly configurable and overly closed.
If you plan to allow others or companies to use your PHP application, you need to make sure that the program is configurable. At the very least, allow the user to set the database login and password in a secure manner so that the material in it is not exposed to the outside world.
This article shows several techniques for storing configuration settings and editing these settings. In addition, the article also provides guidance on which elements need to be configurable and how to avoid the dilemma of excessive or inadequate configuration.
Configuring using the INI file
PHP has built-in support for configuration files. This is done through an initialization file (INI) mechanism such as the php.ini file, where constants such as database connection timeout or how the session is stored are defined in the php.ini file. If you want, you can customize the configuration for your application in this php.ini file. To illustrate, I add the following line of code to the php.ini file.
Myapptempdir=foo
I then wrote a small PHP script to read this configuration item, as shown in Listing 1.
Listing 1. ini1.php
<?php
function Get_template_directory ()
{
$v = Get_cfg_var ("Myapptempdir");
return ($v = = null)? "TempDir": $v;
}
Echo (Get_template_directory (). " \ n ");
? >
When you run this code on the command line, you get the following result:
% PHP ini1.php
Foo
%
That's great. But why not use the standard INI function to get the value of the Myapptempdir configuration item? I studied it and found that in most cases, custom configuration items could not be used to obtain them. However, the use of the Get_cfg_var function can be accessed.
To make this method simpler, the access to the variable is encapsulated in the second function, which uses the configuration key name and a default value as the parameter, as shown below.
Listing 2. ini2.php
function Get_ini_value ($n, $DV)
{
$c = Get_cfg_var ($n);
return ($c = = null)? $DV: $c;
}
function Get_template_directory ()
{
Return Get_ini_value ("Myapptempdir", "TempDir");
}
This is a good generalization of how to access the INI file, so if you want to use a different mechanism or store the INI file in another location, you don't have to bother to change a large number of functions.
I do not recommend using the INI file as an application configuration, for two reasons. First, although it is easier to read the INI file, it is almost impossible to write the INI file securely. So this is only appropriate for read-only configuration items. Second, the php.ini file isServeris shared on all applications, so I think the application-specific configuration item should not be written in that file.
What do I need to know about the INI file? The most important thing is how to reset the include path to add a configuration entry, as shown below.
Listing 3. ini3.php
<?php
Echo (Ini_get ("Include_path"). " \ n ");
Ini_set ("Include_path",
Ini_get ("Include_path"). ":./mylib");
Echo (Ini_get ("Include_path"). " \ n ");
? >
In this case, I added my local mylib directory to the include path, so I was able to require the PHP file from the directory without having to add the path to the Require statement.
the configuration in PHP
A common alternative to storing configuration entries in the INI file is to use a simple PHP script to keep the data. The following is a sample.
Listing 4. config.php
<?php
# Specify the location of the temporary directory
#
$TEMPLATE _directory = "TempDir";
? >
The code that uses the constant is shown below.
Listing 5. php.php
<?php
Require_once ' config.php ';
function Get_template_directory ()
{
Global $TEMPLATE _directory;
return $TEMPLATE _directory;
}
Echo (Get_template_directory (). " \ n ");
? >
The code first contains the configuration file (config.php), and then you can use these constants directly.
There are many advantages to using this technique. First, if someone simply browses to the config.php file, the page is blank. So you can put config.php in the same file and act as the root of your WEB application. Second, it is editable in any editor and even has syntax coloring and grammar checking in some editors.
The disadvantage of this technique is that it is a read-only technique like the INI file. Extracting data from this file is easy, but adjusting the data in the PHP file is difficult and, in some cases, even impossible.
The following workaround shows how to write a configuration system that is both readable and writable in nature.
text File
The previous two examples are appropriate for read-only configuration entries, but what about both read and write configuration parameters? First, look at the text configuration file in Listing 6.
Listing 6. Config.txt
# My application ' s configuration file
Title=my App
Templatedirectory=tempdir
This is the same file format as the INI file, but I wrote the auxiliary tools myself. To do this, I created my own Configuration class, as shown below.
Listing 7. text1.php
<?php
Class Configuration
{
Private $configFile = ' config.txt ';
Private $items = Array ();
function __construct () {$this->parse ();}
function __get ($id) {return $this->items[$id];}
Function Parse ()
{
$fh = fopen ($this->configfile, ' R ');
while ($l = fgets ($FH))
{
if (Preg_match ('/^#/', $l) = = False)
{
Preg_match ('/^ (. *?) =(.*?) $/', $l, $found);
$this->items[$found [1]] = $found [2];
}
}
Fclose ($FH);
}
}
$c = new Configuration ();
Echo ($c->templatedirectory.) \ n ");
? >
The code first creates a Configuration object. The constructor then reads the Config.txt and sets the local variable $items with the parsed file contents.
The script then looks for templatedirectory, which is not directly defined in the object. Therefore, using the $id set to ' Templatedirectory ' to invoke the Magic __get method, the __get method returns the value of $items array for that key.
This __get method is specific to the PHP V5 environment, so this script must run under PHP V5. In fact, all the scripts in this article need to run under PHP V5.
When you run this script at the command line, you see the following results:
% PHP text1.php
TempDir
%
Everything is expected, the object reads the Config.txt file, and then gets the correct value for the Templatedirectory configuration item.
But what do you do with setting a configuration value? Creating a new method and some new test code in this class will enable you to get this functionality, as shown below.
Listing 8. text2.php
<?php
Class Configuration
{
...
function __get ($id) {return $this->items[$id];}
function __set ($id, $v) {$this->items[$id] = $v;}
Function Parse () {...}
}
$c = new Configuration ();
Echo ($c->templatedirectory.) \ n ");
$c->templatedirectory = ' foobar ';
Echo ($c->templatedirectory.) \ n ");
? >
Now, with a __set function, it is the "cousin" of the __get function. The function does not get a value for a member variable, which is called when you want to set a member variable. The test code at the bottom sets the value and prints out the new value.
Here's what happens when you run this code on the command line:
% PHP text2.php
TempDir
Foobar
%
That's great! But how can you store it in a file so that the changes are fixed? To do this, you need to write a file and read it. A new function for writing a file, as shown below.
Listing 9. text3.php
<?php
Class Configuration
{
...
function Save ()
{
$NF = ';
$fh = fopen ($this->configfile, ' R ');
while ($l = fgets ($FH))
{
if (Preg_match ('/^#/', $l) = = False)
{
Preg_match ('/^ (. *?) =(.*?) $/', $l, $found);
$nf. = $found [1]. " = ". $this->items[$found [1]]." \ n ";
}
Else
{
$nf. = $l;
}
}
Fclose ($FH);
Copy ($this->configfile, $this->configfile. Bak ');
$fh = fopen ($this->configfile, ' W ');
Fwrite ($FH, $NF);
Fclose ($FH);
}
}
$c = new Configuration ();
Echo ($c->templatedirectory.) \ n ");
$c->templatedirectory = ' foobar ';
Echo ($c->templatedirectory.) \ n ");
$c->save ();
? >
The new Save function subtly operates config.txt. Instead of rewriting the file with updated configuration items (which removes annotations), I read the file and flexibly rewrote the contents of the $items array. In this case, the comments in the file are preserved.
Run the script at the command line and output the contents of the text configuration file to see the following output.
Listing 10. Save function output
% PHP text3.php
TempDir
Foobar
% Cat Config.txt
# My application ' s configuration file
Title=my App
Templatedirectory=foobar
%
The original Config.txt file is now updated with the new value.
XML configuration file
Although text files are easy to read and edit, they are not as popular as XML files. In addition, XML has a number of applicable editors that can understand tags, special symbol escapes, and so on. So what will the XML version of the configuration file look like? Listing 11 shows the XML-formatted configuration file.
Listing 11. Config.xml
<?xml version= "1.0"? > >
<config>
<Title> my App </Title>
<TemplateDirectory> TempDir </TemplateDirectory>
</config>
Listing 12 shows an updated version of the Configuration class that uses XML to mount configuration settings.
Listing 12. xml1.php
<?php
Class Configuration
{
Private $configFile = ' config.xml ';
Private $items = Array ();
function __construct () {$this->parse ();}
function __get ($id) {return $this->items[$id];}
Function Parse ()
{
$doc = new DOMDocument ();
$doc->load ($this->configfile);
$CN = $doc->getelementsbytagname ("config");
$nodes = $CN->item (0)->getelementsbytagname ("*");
foreach ($nodes as $node)
$this->items[$node->nodename] = $node->nodevalue;
}
}
$c = new Configuration ();
Echo ($c->templatedirectory.) \ n ");
? >
It seems that XML has another benefit: the code is simpler and easier than the text version of the code. To save this XML, another version of the Save function is required to save the result in XML format instead of text format.
Listing 13. xml2.php
...
function Save ()
{
$doc = new DOMDocument ();
$doc->formatoutput = true;
$r = $doc->createelement ("config");
$doc->appendchild ($R);
foreach ($this->items as $k => $v)
{
$KN = $doc->createelement ($k);
$kn->appendchild ($doc->createtextnode ($v));
$r->appendchild ($KN);
}
Copy ($this->configfile, $this->configfile. Bak ');
$doc->save ($this->configfile);
}
...
This code creates a new XML Document Object model (Document Object model, DOM) and then saves all the data in the $items array to this model. When you are done, use the Save method to save the XML as a file.
Working with Databases
The final alternative is to use a database to hold the value of the configuration element. The first thing to do is to store the configuration data in a simple pattern. The following is a simple pattern.
Listing 14. Schema.sql
DROP TABLE IF EXISTS settings;
CREATE TABLE Settings (
ID mediumint not NULL auto_increment,
Name TEXT,
Value TEXT,
PRIMARY KEY (ID)
);
This requires a number of adjustments based on application requirements. For example, if you want a configuration element to be stored per user, you need to add a user ID as an additional column.
To read and write the data, I wrote the updated Configuration class as shown in Figure 15.
Listing 15. db1.php
<?php
Require_once (' db.php ');
$dsn = ' mysql://root:password@localhost/config ';
$db =& db::connect ($DSN, Array ());
if (Pear::iserror ($db)) {die ($db->getmessage ());}
Class Configuration
{
Private $configFile = ' config.xml ';
Private $items = Array ();
function __construct () {$this->parse ();}
function __get ($id) {return $this->items[$id];}
function __set ($id, $v)
{
Global $db;
$this->items[$id] = $v;
$sth 1 = $db->prepare (' DELETE from Settings WHERE name=? ');
$db->execute ($sth 1, $id);
if (Pear::iserror ($db)) {die ($db->getmessage ());}
$sth 2 = $db->prepare (' INSERT into settings (ID, name, value) VALUES (0,?,?) ');
$db->execute ($sth 2, Array ($id, $v));
if (Pear::iserror ($db)) {die ($db->getmessage ());}
}
Function Parse ()
{
Global $db;
$doc = new DOMDocument ();
$doc->load ($this->configfile);
$CN = $doc->getelementsbytagname ("config");
$nodes = $CN->item (0)->getelementsbytagname ("*");
foreach ($nodes as $node)
$this->items[$node->nodename] = $node->nodevalue;
$res = $db->query (' SELECT name,value from Settings ');
if (Pear::iserror ($db)) {die ($db->getmessage ());}
while ($res->fetchinto ($row)) {
$this->items[$row [0]] = $row [1];
}
}
}
$c = new Configuration ();
Echo ($c->templatedirectory.) \ n ");
$c->templatedirectory = ' new Foo ';
Echo ($c->templatedirectory.) \ n ");
? >
This is actually a mixed text/database solution. Please observe the parse method carefully. The class first reads the text file to get the initial value, then reads the database, and then updates the key to the most recent value. After you set a value, the key is removed from the database and a new record with the updated value is added.
It is interesting to see how the Configuration class works through multiple versions of this article, which reads data from text files, XML, and databases, and maintains the same interface all the time. I encourage you to also use interfaces with the same stability in development. It is not clear how this work works specifically for the clients of the object. The key is the contract between the object and the client.
What is configuration and how to configure it
It is difficult to find an appropriate middle point between configuring too many configuration options and insufficient configuration. To be sure, any database configuration (for example, database name, database user, and password) should be configurable. In addition, I have some basic recommended configuration items.
In advanced settings, each feature should have a separate enable/disable option. These options are allowed or disabled based on their importance to the application. For example, in a WEB forum application, the latency feature is enabled by default. However, email notifications are disabled by default, because this seems to require customization.
The user interface (UI) options should all be set to one location. The structure of the interface (for example, menu location, additional menu items, URLs that are linked to specific elements of the interface, logos used, and so on) should all be set to a single location. I strongly recommend that you do not specify a font, color, or style entry as a configuration item. These should be set by cascading style sheets (cascading style sheets,css), and the configuration system should specify which CSS file to use. CSS is an efficient and flexible way to set fonts, styles, colors, and so on. With many excellent CSS tools, your application should make good use of CSS rather than trying to set standards yourself.
In each feature, I recommend setting 3 to 10 configuration options. These configuration options should be named in a way that is clearly meaningful. If the configuration options are available through the UI settings, the option names in text files, XML files, and databases should be directly related to the title of the interface element. In addition, all of these options should have a clear default value.
In general, the following options should be configurable: the e-mail address, what the CSS uses, the location of the system resources referenced from the file, and the file name of the graphical element.
For graphical elements, you might want to create a separate profile type named skin that contains settings for the configuration file, including the location of the CSS file, the location of the graphic, and what these types of things are. Then, let the user choose among a variety of skin files. This makes large-scale changes to the look and feel of the application easier. This also provides an opportunity for the user to change the skin between different product installations. This article does not cover these skin files, but the basics you have learned here will make support for skin files easier.
Concluding remarks
Configurable is a critical part of any PHP application and should be the central part of the design at the outset. I hope this article will help you implement the configuration architecture and provide guidance on what configuration options should be allowed.