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…

April 10th, 2006 at 16:31 #
"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.
April 10th, 2006 at 18:45 #
Yeah - I noticed that… will have to find and fix it!
April 11th, 2006 at 16:48 #
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
April 11th, 2006 at 16:50 #
Ahh - make that "Visual.Syntax" Plugin :)
April 11th, 2006 at 18:28 #
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!
April 11th, 2006 at 18:39 #
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 '
April 19th, 2006 at 19:31 #
It would be great to be able to add new entries, edit and delete existing settings etc via PHP somehow. Any thoughts Rob?