Akrabat_Config (4th Go!)
Ok, I’m dense!
I’ve finally worked out what Nico was saying in that Akrabat_Config doesn’t allow for more than one included section when using the “include=” construct. This is because parse_ini_file() will overwrite keys of the same name.
Thus to support multiple sections the syntax has to be:
include = one,two,three
Akrabat_Config now looks like this:
< ?php
class Akrabat_Config
{
    private $_config;
    /**
     * Load the section $section from the ini file called $filename. If
     * $section is null, then the entire file is loaded.
     *
     * 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.
     *
     * If any key includes a ".", then this will act as a separator to create
     * a sub-array.
     *
     * 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=null, $section=null)
    {
        $this->_config = array();
        if (!is_null($filename)) {
            $this->load($filename, $section);
        }
    }
    /**
     * Load an inifile, overwriting any duplicate keys.
     * If $section is null, then the entire file is loaded.
     *
     * @param string $filename
     * @param string $section
     */
    public function load($filename, $section=null)
    {
        $iniArray = parse_ini_file($filename, true);
        if ($section) {
            if( isset($iniArray[$section])) {
                $this->_config = array_merge($this->_config, $this->processIncludes($iniArray, $section));
            } else {
                throw new Exception("No section '$section' in $filename'");
            }
        }
        else {
            foreach($iniArray as $section=>$value) {
                if (is_array($value)) {
                    if(!isset($this->_config[$section])) {
                        $this->_config[$section] = array();
                    }
                    $this->_config[$section] = array_merge($this->_config[$section], $this->processIncludes($iniArray, $section));
                } else {
                    $this->_config[$section] = $value;
                }
            }
        }
    }
    /**
     * Helper function to process the "include=" inheritance and then
     * process the "dot" single level sub-array syntax in each key.
     *
     * @param array $iniArray
     * @param string $section
     * @return array
     */
    protected function processIncludes($iniArray, $section)
    {
        $config = array();
        foreach ($iniArray[$section] as $key => $value) {
            if ($key == 'include') {
                $sections = explode(',', $value);
                foreach ($sections as $s) {
                    if( isset($iniArray[$s])) {
                        $config = array_merge($config, $this->processLevelsInSection($iniArray[$s]));
                    }
                }
                unset($iniArray[$section][$key]);
            }
        }
        $config = array_merge($config, $this->processLevelsInSection($iniArray[$section]));
        return $config;
    }
    /**
     * Helper function to handle single level namespace in the key
     *
     * @param array $section
     * @return array
     */
    protected function processLevelsInSection($section)
    {
        $config = array();
        foreach ($section as $key=>$value) {
            if (strpos($key, '.')) {
                $pieces = explode('.', $key, 2);
                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);
    }
    /**
     * This is a read only class...
     *
     * @param string $name
     * @param mixed $value
     */
    function __set($name, $value)
    {
        throw new Exception('Akrabat_Config is read only!');
    }
}
?>
Tests:
< ?php
require_once 'PHPUnit2/Framework/TestCase.php';
include "Zend.php";
class ConfigTest extends PHPUnit2_Framework_TestCase
{
    protected $_iniFileConfig;
    protected $_iniFileOne;
    protected $_iniFileTwo;
    function setUp()
    {
        Zend::loadClass('Akrabat_Config');
        $this->_iniFileConfig = dirname(__FILE__).'/data/config.ini';
        $this->_iniFileOne = dirname(__FILE__).'/data/one.ini';
        $this->_iniFileTwo = dirname(__FILE__).'/data/two.ini';
    }
    function tearDown()
    {
    }
    function testLoadSectionAll()
    {
        $config = new Akrabat_Config($this->_iniFileConfig, 'all');
        $this->assertEquals('all', $config->hostname);
        $this->assertEquals('all', $config->test['me']);
    }
    function testSectionInclude()
    {
        $config = new Akrabat_Config($this->_iniFileConfig, 'staging');
        $this->assertEquals('staging', $config->hostname);
        $this->assertEquals('staging', $config->test['me']);
        $this->assertEquals('2', $config->secondVar);
    }
    function testSectionMultiLevels()
    {
        $config = new Akrabat_Config($this->_iniFileConfig, 'multi');
        $this->assertEquals('four', $config->one['two.three']);
        $this->assertEquals('five', $config->one['two.three.four']);
        $this->assertEquals('dotdotthree',$config->one['two..three']);
    }
    function testSectionLeadingDot()
    {
        $config = new Akrabat_Config($this->_iniFileConfig, '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"));
    }
    function testLoadEntireConfigFile()
    {
        $config = new Akrabat_Config($this->_iniFileConfig);
        $this->assertEquals('all', $config->all['hostname']);
        $this->assertEquals('all', $config->all['test']['me']);
        // ensure that the include=all in "staging" works
        $this->assertEquals('avalue', $config->staging['akey']);
        // test multi-level
        $multi = $config->get('multi');
        $this->assertEquals('four', $multi['one']['two.three']);
        // test top level
        $this->assertEquals('1', $config->toplevel);
    }
    function testReadOnly()
    {
        $config = new Akrabat_Config($this->_iniFileConfig);
        try {
            $config->test = '32';
        }
        catch (Exception $expected) {
            return;
        }
        $this->fail('An expected Exception has not been raised.');
    }
    function testSecondFileOverwritesFirst()
    {
        $config = new Akrabat_Config();
        $config->load($this->_iniFileOne);
        $this->assertEquals('one', $config->akey);
        $config->load($this->_iniFileTwo);
        $this->assertEquals('two', $config->akey);
        $this->assertEquals('two', $config->db['hostname']);
        $this->assertEquals('thisDb', $config->db['database']);
    }
}
?>
Ini files required for testing:
config.ini:
toplevel = 1 [all] hostname = all akey = avalue test.me = all [staging] include = all,second 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 [second] secondVar = 2
one.ini:
akey = one [db] hostname = one database = thisDb
two.ini:
akey = two [db] hostname = two
Akrabat_Config is turning into a good learning exercise! Especially as it’s conceptually very very simple…



"one more thing" ;) I tried to copy&paste the code from here, but all the quotation marks were wrong. Seems like WordPress tries to make them pretty.
Yeah – I noticed that… will have to find and fix it!
Hey Rob,
The following code should fix your quotes problem in WordPress:
[code]
add_filter('the_content', 'strip_smart_quotes');
function strip_smart_quotes($data)
{
$data = str_replace(array("'", "'"), "'",$data);
$data = str_replace(array(""", """), '"', $data);
$data = str_replace("″", '"', $data);
$data = str_replace("′", "'", $data);
return $data;
}
[/code]
This is a direct copy from my Wisual.Syntax plugin so should work fine for you.
BTW – thanks for your work on the config class, it is coming along nicely…
Matthew
Ahh – make that "Visual.Syntax" Plugin :)
Hi Matthew!
I'm using a modified version of your Visual.Syntax plugin already. Seems that I commented out the line:
add_filter('the_content', 'strip_smart_quotes');
whilst testing and forgot to reinstate it…
Excellent work btw!
Doesn't seem to work completely though :(
Look at the 32 in the test function testReadOnly()
$config->test = '32?;
Nearly sure that the second quote is not a '
It would be great to be able to add new entries, edit and delete existing settings etc via PHP somehow. Any thoughts Rob?