Documentation and Tutorial for PEAR-Config

by Alexander Merz
$Id: README.Config,v 1.1 2003/04/23 03:53:27 vieglais Exp $

A first example

For beginning we start with a useful script example:

----- example.php ------

<?php

require_once( "config.php" ) ;

$path 			= "c:/windows/system/" ;	// change this to the location of $file
$file 			= "php.ini" ;				// filename to parse
$options["cc"] 	= ';' ;						// set the comment char
$blocks			= array() ;					// contains all blocknames
$data			= array() ;				// contains all key-value-pairs

$conf = new Config( "IniFile") ;			// Data container is a ini file

$conf -> parseInput( $path.$file, $options) ;
$blocks = $conf -> getBlocks( "/" ) ;

foreach( $blocks as $block ) {				// read the k-v-p of every block
	$data[$block] = $conf -> getValues( "/".$block ) ;
}

// output
?>
<html>
<head>
	<title>Test for PEAR - Config</title>
</head>
<body>
	<table>
		<?php
			while (list($n, $d) = each ($data)) {
				echo "<tr><td colspan=\"2\">Block: $n</td></tr>";
				foreach( $d as $k => $v ) {
					echo "<tr><td>$k</td><td>$v</td></tr>";
				}
			}
		?>
	</table>
</body>

----- example.php ------

This script parses the php.ini and creates a table containing the name of all blocks and
the key-value-pairs of every block.

Now, lets cut up the script.

	require_once( "config.php" ) ;

First of all, to use the Config-API you have to include
"config.php". If you have a regular PHP installation including PEAR, there should be
no problem for PHP to find the file. Unfortunatly a large number of ISP didn't installed PEAR
or have a older installation, where Config isn't present. In this case, we have good news for
you.

Config is "pure" PHP. This means you can download "config.php" and (important!) the
Config directory from cvs.php.net and copy the files to your webspace. Take care of using the
correct path to "config.php" in the require statement. Also, go this way, if you perhaps want
to use a newer version of Config.

	$conf = new Config( "IniFile") ;

With this line you create a new instance of the Config class. The constructor requires the
declaration of the type of the container, which contains the configuration data. Check the
'Implementations' section in this documentation the see which containers are avaible at the
moment.

"php.ini" is a typical example for a ini file and should be present on your system, if you have
installed PHP ( else this document should be a bit useless for you!?). So, to work with this
file, we have to say Config to parse this file.

	$conf -> parseInput( $path.$file, $options) ;

Normaly this function reads the data container and writes the data into an internal data
structure. How to adress specific the container depends on the implemention, IniFile
requires an existing file. The second parameter contains a set of options. Which options exist
and/ or required depends on the type of the container. In this case, Ini files have often
comments, which should not be parsed, "php.ini" uses the semicolon to mark up the start of
a comment.

At this point, we can start to accessing the configuration data.

To accessing the data, you should know something about how Config 'structurize' the data.
Config distinguish three types of elements: keys, values and blocks. Take a look on "php.ini".

The first 'key' is "engine". Keys are names of options and/or of data elements. To every key
belongs a 'value'. The value of the key "engine" could be "on" or "off". "on" or "off" are
data of the key. Take care, also missing of such data, ie. a empty string is a value.

Often, ie. "php.ini", the data container contains a huge number of key-value pairs. Sometimes
it isn't possible to avoid two or more options with the same key name, ie. you want to save
the access data for databases with different usernames, passwords and tablenames. To separate
them, you can edge them in a 'block'. Another typical use of blocks is to summarize
key-values pairs which belong together in a block, as you can see in "php.ini". Especially, if
a data container should be editable be humans without a special programm, you should use blocks
to structurize the data.

The data in "php.ini" is completly separate into blocks, so as the first access, we want the
names of the blocks.

	$blocks = $conf -> getBlocks( "/" ) ;

getBlocks() returns an array containing all block names. You maybe wonder about the argument.
getBlocks requires a 'blockpath'. Config structurize the data as a tree comparable to a
file system. Think blocks as directories and key-value pairs as files. You can store a file
in the root dir ( "/" under Unix-like systems) or in a directory ( ie. ( "/usr/" ) or
subdirectories ( ie. "/usr/bin/" ). According to this, there are implementations which
support sub-blocks in blocks.

IniFile doesn't support block nesting, only blocks in the first level, the 'root' level and
the root level is adressed with a single slash.

Take care: If you want all sub blocks of ie. the block named "db_accessdata", the blockpath is
"/db_accessdata/" - don't forget the leading root slash! Also, if you need to address
key-value-pairs, as you can see later in this manual, which are not in a block, you have to
use "/" as blockpath.

As the next we want all key-value pairs of every block. To get them we use

	$data[$block] = $conf -> getValues( "/".$block ) ;

to recieve them. getValues() returns a hash with all key-value pairs in the given block path.
The key will used as hash key containing the value of the key,
ie: $data[ 'PHP' ][ 'engine' ] = "on".

So thats all, what would be worthy to talk about the script! Now a little exercise for you:
You should able to rewrite the script to print out the content of every configuration data
container including key-value-pairs in subblocks.

Getting and Setting

getValues is a possibility to get all config data of a block. But often you need only
a few of them and/ or you want more influence oft the returned data.

To only get the value of one key, you can use getValue(). getValue() requires the blockpath  as
the first argument like dicussed above, as the next the name of the key as a string.
getValue() has a third optional parameter. If the key in the named block doesn't exists, the
key and if neccessary the block will be created and the third parameter will be assigned
to them and returned by getValue().

The counterpart of getValue() is setValue(). setValue() requires a blockpath, the name of the
key too, but as the third argument the value, which should be assigned to the key. The behavior
of setValue, if the key or the block not exists, is the same like getValue(). setValue()
returns the assigned value too.

Saving changes

If you changed values of keys or adding keys or blocks, this changes only influence the
internal data structure of Config. So changes will lost during finishing the script.
To save them permantly, you have to call writeInput(). writeInput has two optional
arguments. The first is the name of the data container, where the data should be saved.
If you left this part empty, writeInput() use the data container given in parseInput() as
destination.

The second optional argument orders writeInput() to save the data preserving the
content and format of the original container, as good as possible, if the argument is true.
This option makes in the most cases only sense in file-based containers like IniFile. Normaly
configuration files contains ie. comments, a lot of whitespaces to make the files editable by
humans. This data will not be parsed into the internal data struture and so cannot be written
back to the container. If the preserving argument is true, writeInput() takes the original
container and tries to copy the content, and changes only different values and adds new keys and
blocks keeping the original file content including comments and whitespace.

Implementations

IniFile

The container is a file with following format

...
; comment
[blockname]	; comment
key=value 	; comment
key="value with whitespaces; and the comment char" ; comment
...

Comments are marked by a special starting comment char and can stay on a single line
or after a value, but not between a key-value assignment.

Blocks are marked between brackets and the block name between them. There is no block end
markup. A block ist finish by the end of the file or a new block markup. Block nesting is not
possible.

Only one key-value assignment per line is allowed. The assignment consist of the keyname, a
equal sign and the value. If the value contains whitespace or the comment char, you have to
set the value between double marks.

Options for parseInput() are:
	"cc" - defines the comment char


XML (by Christian Stocker <chregu@phant.ch>)

The main advantage of the XML-Container is, that it knows Sub-blocks. So you can
have nested blocks, which I  have to use from time to time.
The XML-Container depends on the domxml implementation of PHP, therefore you
have to compile you're PHP with the option --with-domxml (or ask you're provider
to do so...)

An example XML-file looks this way

<?xml version="1.0"?>
    <block1 key1="value1">
    	<block2 key2="value2" key3="value3"/>
    </block1>

The XML-File has to be well-formed, otherwise you get an error.

The Constructor has to be called the following way

$conf = new Config( "xml") ;

accessing these values works the same as in the IniFile-Container, except for
Sub-blocks you can use the Directory-structure mentioned above. For example, if
you want the values of Block2:

$conf->getValues("/block1/block2/");

Options (default value in brackets):

IncludeContent [True]	    Since an xml-tags also can have content (not only
							attributes), this flag indicates if we want to take
							this as a value for the block.
							example:
							...
							<block1>This is content</block1>
							...
							$data = $conf->getValues("/block1/");
							would give us
							$data[content] = "This is content";

							Be aware:
							<block1>This is content<block2> and this also</block2></block1>
							...
							$data = $conf->getValues("/block1/");
							gives you
							$data[content] = "This is content and this also";

IncludeChildren	[True]		For more flexibility in parsing your config files,
							there's an array containin all the names of the sub-
							blocks of one block, if you set this.
							...
							<block1 key1="value1">
							    	<block2 key2="value2" key3="value3"/>
									<block3 key4="value2" key5="value3"/>
						    </block1>
						    ...
							$data = $conf->getValues("/block1/");
							gives you an array with the values:
							[key1] => value1
							[children] => Array
								(
									[0] => block2
									[1] => block3
								)
							you can then get the values of block2 and block3
							with a simple foreach-loop:

							foreach ($data[children] as $childname) {
								$childdata = $conf->getValues("/block1/".$childname);
							}

MasterAttribute [Null]		You can assign one attribute of a block as a
							MasterAttribute, then the value of this attribute
							will be assigned to the parent-block with the name
							of the sub-block (which have the MasterAttribute) as
							the key and the value of the MasterAttribute. You
							can also set "content" as the MasterAttribute.

							Example:
							MasterAttribute = "key2";
							$data = $conf->getValues("/block1/");
							gives you an array with the values:
							[key1] => value1
							[block2] => value2


IncludeMasterAttribute [True]	If you want to include the MasterAttribute also
i							n the subblocks, set this flag.
							MasterAttribute = "key2";
							$data = $conf->getValues("/block1/block2");
							gives you an array with the values:
							[key2] => value2
							[key3] => value3

							if you set the flag False, then [key2] will not be included.
                            
KeyAttribute [False]        If you have 2 Blocks on the same level with the same name, 
                            this won't work with the default settings, but you can use
                            the KeyAttribute flag.
                            Assuming the following part of a xml:
                            ...
                        	<block2 name="whatever" key3="value3"/>
                        	<block2 name="whereever" key4="value4"/>
                        	<block2 key4="value5"/>
                            ...
                            if you set now the flag KeyAttribute to "name", you'll get 
                            the name attribute as the key name, whereever this attribute
                            is set:
                            
                            $data = $conf->getValues(".../block2");
                            
                            [whatever]  => array(name=>whatever, key3=>value3)
                            [whereever] => array(name=>whereever, key4=>value4)
                            [block2]    => array(key4=>value5)

Limitations:

Writing is not supported yet. you can't write back the changes you made. Since
xml files can be written in different ways meaning almost the same, it's hard to
preserve the old format. But it's certainly possible..

DB

you can store you're config data also in a Database. This implementation uses
the Pear::DB Classes, so any Database supported within pear should be supported
here also.

the table in the Database has to look the following way (the length of the
varchar does not matter and additional fields can be included.)

CREATE TABLE config (
   datasrc varchar(50) NOT NULL,
   block varchar(50) NOT NULL,
   name varchar(50) NOT NULL,
   value varchar(50) NOT NULL
);

Calling the constructor:

$conf = new Config("db","mysql://username:password@localhost/dbname") ;

the second parameter is the dsn, as described in the pear-db documentation.

The rest is quite the same as in the IniFile-Implementation. Just try to replace
the constructor-line in the examples for the IniFile-Container and the rest
should work.

datasrc:	it's the equivalent to the filename. so you can have different "ini-files" in the same db-table (for example to store different settings for different users)

block/name/value: has the same meaning as in the IniFile-Container.



Writing back the data is also supported.
Sub-blocks are not supported.

Options:

Table	[config]	The name of the db-table, where the keys/values/etc are
					stored.


API-Reference

Config($storage_driver,$storage_options = "")

	Constructor

	Parameters
		storage_driver 	- name of the data container
		storage_options - n/a

array getBlocks( $block = "/")

	returns all blocknames of the given blockpath

	Parameters
		block			- existing blockpath

	Return
		array			- array containing all blocknames


mixed getValue( $block = "/", $key, $default = "")

	returns the value of the key in the block

	Parameters
		block			- blockpath
		key				- key name
		value			- default value, if key not found

	Return
		mixed			- the value of the key or the default value


array getValues( $block = "/")

	returns all key-value-pairs of the given blockpath

	Parameters
		block			- existing blockpath

	Return
		array			- hash containing the key-value-pairs

mixed setValue( $block = "/", $key, $value = "")

	sets the key in the block to the given value

	Parameters
		block			- blockpath
		key				- key name
		value			- value to assign

	Return
		mixed			- the given value

mixed parseInput ($src ,$options = array() )

	set the data source to parse

	Parameters
		src				- Source to parse
		ooptions		- array of the settings

	Return
		mixed			- true on success, else PEAR_ERROR

mixed writeInput( $dest = "", $preserve = True )

	writes the data to dest

	Parameters
		dest			- Destination for writing
		preserve		- if true, try to preserve writing

	Return
		mixed			- true if success, else PEAR_ERROR


WDDX
[expat library + compiling PHP --with-xml and --enable-wddx required]

Configuration can be also stored in XML based WDDX files. This container isn`t 
good for reading/modyfying by human, but it is very useful if you need config
file for internal use of your application. This container supports nested
blocks, can store arrays and can write modified configuration to file. This is
very bad looking example of WDDX file [it is much nicer than you think - 
normaly there are no new lines or tabs :)]:

<wddxPacket version='1.0'><header/><data>
<struct>
  <var name='/block1'><struct>
    <var name='/block2'><struct>
      <var name='/block3'><struct>
        <var name='val123'><string>1</string></var>
      </struct></var>
      <var name='val121'><string>test</string></var>
      <var name='val122'><string>2</string></var>
    </struct></var>
    <var name='/block11'><struct>
      <var name='11'><string>bueeeee</string></var>
    </struct></var>
    <var name='valx1'><string>1</string></var>
    <var name='valt1'><string>2</string></var>
  </struct></var>
  <var name='valxt1'><string>test1</string></var>
</struct>
</data></wddxPacket>

You can try to use it as config file, because it is written as such. If 
you have any experience with WDDX you should know that not every WDDX
compatible file will work as config file in this container - it needs to
be in specific format to give full advantage of nested blocks - every block
is an array stored in parent block, array must be stored under key, that 
starts with '/'. If it isn`t - it`s just an array used as configuration 
variable, so nesting will not be supported in those.

If the text above doesn`t seem to be much interesting it means you don`t 
have to understand what`s in WDDX itself. So - create empty file, use it
with Config class (parseInput), write some data to it (setValue) and then
write the file to disk (writeInput). This is the easiest way of creating
WDDX configuration without knowing anything about it. 

There are no special features to be set in parseInput - this argument is
there for compability reasons. Preserving behaviour during file writing
is also ignored.