MVC in PHP part 2: Config files
In part 1 of this series we successfully set up our environment and took the first steps in writing a custom framework with the model-view-controller system in PHP. Right now, we will take a look at static configuration files to provide certain settings for the framework to work with.
The need for configuration
As said in the previous article, we will need the configuration files to further expand our models, views and controllers alike. Although I'm a big fan of the convention over configuration concept in Ruby on Rails, there are always a few things that need configuration (like database name and credentials) and some settings that, for the sake of flexibility across multiple implementations of your framework, are better kept outside of the code.
In future articles we will come across more parameters that might need a separate entry in the configuration files, but for now let's just store our database credentials and configuration in a file and figure out a good way to extract those settings and use them to create a basic database connection.
3 times a lady
There are several ways to store configuration parameters for our web application:
- A table in your database
- An XML configuration file
- An INI configuration file
Since our goal here is to store the database configuration, it doesn't need a rocket scientist to figure out that a table in our database is not exactly the optimal solution. That leaves us with either an XML or an INI file. For the sake of simplicity, and to avoid the rather complicated DomDocument object, we will use an INI file. These files are both easy to create and parse while offering all the features and flexibility we need.
Creating the INI file
We will start by creating the INI file in the root directory of application. For convenience, we will call this file config.ini. Take a look at the previous article in this series if you need a recap on the file- and folder structure we were using.
INI files have a well-defined structure. The file is divided into sections and each section has its own set of key-value pairs. A section is defined by its name between square brackets. Key-value pairs within a section and assigned using a single equal sign. As always, an example says more than a thousand descriptions.
[database] username = someusername password = somepassword hostname = localhost database = somedatabasename driver = mysql
Parsing the data
PHP has the simple but effective parse_ini_file function that takes the filename (as a string) and an optional boolean to process the sections as parameters. This function returns an array with the section name as the key and another array with key-value pairs as the value. This is how the array would look if we were to parse our configuration file:
array ( [database] => array ( [username] => someusername [password] => somepassword [hostname] => localhost [database] => somedatabasename [driver] => mysql` ) )
Nifty object oriented programming
Since the goal of this series is to create clean and re-usable code, we will create a class to parse and store the configuration file. Afterwards, this class can be used to retrieve the sections and parameters.
class Config {
private $data = array();
private $filename;
private $path;
public function __construct($filename = "config.ini",$process_sections = true) {
$this->path = $_SERVER["DOCUMENT_ROOT"];
$this->data = parse_ini_file($this->path.'/'.$filename,$process_sections);
}
public function getParam($section,$key = null) {
if (array_key_exists($section,$this->data)) {
if ($key && array_key_exists($key,$this->data[$section])) {
// return the the value for $key in the current $section
return $this->data[$section][$key];
} else {
// return the entire section as an array
return $this->data[$section];
}
} else {
return false;
}
}
}
We will use the constructor-method to parse the configuration file when the object is created. Afterwards we can retrieve entire sections or specific parameters from the object by using the getParam method. Since our configuration file hold the database configuration parameters, the ideal way to demonstrate the usage of this class would be to make a database connection. To handle databases in a PHP5 framework, I strongly suggest using the PDO object, but we will first demonstrate the usage of the Config class with the very straightforward mysql_connect and mysql_select_db functions .
Using mysql_connect()
$config = new Config();
$user = $config->getParam('database','username');
$pass = $config->getParam('database','password');
$host = $config->getParam('database','hostname');
$name = $config->getParam('database','database');
$connection = mysql_connect($host,$user,$pass);
mysql_select_db($name,$connection);
Using PDO
$config = new Config();
$user = $config->getParam('database','username');
$pass = $config->getParam('database','password');
$host = $config->getParam('database','hostname');
$name = $config->getParam('database','database');
$driver = $config->getParam('database','driver');
$connection = new PDO ("$driver:dbname=$name;host=$host", $user, $pass);
As we can see in the two code examples above, the usage of our Config class is not at all related to the way we connect to the database. This is one of the major benefits of good object oriented design and we will encounter more of these situations along the way.
Just to make things clear, don't use the code above to make database connections. The focus of this article is on the Config class and nothing more. One of the upcoming articles will be completely devoted to handling databases, where we will have an in-depth look on how to connect to databases using proper code and practices. For now, just remember the way we handled the configuration parameters.
Expand for flexibility
Earlier in this article I briefly hinted the possibility of using an XML configuration file instead of the INI file. In the next article we will make an abstraction our Config class and create subclasses for the INI and XML parsing so that we can use both in the same way. My suggestion for now? Read a bit about the DomDocument object, because we're going to parse an XML configuration file.
Where do we go now?
You can browse through the recent articles or go to the archive for older items.
There are 11 comments for this article
Thomas on Dec 2, 2008
Since we're going to store passwords and other possible sensitive data in the config file, it might be good practice to make sure no one can access that config file and add the following in .htaccess:
<Files config.ini>
Deny from all
</Files>
Imagine what would happen if mod_rewrite is disabled or if you've added the ini extension to the rewrite rule as stated in the previous chapter.
david on Dec 2, 2008
Why not use a regular array, then you don't need to parse the file and to keep the syntactic sugar you can use the file name.
Jonas on Dec 2, 2008
I agree that a config file with a defined array can be used to store your configuration. It's both a safe and parse-free solution.
Nonetheless, it's just ONE way to store configuration settings. We could easily write multiple subclasses and an abstracted main Config class (which is exactly what is planned for the next article). I only had the ini and xml subclasses in mind, but it's perfectly feasible to create a third subclass that handles a configuration array defined in, let's say, config.php
Also, keep in mind that we're not writing a single application, but a framework that can handle various problems and situations. Imagine, for instance, that you want to include an existing php module or script in an application that you are building with your framework. If the module or script is configured with an ini or xml file, wouldn't it be nice to just use your own config-object to read, change and write the settings for the included module?
On top of that, I really don't like too many square brackets in my code, but that's more of a personal issue :)
Dave on Dec 2, 2008
Your link to "parse_ini_file" points to mysql_connect - may wanna correct that ;)
Personally I use XML files and keep the config files outside of the web root and then have a .htaccess rule blocking access to .inc, .ini and .xml files; but whatever works :)
Jonas on Dec 2, 2008
Dave,
Thanks for pointing out the wrong link. Somehow, a mistake like that always slips in.
As I said, XML files are a really nice alternative to INI files, but in an effort not to make this article utterly complicated, I wanted to avoid XML parsing for now.
Keeping the config files outside of the web root, however, is something I used to do a lot to increase security, but I decided to move them back inside for compatibility. A lot of shared hosting solutions just don't allow their users to store files outside of the web root. It's no big deal to change the $path variable of course, it's there for a reason ...
Hasanga on Dec 20, 2008
Looking forward to the next chapter \m/
Jonas on Dec 22, 2008
I'm nearly done with part 3, but recent events at work and some personal activities have kept me rather busy. Expect something this week though !
Jan on Jan 26, 2009
So when can we expect part 3 :)
Marvin on Mar 12, 2009
I would say this article can be titled as nearly the best practices for storing db-credentials in php applications..! :-)
Øyvind on Nov 18, 2009
How about: 'localhost', 'dbname' => 'test', 'dbuser' => 'root', 'dbpass' => '' ); ?> Put the above in config.php and in your application put: print_r(require('config.php'));
(well, don't use the print_r in your application, it's just to illustrate that an array is returned from the require function)
The benefit of this is that, as far as I know, the contents of the file won't be sent to the browser if someone opens config.php with their browser. (I've only tested this on a couple of servers, but there's no echo or print statement so nothing should be sent to the browser).
Another benefit is that you don't have to parse the file (which is a tiny, tiny performance boost).
Yet another benefit is that you don't need to declare any global variables when doing it this way (don't need them in the ini-example either)
Øyvind on Nov 18, 2009
The first comment came out a little badly formatted.. There's missing a < ? php return array (