Zend Framework: Thoughts about Zend_Config

Posted by Dmytro Shteflyuk on under PHP

In Zend Framework’s mailing list discussion about Zend_Config class is in full swing. I have my own ideas about this class and I will try to explain their here. I need something that can load and save configuration into different storages (for example, XML, database or plain text like INI-files), it’s necessary to have ability to change every parameter of storage (for example, file name, database tables or even database structure), it will be able if I can extend storage system with my own storage strategies.

First I’ll try to imagine how user’s code will look:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Zend::loadClass('Config');

// Use default XML-persistence engine
$config = new Config('config.xml');

// Use plain text storage engine
Zend::loadClass('Config_Text_Storage');
$persistance = new Config_Text_Persistance('config.ini');
$config->setPersistance($persistance);

echo 'Host: ' . $config->database->host->value . '<br/>';
echo 'Port: ' . $config->database->host->port . '<br/>';
echo 'Database: ' . $config->database->name . '<br/>';
echo 'User: ' . $config->database->user . '<br/>';
echo 'Password: ' . $config->database->password . '<br/>';

I think configuration representation in memory will be objects tree. Config will be user friendly class with several simple functions like load, save configuration, change persistance engine, access root node of configuration object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/** Exception for Config class. */
require_once 'Config/Exception.php';

/** Exception for Config class. */
require_once 'Config/Persistance/Interface.php';

class Config
{
  private $_persistance = null;

  private $_rootNode = null;

  /**
   * Create configuration object. You can pass string argument with
   * path to XML configuration file.
   *
   * @param string $xmlConfigPath
   */

  public function __construct($xmlConfigPath = null)
  {
    require_once 'Config/XML/Persistance.php';
    $this->_persistance = new Config_XML_Persistance($xmlConfigPath);

    if ($xmlConfigPath) $this->load();
  }

  /** Save configuration. */
  public function save()
  {
    $this->checkPersistanceAssigned();
    $this->_persistance->save();
  }

  /** Load configuration */
  public function load()
  {
    $this->checkPersistanceAssigned();
    $this->_persistance->load();
    $this->_rootNode = $this->_persistance->getRootNode();
  }

  /**
   * Get config variable with $name name.
   *
   * @param string $name
   * @return mixed
   */

  public function __get($name)
  {
    return $this->_rootNode->__get($name);
  }

  /**
   * Set persistance object.
   *
   * @param Config_Persistance_Interface $persistance
   */

  public function setPersistance(Config_Persistance_Interface $persistance)
  {
    $this->_persistance = $persistance;
  }

  /** Check if persistance object has been assigned. */
  private function checkPersistanceAssigned()
  {
    if (!$this->_persistance) throw new Config_Exception('No configuration persistance object assigned');
  }
}

Configuration nodes are very easy to code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

class Config_Node
{
  private $_values;

  public function __construct($root = null)
  {
    $this->_values = $root;
  }

  public function __get($name)
  {
    if (array_key_exists($name, $this->_values) === false)
      throw new Config_Exception('No config node "' . $name . '" defined.');

    if (is_object($this->_values[$name])
      && sizeof($this->_values[$name]->_values) == 1
      && array_key_exists('value', $this->_values[$name]->_values))
    {
      return $this->_values[$name]->__get('value');
    }

    return $this->_values[$name];
  }

  public function __set($name, $value)
  {
    $this->_values[$name] = $value;
  }
}

Now I’ll try to develop simplest strategy for XML-based storage.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/** Exception for Config_XML_Persistance class. */
require_once 'Config/XML/Exception.php';

/** Interface Config_Persistance_Interface. */
require_once 'Config/Persistance/Interface.php';

/** Configuration node class Config_Node. */
require_once 'Config/Node.php';

class Config_XML_Persistance implements Config_Persistance_Interface
{
  private $_xmlConfigFile = null;
  private $_rootNode = null;

  public function __construct($xmlConfigFile = null)
  {
    $this->_xmlConfigFile = $xmlConfigFile;
  }

  /** Load configuration from XML file. */
  public function load()
  {
    if (!$this->_xmlConfigFile)
      throw new Config_XML_Exception('Config file name is not assigned.');

    $dom = new DOMDocument();
    $dom->preserveWhiteSpace = false;
    $dom->load($this->_xmlConfigFile);

    $this->_rootNode = $this->populateConfig($dom->documentElement);
  }

  /** Save configuration to XML file. */
  public function save()
  {
    throw new Config_XML_Exception('save() function is not implemented for this persistance type.');
  }

  /** Returns root configuration node. */
  public function getRootNode()
  {
    return $this->_rootNode;
  }

  /**
   * Read nodes starting from $root and fill Config_Node objects.
   * Returns root Config_Node object.
   *
   * @param DomNode $root
   * @return Config_Node
   */

  private function populateConfig($root)
  {
    $configNode = new Config_Node();
    foreach ($root->attributes as $attribute)
      $configNode->__set($attribute->name, $attribute->nodeValue);

    foreach ($root->childNodes as $node)
    {
      if ($node->nodeType === 1)
        $configNode->__set($node->tagName, $this->populateConfig($node));
      else
        $configNode->__set('value', $node->nodeValue);
    }
    return $configNode;
  }
}

Here simple configuration XML-file:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>

<config>
  <database type="mysql">
    <host port="3306">localhost</host>
    <name>test</name>
    <user>root</user>
    <password>123456</password>
  </database>
</config>

You can see one obscure aspect of the configuration node class. Please look at host node of configuration: we have an attribure and text node in it. What about ambiguity? How we can access text node? We can’t do following:

1
2
$host = $config->database->host;
$host = $config->database->host->port;

My solution for this problem is pseudo-variable value:

1
2
$host = $config->database->host->value;
$host = $config->database->host->port;

If there are no attributes, pseudo-variable value will be used by default.

You can download my configuration classes here.

5 Responses to this entry

Subscribe to comments with RSS

h0tzenpl0tz
said on March 10th, 2006 at 21:39 · Permalink

nice idea, worth for further consideration implementing it into ZF

Joe
said on March 27th, 2006 at 18:58 · Permalink

Quite nice ! But it looks like SimpleXML, no ? Why didn’t you use simpeXML. It would resolve your problem of pseudo-variable “value”.
Maybe I missed something, actually :P

said on April 10th, 2006 at 14:42 · Permalink

I like it. I hadn’t see this when I finally got around to looking at Config issues. I have gone with ini files at the moment. I suspect that I’ll abstract to allow for xml or db options too at some point.

http://www.akrabat.com/2006/04/10/akrabat_config-three/ is my current incarnation.

Regards,

Rob…

said on April 1st, 2007 at 16:02 · Permalink

Данное конфигурирование уже реализовано в phpxcore.org, думаю оттудова можно взять много полезных идей.

said on April 4th, 2007 at 13:13 · Permalink

Для парсинга конфига использовали PEAR::Config, распарсенный конфиг сохраняли как PHPArray в файл – что довольно ощутимо увеличивало скорость загрузки конфига в дальнейшем.
Поскольку одна из задач – поддержка PHP4 – то приходится извращаться при обращении к конфигу :(
(это я о phpxcore.org)

Comments are closed

Comments for this entry are closed for a while. If you have anything to say – use a contact form. Thank you for your patience.