Hierarchical (tree-like) map with INI file support in PHP

The Problem

We would like to be able to parse INI files into an easy to use structure. Ini files are organized into sections and each section contains key-value assignments. Example INI file:

; This is a comment line and will not be
; interpretted.
[section1]
key=value
key2=value2

[section2]
key=value

Here are some common rules that apply to INI files:

  1. Empty lines and lines starting with ';' are not interpretted.
  2. Each section starts with a line that contains only the section name in square brackets ([]).
  3. A section ends with a new section start or the end of the file.
  4. A line containing expression “keyName=assignValue” is a key definition that assigns a value assignValue to a key keyName
  5. All key definitions belong to a section.
  6. Characters {}|&~![()” cannot be used anywhere in the key and have a special meaning in the value.
  7. There are reserved words which must not be used as keys for ini files. These include: null, yes, no, true, and false.
  8. Value null results in a NULL, false and no result in boolean FALSE, yes and true results in a boolean TRUE.
  9. We can retrieve values from the map by specifying a section name and a key name.
  10. Unique keys. This means that each key can be assigned only one value.

We will introduce additional rules:

  1. Keys that are defined outside a section, belong to a global section with an empty name.
  2. Keys can be hierarchical, e.g. level1/level2/level3. The full key name (absolute key) is actually the section name plus the key name separated by '/' character, e.g. section/key/subkey.

NOTE: Please note that the key 'section/key' defined in global section and 'key' defined in a section 'section' are not the same. The absolute key in the first case is '/section/key' whilst in the second case it is 'section/key'.

The Solution

The following class allows to assign values to hierarchical keys. It provides factory method for creating a new instance and parsing an INI-file from a string.

/**
 * Ini structure with nested keys support.
 *
 * @author Ivan Georgiev
 */
class ig_util_IniStructure {
	var $data = array();
	var $separator = '/';
 
	/**
	 * Create a new instance and parse an INI file from a string
	 * to initialize the instance.
	 *
	 * @param string $data
	 * @return ig_util_IniStructure
	 */
	function & CreateFromString($data) {
		$ini =& new ig_util_IniStructure();
		$lineNo = 0;
		$currentSection = '';
		foreach(explode("\n", $data) as $line) {
			$lineNo++;
			$line = trim($line);
			// Comments
			if ( $line == '' || $line{0} == ';' ) { continue; }
			// Sections
			if ( $line{0} == '[' ) {
				if (substr($line, -1, 1) != ']') {
					trigger_error("(line# $lineNo) Invalid section", E_USER_NOTICE);
					continue;
				}
				$currentSection = substr($line, 1, -1);
				continue;
			}
			// Key-value pairs
			list($key,$value) = @explode('=', $line, 2);
			if (is_null($value)) {
				trigger_error("(line# $lineNo) Invlaid key", E_USER_NOTICE);
				continue;
			}
			$ini->set($currentSection . $ini->separator . trim($key), $ini->_parseValue($value));
		}
		return $ini;
	}
 
	/**
	 * Get value associated with a key.
	 *
	 * @param string key
	 * @param mixed default Value to return if key was not found.
	 * @return mixed The value associated with a key or the default value.
	 */
	function get($key, $default=null) {
		$accessor = $this->_makeAccessor($key);
		$value = eval("return isset($accessor) ? $accessor : \$default;");
		return $value;
	}
 
	/**
	 * Associate a value with a key.
	 *
	 * @param string key
	 * @param mixed value
	 * @return void
	 */
	function set($key, $value) {
		$accessor = $this->_makeAccessor($key);
		eval("$accessor=\$value;");
	}
 
	/**
	 * Get an array with all keys that are sub-key to a given key.
	 *
	 * @param string $rootKey
	 * @return array
	 */
	function keyArray($rootKey=null) {
		$accessor = $this->_makeAccessor($rootKey);
		$keys = eval("return isset($accessor) && is_array($accessor) ? "
				. "array_keys($accessor) : array();");
		return $keys;
	}
 
	/**
	 * The same as {@link keyArray} but returns absolute keys (each key includes
	 * the root key).
	 *
	 * @param string $rootKey
	 * @return array
	 */
	function absoluteKeyArray($rootKey) {
		$keys = $this->keyArray($rootKey);
		$rootPath = is_null($rootKey) ? '' : $rootKey . $this->separator;
		foreach(array_keys($keys) as $keyIndex) {
			$keys[$keyIndex] = $rootPath . $keys[$keyIndex];
		}
		return $keys;
	}
 
	function _makeAccessor($key) {
		$accessor = '$this->data';
		if (!is_null($key)) {
			$accessor .= '[\''
				. str_replace($this->separator, '\'][\'', $key)
				. '\']';
		}
		return $accessor;
	}
 
	/**
	 * Parse value by trimming the value and looking for special values.
	 * @return true
	 */
	function _parseValue($value) {
		$value = trim($value);
		switch (strtolower($value)) {
			case 'true':
			case 'yes':
				$value = true;
				break;
			case 'false':
			case 'no':
				$value = false;
				break;
			case 'null':
				$value = null;
				break;
			default:
				// Nothing
		}
		return $value;
	}
 
}

Discussion

Here are some ideas for future extensions:

Array Key support

We can add array key support. This means that when key ending with [] is met, its value automatically is converted to an array. E.g.

[section]
array_key=value0
array_key[]=value1
array_key[]=value2

results in array value for the 'section/array_key' key:

array('value0', 'value1', 'value2');

The first assigned value wasn't an array, but is automatically converted to array.

The following definition will not result in an array value, because the last line overwrites the previous value definition:

[section]
array_key=value0
array_key[]=value1
array_key[]=value2
array_key=scalar value

Export to a string

This can be a simple toString() method that returns an INI file.

Read/Write real file

To keep the implementation simple, this might be implemented in another class. It can use some abstract reader/writer.

More Features

For additional features, e.g. value quotation, numeric values etc. check:

See also other formats, like YAML

See Also

 
php/util/inistructure.txt · Last modified: 2009/10/31 23:39 (external edit)
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki