В почтовой рассылке Zend Framework кипит обсуждение класса Zend_Config. У меня есть свои идеи об этом классе, и я попытаюсь изложить их здесь. Мне необходимо нечто, что сможет загружать и сохранять конфигурацию в разные хранилища (например, XML, базы данных или текстовые файлы INI), важно иметь возможность изменять параметры хранилища (например, имя файла, таблицы или даже структуру базы данных), было бы замечательно, если бы я мог расширять систему хранения своими собственными стратегиями.
Для начала, попробуем представить как должен выглядеть пользовательский код:
// 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 будет дружественным для пользователя классом с несколькими простыми функциями вроде загрузки, сохранения конфигурации, изменения движка хранения, доступа к корневому узлу конфигурации.
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');
}
}
Узлы конфигурации довольно просто закодировать:
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.
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-файл с конфигурацией:
<config>
<database type="mysql">
<host port="3306">localhost</host>
<name>test</name>
<user>root</user>
<password>123456</password>
</database>
</config>
Вы наверное заметили один туманный аспект в классе узла конфигурации. Рассмотрим узел host: у нас есть атрибут и текстовый узел. Как насчет неоднозначности? Как мы можем получить доступ к тектовому узлу? Мы не можем сделать следующее:
$host = $config->database->host->port;
Мое решение - использовать псевдо-переменную value:
$host = $config->database->host->port;
Если не задано атрибутов, используется псевдо-переменная value по умолчанию.
Вы можете загрузить мои классы для конфигураций здесь.
Русский
English
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
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)