Zend Framework Hidden Gems: Zend_Config

November 24, 2006

Tutorials, Zend Framework

History of Configuration

Managing configuration is a pretty simple thing to do in PHP, in fact, I’d bet that at some point in their programming career every php programmer has written code that looks something like this:


<?php
$db_host = 'localhost';
$db_name = "joe_db";
$db_user = 'joe';
$db_pass = 'secret_password';
...

?>

This works pretty well for 10 – 100 line scripts, but as soon as you get a bit bigger you realize that you’re going to have trouble accessing your configuration variables, so you decide to refactor your script and do something fancy like this:


<?php
$GLOBALS['config']['db_name'] = 'localhost';
...
?>

That works pretty well, now where ever you are in your script you can access your configuration. You discover PHP5 and start using classes to organize your code. You refactor your code and rid yourself of your include() hell by using PHP5′s magic autoload methods only to find that all your classes rely on the file that includes your configuration data. You now decide that you need to move your configuration data into a class, so you change your code to work like this:


<?php

class Config {
    private $vars = array();

    function __construct(){
       $this->vars['db_host'] = 'localhost'
    }

    function __get($var){
        return $this->vars[$var];
    }

    function __set( $var, $data ){
        $this->vars[$var] = $data;
    }
}

?>

Now your class is flexible, it will fit into your class higharchy, you will be able to fetch arbitrary data from it, you can even update the data in real-time. However, you soon find that you want to separate your configuration data from the business logic which manages your configuration data.

There are many reasons to manage your configuration data indepenantly from your business logic. One common reason is that you don’t want to enter your configuration data into the public versioning system that you use. You may also want to grant developers access to change the business logic on a configuration system without giving them access to change the actual configuration data.

We could continue on with this example, we could subclass this config class, and read in an XML file or INI file and then populate the config class with the data, but then we discover Zend_Config and realize that there is no point in reinventing the wheel.

Using Zend_Config

Zend_Config approaches the concept of managing configuration data with two focuses; Simplicity, and a Driver-based backend. Here is a simple example of managing configuration through Zend_Config.


<?php
require_once 'Zend/Config/Ini.php';
$config = new Zend_Config_Ini('./config.ini', 'database');

echo "
";
echo $config->username . '
'; echo $config->password . '
'; echo $config->port; echo "

";
?>

As in our last article, we are not using the Zend framework class loader. Since we are only using this one class we are reducing the number of files that need to be included. I’m doing it this way so that we can focus on the reusable components of the Zend Framework, not the Zend Framework as a framework.

If you were to use Zend_Config in your own project you could tweak your __autoload() function to automatically load the Zend Framework functions. This is possible because Zend Framework maintains a standard class to filename mapping, so you can easily determine the name of the file in which a class resides simply by converting the _ to your directory separator as seen in the following __autoload() example.


<?php

function __autoload( $foo ){
    if ( substr( $foo, 0, 4) == 'Zend' ){
        require_once strtr($foo, '_', DIRECTORY_SEPARATOR) . ".php";
    }
}

?>

Now we are relying on PHP5 built in functionality to autoload our classes, so we don’t have to worry about those pesky includes.

Back to the Zend_Config example.

To make this example work correctly, we’re going to need a ini file to parse. As explained above Zend_Config can work with any type of storage backend for which a driver exists, ini files are easy to create, so we will be using this for our example.


[database]
username = foo
password = bar
hostname = localhost
port = 555

[cache]
life = 60
cache.backend = Core

So we have our ini file, and when running the above example we see that Zend_Config essentially encapsulates our data storage mechanism into an object. So we can access the configuration variables as if they were simply properties of the $config object.

Now, you will notice that the second parameter of the constructor takes the section of the configuration file to load, if you were to add an array instead of a string you are able to load multiple configuration sections.

Zend_Cache has one more bit of syntactical sugar for our consumption, which I will show in the next example. You will notice in the configuration file example I used the key “cache.backend” instead of simply “backend”. This example will show you why:


<?php
$config = new Zend_Config_Ini('./config.ini', array('database', 'cache'));

echo $config->life;
echo $config->cache->backend;
?>

As you can see Zend_Config translates the ini syntax to the class->properties syntax, enabling us to organize our configuration data into easily manageable blocks.

Inheritance

So now we have covered everything involved with managing our configuration files. We have our site on our staging server and plan to move it to production. Traditionally you would have 2 configuration files, one named something like ‘staging.ini’ and the other one named ‘production.ini’ and all that needs to be done when you move the system onto your production server is to change the name of the configuration file that is being used. This has the downside of having to maintain similar data in both configuration files (for example database type, port number which will probably not change between your servers) to solve this problem, Zend_Config introduces the concept of inheritance in the config files.

Take a look at the following ini file.


[database]
database.username = development_user
database.password = devpassword
database.name = development
database.hostname = localhost
database.port = 555

[staging : database]
database.name = production_database
database.hostname = live.server.com

[production : staging]
database.username = live_production_user
database.password = tricky_password

In this scenario we have three development platforms. Our localhost development system which contains it’s own database, which has a user with full read/write access to the development database. On launch day we branch our development tree and export it to our staging server. At this point we need to have access to the production database so that we can run tests, however we use an unprivilaged user so that if something does go wrong with our testing we can’t do any damage to the live database. If all the tests go as planned, we then move the code to our production server where it uses the database user with full access to the database.

Now instead of having three configuration files for our three scenarios, with the help of Zend_Config, we only need one. The developers of Zend_Config have chosen to extend the traditional ini file format to enable an easy way to specify the inheritance of a section. In this ini file, the staging section inherits from the “database” section, and the “production” section inherits from the “staging” section. This way only the data that needs to be changed in each scenario is changed, and there is no data overlap.

In Conclusion

In future articles we will see Zend_Config in use, and talk about various strategies that can be used when dealing with configuration data. One thing to keep in mind is that while you can dynamically update the data in the Zend_Config object that you’ve created, there is no way of saving this data back to the datasource.

Another weakness in Zend_Config is the lack of configuration container backends. This is in comparison to mature packages like Config from PEAR which includes in read/write container backends for arrays, apache config files, php defines etc. However, seeing that Zend Framework hasn’t even reached version 1.0 yet, I’m sure we’ll see a lot of progress made on this front.

Coming up next in the series is the first in our multi-part series of articles on Zend_DB and friends.

,

9 Responses to “Zend Framework Hidden Gems: Zend_Config”

  1. radicalnut Says:

    @vdensity

    Once you have your Zend_Config_Ini object, you can store it in Zend_Registry to make it available later on.

    e.g.
    Zend_Registry::set(‘config’, $config);

  2. vdensity Says:

    Hi,

    Thank you very much for an excellent article. I have one question: Is there a way of caching the ini file after it is loaded by Zend_Config_Ini so that it doesn’t need to parse it again?

    Thanks.

  3. zareef Says:

    @koriolis
    There’s more than one way to do it.

    IMHO, You have a point but article was suggesting an option for non-repeatation of variables which are common for every deployment.

  4. koriolis Says:

    Actually instead of hard-coding a "config.ini" file, you could define a domain named ini file. Ex: my-domain.com.ini, localhost.ini, etc.

    Gives you much more flexibility in defining parameters for different servers (production, testing, developing, etc).

  5. _____anonymous_____ Says:

    > $config = new Zend_Config_Ini(‘./config.ini’, ‘database’);

    > Obviously when moving to staging and then production
    > I would need to change the ‘database’ constant accordingly.
    > Is there a way of detecting the environment?

    I will usually switch on either http_host, or the machine name.
    <pre>
    switch (php_uname(‘n’)) {
    case ‘web1.website.com’:
    $configName = ‘production’;
    break;
    default:
    $configName = php_uname(‘n’);
    break;
    }
    if (‘dev.website.com’ == $_SERVER['HTTP_HOST'])
    $configName = ‘dev’;

    $config = new Zend_Config_Ini(INCLUDES.’/config.ini’, $configName);
    </pre>

    and the config will look like something like this, with the dev section overriding parts of the production information
    <pre>
    [general]
    APC.use = true
    ;###########################################
    [production : general]

    api.GoogleMapAPIKey=abc

    db.adapter = mysqli
    db.config.host = 192.168.1.128
    db.config.username = username
    db.config.password = secret
    db.config.dbname = mainDB

    ;# don’t profile in production
    db.profile = false

    ###########################################
    [dev : production]

    db.profile = true

    db.config.host = 127.0.0.1
    db.config.username = username123
    db.config.password = secret4533
    </pre>

    There is more about the inheritance with Zend_Config at http://framework.zend.com/manual/en/zend.config.adapters.ini.html

  6. jbailey Says:

    Your example covers the problem that I’m having with the Zend::Config class:

    function __construct(){
    $this->vars['db_host'] = ‘localhost’
    }

    You have this in the previous example, but it seems like I lose the ability to have defaults if I switch to the Config class, rather than a homegrown one.

    I want the user to only have to specify certain things (like the name of the database, etc.) but that if the host is a sensible default like localhost, it shouldn’t need to be specified in the accompanying config file.

  7. uplift Says:

    Hi,

    Thanks for a great article.

    One question though… How would you detect which environment you are in to load the appropriate section of the config file. I refer to the line:

    $config = new Zend_Config_Ini(‘./config.ini’, ‘database’);

    Obviously when moving to staging and then production I would need to change the ‘database’ constant accordingly. Is there a way of detecting the environment?

    Or am I being stupid?!

  8. aaronwormus Says:

    Thanks for the tip Matthew. In my work with the Zend Framework, I have been using the individual ZF classes simply as "utility classes" which work alongside my existing code. This is why I haven’t gotten into the "Framework" part Zend Framework, but there isn’t any point re-inventing the wheel when it comes to the autoload, so I’ll cover the Zend.php in the next segment.

    Thanks again.

  9. weierophinney Says:

    There’s a more concise way to do autoloading using the framework:

    function __autoload($class)
    {
        Zend::loadClass($class);
    }
    

    (assuming, of course, that the Zend class has been included already.) The
    beauty of this is that because ZF uses the same naming conventions as PEAR,
    Solar, and other PEAR-CS compliant libraries, this single autoload will work for
    any of them.