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.
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)