В почтовой рассылке 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 по умолчанию.
Вы можете загрузить мои классы для конфигураций здесь.

nice idea, worth for further consideration implementing it into ZF
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
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…
Данное конфигурирование уже реализовано в phpxcore.org, думаю оттудова можно взять много полезных идей.
Для парсинга конфига использовали PEAR::Config, распарсенный конфиг сохраняли как PHPArray в файл – что довольно ощутимо увеличивало скорость загрузки конфига в дальнейшем.
Поскольку одна из задач – поддержка PHP4 – то приходится извращаться при обращении к конфигу :(
(это я о phpxcore.org)