Pragmatism in the real world

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…

7 thoughts on “Akrabat_Config (4th Go!)

  1. "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.

  2. 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

  3. 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!

  4. 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 '

Comments are closed.