Akrabat_Config (2nd Attempt)

Update! This version has been superceded! Check out Akrabat_Config (Take Three!) for an even better version...

Nico Edtinger was kind enough to review Akrabat_Config and pointed out that it wouldn't handle a key with multiple dots in it. His post to fw-general:

One method I don't "like" is processSection(). If I have a name like "foo.bar.baz" it would be saved as $config['foo']['bar'], dropping the last part because you explode without a third parameter. It should be
$pieces = explode('.', $key, 2);

I'd also change the check for a dot to
if(strpos($key, '.')) {

That would work with a key with a leading dot (being stored just as is instead of $config[''][...]) and the explode doesn't need to parse a string that doesn't have a dot anyway and create an array that's not needed.

Thus, this is my second attempt at Akrabat_Config:

The new tests are:

<?php
require_once 'PHPUnit2/Framework/TestCase.php';
include "Zend.php";

class ConfigTest extends PHPUnit2_Framework_TestCase
{
    protected $iniFilename;
    function setUp()
    {
        $this->iniFilename dirname(__FILE__).'/data/config.ini';
    }

    function testLoadAll()
    {
        Zend::loadClass('Akrabat_Config');
        $config = new Akrabat_Config($this->iniFilename'all');
        $this->assertEquals('all'$config->hostname);
        $this->assertEquals('all'$config->test['me']);
    }

    function testInclude()
    {
        Zend::loadClass('Akrabat_Config');
        $config = new Akrabat_Config($this->iniFilename'staging');
        $this->assertEquals('staging'$config->hostname);
        $this->assertEquals('staging'$config->test['me']);
    }

    function testMultiLevels()
    {
        Zend::loadClass('Akrabat_Config');
        $config = new Akrabat_Config($this->iniFilename'multi');
        zend::dump($config);
        $this->assertEquals('four'$config->one['two.three']);
        $this->assertEquals('five'$config->one['two.three.four']);
    }

    function testLeadingDot()
    {
        Zend::loadClass('Akrabat_Config');
        $config = new Akrabat_Config($this->iniFilename'dot');
        $this->assertEquals('dot'$config->get("."));
        $this->assertEquals('doubledot'$config->get(".."));
        $this->assertEquals('t-dot'$config->get("t."));
        $this->assertEquals('dot-t'$config->get(".t"));
    }
}

?>

with data/config.ini:

[all]
hostname = all
test.me = all

[staging]
include = all
hostname = staging
test.me = staging

[multi]
one.two.three = four
one.two.three.four = five
one.two..three = dotdotthree

[dot]
. = dot
.. = doubledot
t. = t-dot
.t = dot-t

and Akrabat_Config looks like this:

<?php

class Akrabat_Config
{
    private $_config;

    /**
     * Load the section $section from the ini file called $filename.
     * If any keys with $section are called "include", then the section
     * pointed to by the "include" is then included first. Thus, the keys
     * in $section will override any keys of the same name in the sections
     * that have been "include"ed.
     *
     * example ini file:
     *      [all]
     *      db.connection = database
     *      hostname = live
     *
     *      [staging]
     *      include = all
     *      hostname = staging
     *
     * after callgin $config = new Akrabat_Config($file, 'staging'); then
     *      $config->hostname = staging
     *      $config->db['connection'] = database
     *
     *
     * @param string $filename
     * @param string $section
     */
    function __construct($filename$section)
    {
        $iniArray parse_ini_file($filenametrue);

        $config = array();
        if(isset($iniArray[$section]))
        {
            foreach($iniArray[$section] as $key => $value)
            {
                if($key == 'include')
                {
                    if(isset($iniArray[$value]))
                    {
                        $config array_merge($config$this->processSection($iniArray[$value]));
                    }
                    unset($iniArray[$section][$key]);

                }
            }
            $config array_merge($config$this->processSection($iniArray[$section]));
        }
        else
        {
            throw new Exception("No section '$section' in $filename'");
        }
        $this->_config $config;
    }

    /**
     * Helper function to handle single level namespace in the key
     *
     * @param array $section
     * @return array
     */
    protected function processSection($section)
    {
        $config = array();
        foreach($section as $key=>$value)
        {
            if(strpos($key'.'))
            {
                $pieces explode('.'$key2);
                if(!empty($pieces[1]))
                {
                    $config[$pieces[0]][$pieces[1]] = $value;
                }
                else
                {
                    $config[$key] = $value;
                }
            }
            else
            {
                $config[$key] = $value;
            }

        }
        return $config;
    }

    /**
     * @param string $name
     * @param mixed $default
     * @return mixed
     */
    function get($name$default=false)
    {
        $result $default;
        if(isset($this->_config[$name]))
        {
            $result $this->_config[$name];
        }
        return $result;
    }

    /**
     * magic function so that $config->value will work.
     *
     * @param string $name
     * @return mixed
     */
    function __get($name)
    {
        return $this->get($name);
    }

}
?>

If you would like to comment on this article, please ping me on twitter.
If your response won't fit into 140 characters, write a blog post and then ping me!