Zend Framework: размышления о Zend_Config

(PHP) · English (27,686 views)

В почтовой рассылке Zend Framework кипит обсуждение класса Zend_Config. У меня есть свои идеи об этом классе, и я попытаюсь изложить их здесь. Мне необходимо нечто, что сможет загружать и сохранять конфигурацию в разные хранилища (например, XML, базы данных или текстовые файлы INI), важно иметь возможность изменять параметры хранилища (например, имя файла, таблицы или даже структуру базы данных), было бы замечательно, если бы я мог расширять систему хранения своими собственными стратегиями.

Для начала, попробуем представить как должен выглядеть пользовательский код:

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/>';

Я думаю, конфигурация должна представляться в памяти как массив объектов. Config будет дружественным для пользователя классом с несколькими простыми функциями вроде загрузки, сохранения конфигурации, изменения движка хранения, доступа к корневому узлу конфигурации.

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');
  }
}

Узлы конфигурации довольно просто закодировать:

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;
  }
}

Теперь я попытаюсь разработать простейшую стратегию, основанную на XML.

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;
  }
}

Вот простой XML-файл с конфигурацией:

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>

Вы наверное заметили один туманный аспект в классе узла конфигурации. Рассмотрим узел host: у нас есть атрибут и текстовый узел. Как насчет неоднозначности? Как мы можем получить доступ к тектовому узлу? Мы не можем сделать следующее:

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

Мое решение – использовать псевдо-переменную value:

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

Если не задано атрибутов, используется псевдо-переменная value по умолчанию.

Вы можете загрузить мои классы для конфигураций здесь.

5 Responses to this entry

Subscribe to comments with RSS

h0tzenpl0tz
said on 10.03.2006 at 21.39 · Permalink

nice idea, worth for further consideration implementing it into ZF

Joe @
said on 27.03.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 10.04.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 01.04.2007 at 16.02 · Permalink

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

said on 04.04.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.