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?