<?php
class Config{
protected
$_level = 0,
$_parent = null,
$_data = array(),
$_output = null;
public function __construct(array $array, $level = 0, $parent = null){
$this->_level = $level;
$this->_parent = $parent;
foreach($array as $key => $value)
$this->_data[$key] = is_array($value) ? new self($value, $level + 1, $this) : $value;
}
public static function load($file){
$config = new self(parse_ini_file($file, true));
$config->setOutput($file);
return $config;
}
public function setOutput($file){
$this->_output = $file;
}
public function __get($name){
if($this->_level > 1)
throw new Exception('Unsupported nesting level');
if(array_key_exists($name, $this->_data))
return $this->_data[$name];
if($this->_level < 1){
$this->_data[$name] = new self(array(), $this->_level + 1, $this);
return $this->_data[$name];
}
throw new Exception('Invalid request');
}
public function __set($name, $value){
if($this->_level > 1)
throw new Exception('Unsupported nesting level');
elseif($this->_level < 1)
throw new Exception('You cannot assign values to sections');
if(!isset($this->_data[$name]) || ($this->_data[$name] !== $value)){
$this->_data[$name] = is_array($value) ? new self($value, $this->_level + 1, $this) : $value;
($this->_level > 0 && isset($this->_parent)) ? $this->_parent->write() : $this->write();
}
}
public function __isset($name){
return isset($this->_data[$name]);
}
public function __clone(){
$array = array();
foreach($this->_data as $key => $value)
$array[$key] = ($value instanceof Config) ? clone $value : $value;
$this->_data = $array;
}
public function __unset($name){
unset($this->_data[$name]);
}
// from Zend
public function toArray(){
$array = array();
$data = $this->_data;
foreach($data as $key => $value)
$array[$key] = ($value instanceof Config) ? $value->toArray() : $value;
return $array;
}
// from Zend
public function merge(Config $merge){
foreach($merge as $key => $item){
if(array_key_exists($key, $this->_data))
$this->$key = ($item instanceof Config && $this->$key instanceof Config) ? $this->$key->merge(new self($item->toArray(), $this->_level + 1)) : $item;
else
$this->$key = ($item instanceof Config) ? new self($item->toArray(), $this->_level + 1) : $item;
}
return $this;
}
public function write($exclusive_lock = true){
if(empty($this->_output))
throw new Exception('You need to set an output location before write');
$flags = 0;
if($exclusive_lock)
$flags |= LOCK_EX;
$result = @file_put_contents($this->_output, $this->__toString(), $flags);
if($result === false)
throw new Exception('Could not write configuration to file "'.$this->_output.'"');
return true;
}
public function __toString(){
$meta = $temp_meta = array();
if(isset($this->_output)){
$section = $last_key = '';
foreach(file($this->_output) as $line){
// extra stuff (comments, new lines etc.)
if(preg_match('/^\s*(;.*)?$/', $line)){
// extra stuff (comments, new lines etc.)
$temp_meta[] = $line;
// section
}elseif(preg_match('/\[(.*)\]/', $line, $match)){
$section = $match[1];
// add any meta data we got up to this point
$meta[$section]['_before'] = implode('', $temp_meta);
$temp_meta = array();
// entry
}elseif(preg_match('/^\s*(.*?)\s*=\s*(.*?)\s*$/', $line, $match)){
$last_key = $match[1];
// if we have recorded meta so far, it means it needs to be placed before an entry
if($temp_meta){
$meta[$section][$last_key] = implode('', $temp_meta);
$temp_meta = array();
}
}
}
}
$ini_string = '';
$config = $this->toArray();
if($this->_level < 1){
foreach($config as $group => $keys){
if(isset($meta[$group]['_before']))
$ini_string .= $meta[$group]['_before'];
$ini_string .= '['.$group.']'."\n";
$max_length = $keys ? max(array_map('strlen', array_keys($keys))) : 0;
foreach($keys as $key => $value){
if(isset($meta[$group][$key]))
$ini_string .= $meta[$group][$key];
$ini_string .= str_pad($key, $max_length).' = '.$this->_prepareValue($value)."\n";
}
}
}else{
$max_length = max(array_map('strlen', array_keys($config)));
foreach($config as $key => $value)
$ini_string .= str_pad($key, $max_length).' = '.$this->_prepareValue($value)."\n";
}
return $ini_string;
}
protected function _prepareValue($value){
if(is_integer($value) || is_float($value))
return $value;
elseif(is_bool($value))
return ($value ? 'true' : 'false');
elseif(strpos($value, '"') === false)
return '"'.$value.'"';
throw new Exception('Value can not contain double quotes');
}
}
$config = Config::load('file.ini');
print $config;
// add new branch and entry
$config->foo->setting1 = 'abc';
$config->foo->setting2 = 'def';