Pragmatism in the real world

Zend_Config Proposal v3: Akrabat_Config (8)

After discussion with Jayson on the Zend Framework’s fw-general list and dealing with some excellent bug reports by Richard (aka OpenMacNews), I’ve come to the conclusion that the implementation of Akrabat_Config is wrong.

I’ve redone it as two separate classes with no base class. We now have:

  • Akrabat_Config
  • Akrabat_Config_ini

Akrabat_Config takes an associative array and provides read only access to it as properties properties . It also implements Countable and Iterator to make life easier. Akrabat_Config_ini loads an ini file and creates an associative array. Therefore Akrabat_Config_Ini only deals with all the handling of the “extends” keyword and nesting using full-stops in the key name. Akrabat_Config only deals with providing data access.

All in all much simpler!

I’ve updated the code to version 0.8(!):
Akrabat_Config_0.8.zip

I’ve also sorted out a new version of the Zend_Config proposal, which is now at version 3:

Zend Framework Component Proposal


Proposed Component Name
-----------------------------------------------
Zend_Config


Proposers
-----------------------------------------------
Rob Allen (rob@akrabat.com


Revision
-----------------------------------------------
3 - 21 May 2006: Further reworking based on mailing list.


Overview
-----------------------------------------------
Zend_Config is a very simple configuration file reader.
It provides an easy means to read configuration files
and access the data within them as a set of key->value
pairs. It will support at least one nested level of data.
Initially providing support for ini files, it should
be easy to extend for other formats such as YAML.


References
-----------------------------------------------
Mailing list thread resurrected here:
    http://www.zend.com/lists/fw-general/200604/msg00178.html.
Unfortunately, I could not find the original post by Andi Gutmans 
in the archives.

Other discussion can be found here:
    http://www.akrabat.com/index.php?s=Akrabat_Config

Feedback on v1 from Zend is discussed here:
    http://www.zend.com/lists/fw-general/200605/msg00134.html
    
Prototype code can be found here:
    http://www.akrabat.com/2006/05/13/zend_config-proposal-v2/

Mailing list feedback on v2 is discussed here:
    http://www.zend.com/lists/fw-general/200605/msg00563.html
    
Mailing list discussion on v2.1 is here:
    http://www.zend.com/lists/fw-general/200605/msg00748.html


Requirements
-----------------------------------------------
* Ability to load configuration information from a single config file
  and provide access to the data as object properties.
* A top level section name must be specified for loading.
* Optional option to allow modification of the config data held in
  memory.
* No ability to modify the original data in the config file.
* For ini files, support for "namespaces" using the syntax:
        namespace.property = value
* Iterator is implemented for easily listing of configuration
  information.
* A special "inheritence keyword "extends" will be be used to allow for
  including additional sections within this section. For ini files,
  the syntax would be:
        extends = section


Dependencies on Other Framework Components
-----------------------------------------------
Zend_Exception


Theory of Operation
-----------------------------------------------
Zend_Config loads an provides a property based interface to an array.
The data is read only unless $allowModifications is set to true on 
construction. Zend_Config also implements Countable and Interator to
facilitate easy access to the data.

Sometimes it is necessary to get at a group of config variables as an
array (e.g. the $config paramter in Zend_Db::factory()). Zend_Config 
provides the function "asArray()" to allow easy integration with such
requirements.

Zend_Config_Ini is a class thatloads ini files and creates and
associated array for use by Zend_Config. It loads a single section from
the specified ini file. It supports using the full stop character (".")
in a key name to provide additional nesting levels. That is, a key named
"db.name" will be represented in the array as $data['db']['name'].

Zend_Config_ini also supports loading of keys from one section of the
ini file into another section. This is done using a special key called
"extends". It is possible for the parent section to itself extend from
another section. Multiple inheritence such that a section can extend 
from two or more sections is not permitted.

In order to make it easy to use with Zend_Config, Zend_Config_Ini has a
static method, "load()", which loads the ini file and returns the array.

In the future, should there be demand for other config file types, other
Zend_Config_XXX classes can be created for use by Zend_Config. The only
requirement would be for such classes to be able to generate an 
associative array for loading into Zend_Config.


Class Index
-----------------------------------------------
Zend_Config_Exception
Zend_Config
Zend_Config_Ini


Use Cases
-----------------------------------------------
Given the following "myapp.ini" file:
    [all]
    namespace.property = example
    db.connection = foo
    db.name = bar
    db.password = pwd
    hostname = www.zend.com

    [dev]
    extends=all
    hostname=andi_box
    db.name=local

    [andi_dev]
    extends=development
    hostname=andi_box
    db.connection=localhost

    [staging]
    extends=all
    hostname=dev.zend.com


Then we can do something like:

< ?php
    $obj=new Zend_Config(Zend_Config_Ini::load('myapp.ini', 'andi_dev'));
    
    print $obj->hostname;   // prints "andi_box"
    print $obj->db->connection; // prints "localhost"
    print $obj->db->name; // prints "local"
    print $obj->db->password; // prints "pwd"
?>


Example use with Zend_Db:
"db.ini" file:
    [all]
    db.type = pdoMssql
    db.config.host = localhost
    db.config.username = user
    db.config.password = pwd
    db.config.dbname = db

< ?php
    $obj=new Zend_Config(Zend_Config_Ini::load('db.ini', 'all'));
    $db = Zend_Db::factory($obj->db->type, $obj->db->config->asArray());
?>



Class Skeletons
-----------------------------------------------
class Zend_Config_Exception extends Zend_Exception {}

class Zend_Config implements Countable, Iterator
{
    protected $_allowModifications;
    protected $_iterationPointerValid;
    protected $_data;
    
    /**
     * Akrabat_Config provides a property based interface to
     * an array. The data is read only unless $allowModifications
     * is set to true on construction.
     *
     * Akrabat_Config also implements Countable and Interator to
     * facilitate easy access to the data.
     *
     * @param array $array
     * @param boolean $allowModifications
     */
     public function __construct($array, $allowModifications=false) {}

    /**
     * Ensure that the key is a valid PHP property name
     *
     * @param string $key
     * @return boolean
     */
    protected function isValidKeyName($key) {}
    
    /**
     * Magic function so that $obj->value will work.
     *
     * @param string $name
     * @return mixed
     */
    public function __get($name) {}
    
    /**
     * Only allow setting of a property if $allowModifications
     * was set to true on construction. Otherwise, throw an exception.
     *
     * @param string $name
     * @param mixed $value
     */
    public function __set($name, $value) {}
    
    /**
     * Return an associative array of the stored data.
     *
     * @return array
     */
    public function asArray()

    /**
     * Support isset() overloading on PHP 5.1
     *
     * @param string $name
     * @return boolean
     */
    protected function __isset($name)
    
    /**
     * Defined by Countable interface
     */
    function count() {}
    
    /**
     * Defined by Interator interface
     */
    function current () {}
    function key () {}
    function next () {}
    function rewind () {}
    function valid () {}
}


class Zend_Config_Ini
{
    /**
     * Load $section from an ini file called $filename into
     * an associative array.
     *
     * If any keys with $section are called "extends", then the section
     * pointed to by the "extends" is then included into the properties.
     * Note that the keys in $section will override any keys of the same
     * name in the sections that have been included via "extends".
     *
     * If any key includes a ".", then this will act as a separator to
     * create a sub-property.
     *
     * @param string $filename
     * @param string $section
     * @return array
     */
    public static function load($filename, $section) {}

    /**
     * Helper function to process each element in the section and handle
     * the "extends" inheritance keyword. Passes control to processKey()
     * to handle the "dot" sub-property syntax in each key.
     *
     * @param array $iniArray
     * @param string $section
     * @param array $config
     * @return array
     */
    protected function processExtends($iniArray, $section, $config) {}

    /**
     * Assign the key's value to the property list. Handle the "dot"
     * notation for sub-properties by passing control to
     * processLevelsInKey().
     *
     * @param array $config
     * @param string $key
     * @param string $value
     * @return array
     */
    function processKey($config, $key , $value) {}

    /**
     * Helper function to handle the "dot" namespace syntax in the key.
     * Uses "." as the separator.
     *
     * @param array $parent
     * @param string $key
     * @param string $value
     * @return array
     */
    protected function processLevelsInKey($parent, $key, $value) {}

}