From 0c84bf614d505b3417bbe3b2ff5589b8748329b7 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 18 Nov 2014 13:02:56 +0100 Subject: [PATCH] Split config functionality into two classes There is now Icinga\Application\Config as our ini configuration handler and Icinga\Data\ConfigObject as our general configuration container. refs #7147 --- library/Icinga/Application/Config.php | 334 ++++++------------ library/Icinga/Data/ConfigObject.php | 304 ++++++++++++++++ .../library/Icinga/Application/ConfigTest.php | 310 ++++++---------- .../library/Icinga/Data/ConfigObjectTest.php | 224 ++++++++++++ 4 files changed, 751 insertions(+), 421 deletions(-) create mode 100644 library/Icinga/Data/ConfigObject.php create mode 100644 test/php/library/Icinga/Data/ConfigObjectTest.php diff --git a/library/Icinga/Application/Config.php b/library/Icinga/Application/Config.php index 6a08eb93e..dcb837fde 100644 --- a/library/Icinga/Application/Config.php +++ b/library/Icinga/Application/Config.php @@ -6,15 +6,14 @@ namespace Icinga\Application; use Iterator; use Countable; -use ArrayAccess; -use LogicException; use UnexpectedValueException; +use Icinga\Data\ConfigObject; use Icinga\Exception\NotReadableError; /** - * Container for configuration values and global registry of application and module related configuration. + * Container for INI like configuration and global registry of application and module related configuration. */ -class Config implements Countable, Iterator, ArrayAccess +class Config implements Countable, Iterator { /** * Configuration directory where ALL (application and module) configuration is located @@ -38,14 +37,14 @@ class Config implements Countable, Iterator, ArrayAccess protected static $modules = array(); /** - * This config's data + * The internal ConfigObject * - * @var array + * @var ConfigObject */ - protected $data; + protected $config; /** - * The INI file this configuration has been loaded from or should be written to + * The INI file this config has been loaded from or should be written to * * @var string */ @@ -54,19 +53,11 @@ class Config implements Countable, Iterator, ArrayAccess /** * Create a new config * - * @param array $data The data to initialize the new config with + * @param ConfigObject $config The config object to handle */ - public function __construct(array $data = array()) + public function __construct(ConfigObject $config = null) { - $this->data = array(); - - foreach ($data as $key => $value) { - if (is_array($value)) { - $this->data[$key] = new static($value); - } else { - $this->data[$key] = $value; - } - } + $this->config = $config !== null ? $config : new ConfigObject(); } /** @@ -82,7 +73,7 @@ class Config implements Countable, Iterator, ArrayAccess /** * Set this config's file path * - * @param string $filepath The path to the config file + * @param string $filepath The path to the ini file * * @return self */ @@ -93,50 +84,33 @@ class Config implements Countable, Iterator, ArrayAccess } /** - * Deep clone this config - */ - public function __clone() - { - $array = array(); - foreach ($this->data as $key => $value) { - if ($value instanceof self) { - $array[$key] = clone $value; - } else { - $array[$key] = $value; - } - } - - $this->data = $array; - } - - /** - * Return the count of available sections and properties + * Return the count of available sections * * @return int */ public function count() { - return count($this->data); + return $this->config->count(); } /** - * Reset the current position of $this->data + * Reset the current position of the internal config object * - * @return mixed + * @return ConfigObject */ public function rewind() { - return reset($this->data); + return $this->config->rewind(); } /** - * Return the section's or property's value of the current iteration + * Return the section of the current iteration * - * @return mixed + * @return ConfigObject */ public function current() { - return current($this->data); + return $this->config->current(); } /** @@ -146,165 +120,47 @@ class Config implements Countable, Iterator, ArrayAccess */ public function valid() { - return key($this->data) !== null; + return $this->config->valid(); } /** - * Return the section's or property's name of the current iteration + * Return the section's name of the current iteration * - * @return mixed + * @return string */ public function key() { - return key($this->data); + return $this->config->key(); } /** - * Advance the position of the current iteration and return the new section's or property's value + * Advance the position of the current iteration and return the new section * - * @return mixed + * @return ConfigObject */ public function next() { - return next($this->data); + return $this->config->next(); } /** - * Return whether the given section or property is set - * - * @param string $key The name of the section or property - * - * @return bool - */ - public function __isset($key) - { - return isset($this->data[$key]); - } - - /** - * Return the value for the given property or the config for the given section - * - * @param string $key The name of the property or section - * - * @return mixed|NULL The value or NULL in case $key does not exist - */ - public function __get($key) - { - if (array_key_exists($key, $this->data)) { - return $this->data[$key]; - } - } - - /** - * Add a new property or section - * - * @param string $key The name of the new property or section - * @param mixed $value The value to set for the new property or section - */ - public function __set($key, $value) - { - if (is_array($value)) { - $this->data[$key] = new static($value); - } else { - $this->data[$key] = $value; - } - } - - /** - * Remove the given property or section - * - * @param string $key The property or section to remove - */ - public function __unset($key) - { - unset($this->data[$key]); - } - - /** - * Return whether the given section or property is set - * - * @param string $key The name of the section or property - * - * @return bool - */ - public function offsetExists($key) - { - return isset($this->$key); - } - - /** - * Return the value for the given property or the config for the given section - * - * @param string $key The name of the property or section - * - * @return mixed|NULL The value or NULL in case $key does not exist - */ - public function offsetGet($key) - { - return $this->$key; - } - - /** - * Add a new property or section - * - * @param string $key The name of the new property or section - * @param mixed $value The value to set for the new property or section - */ - public function offsetSet($key, $value) - { - if ($key === null) { - throw new LogicException('Appending values without an explicit key is not supported'); - } - - $this->$key = $value; - } - - /** - * Remove the given property or section - * - * @param string $key The property or section to remove - */ - public function offsetUnset($key) - { - unset($this->$key); - } - - /** - * Return whether this config has any data + * Return whether this config has any sections * * @return bool */ public function isEmpty() { - return $this->count() === 0; + return $this->config->isEmpty(); } /** - * Return the value for the given property or the config for the given section - * - * @param string $key The name of the property or section - * @param mixed $default The value to return in case the property or section is missing - * - * @return mixed - */ - public function get($key, $default = null) - { - $value = $this->$key; - if ($default !== null && $value === null) { - $value = $default; - } - - return $value; - } - - /** - * Return all section and property names + * Return this config's section names * * @return array */ public function keys() { - return array_keys($this->data); + return $this->config->keys(); } /** @@ -314,46 +170,7 @@ class Config implements Countable, Iterator, ArrayAccess */ public function toArray() { - $array = array(); - foreach ($this->data as $key => $value) { - if ($value instanceof self) { - $array[$key] = $value->toArray(); - } else { - $array[$key] = $value; - } - } - - return $array; - } - - /** - * Merge the given data with this config - * - * @param array|Config $data An array or a config - * - * @return self - */ - public function merge($data) - { - if ($data instanceof self) { - $data = $data->toArray(); - } - - foreach ($data as $key => $value) { - if (array_key_exists($key, $this->data)) { - if (is_array($value)) { - if ($this->data[$key] instanceof self) { - $this->data[$key]->merge($value); - } else { - $this->data[$key] = new static($value); - } - } else { - $this->data[$key] = $value; - } - } else { - $this->data[$key] = is_array($value) ? new static($value) : $value; - } - } + return $this->config->toArray(); } /** @@ -367,14 +184,14 @@ class Config implements Countable, Iterator, ArrayAccess * * @throws UnexpectedValueException In case the given section does not hold any configuration */ - public function fromSection($section, $key, $default = null) + public function get($section, $key, $default = null) { - $value = $this->$section; - if ($value instanceof self) { + $value = $this->config->$section; + if ($value instanceof ConfigObject) { $value = $value->$key; } elseif ($value !== null) { throw new UnexpectedValueException( - sprintf('Value "%s" is not of type "Config" or a sub-type of it', $value) + sprintf('Value "%s" is not of type "%s" or a sub-type of it', $value, get_class($this->config)) ); } @@ -385,6 +202,78 @@ class Config implements Countable, Iterator, ArrayAccess return $value; } + /** + * Return the given section + * + * @param string $name The section's name + * + * @return ConfigObject + */ + public function getSection($name) + { + $section = $this->config->get($name); + return $section !== null ? $section : new ConfigObject(); + } + + /** + * Set or replace a section + * + * @param string $name + * @param array|ConfigObject $config + * + * @return self + */ + public function setSection($name, $config = null) + { + if ($config === null) { + $config = new ConfigObject(); + } elseif (! $config instanceof ConfigObject) { + $config = new ConfigObject($config); + } + + $this->config->$name = $config; + return $this; + } + + /** + * Remove a section + * + * @param string $name + * + * @return self + */ + public function removeSection($name) + { + unset($this->config->$name); + return $this; + } + + /** + * Return whether the given section exists + * + * @param string $name + * + * @return bool + */ + public function hasSection($name) + { + return isset($this->config->$name); + } + + /** + * Initialize a new config using the given array + * + * The returned config has no file associated to it. + * + * @param array $array The array to initialize the config with + * + * @return Config + */ + public static function fromArray(array $array) + { + return new static(new ConfigObject($array)); + } + /** * Load configuration from the given INI file * @@ -394,19 +283,20 @@ class Config implements Countable, Iterator, ArrayAccess */ public static function fromIni($file) { - $config = new static(); + $emptyConfig = new static(); $filepath = realpath($file); if ($filepath === false) { - $config->setConfigFile($file); + $emptyConfig->setConfigFile($file); } elseif (is_readable($filepath)) { + $config = new static(new ConfigObject(parse_ini_file($filepath, true))); $config->setConfigFile($filepath); - $config->merge(parse_ini_file($filepath, true)); + return $config; } else { throw new NotReadableError(t('Cannot read config file "%s". Permission denied'), $filepath); } - return $config; + return $emptyConfig; } /** diff --git a/library/Icinga/Data/ConfigObject.php b/library/Icinga/Data/ConfigObject.php new file mode 100644 index 000000000..23b2e1651 --- /dev/null +++ b/library/Icinga/Data/ConfigObject.php @@ -0,0 +1,304 @@ +data = array(); + + foreach ($data as $key => $value) { + if (is_array($value)) { + $this->data[$key] = new static($value); + } else { + $this->data[$key] = $value; + } + } + } + + /** + * Deep clone this config + */ + public function __clone() + { + $array = array(); + foreach ($this->data as $key => $value) { + if ($value instanceof self) { + $array[$key] = clone $value; + } else { + $array[$key] = $value; + } + } + + $this->data = $array; + } + + /** + * Return the count of available sections and properties + * + * @return int + */ + public function count() + { + return count($this->data); + } + + /** + * Reset the current position of $this->data + * + * @return mixed + */ + public function rewind() + { + return reset($this->data); + } + + /** + * Return the section's or property's value of the current iteration + * + * @return mixed + */ + public function current() + { + return current($this->data); + } + + /** + * Return whether the position of the current iteration is valid + * + * @return bool + */ + public function valid() + { + return key($this->data) !== null; + } + + /** + * Return the section's or property's name of the current iteration + * + * @return mixed + */ + public function key() + { + return key($this->data); + } + + /** + * Advance the position of the current iteration and return the new section's or property's value + * + * @return mixed + */ + public function next() + { + return next($this->data); + } + + /** + * Return whether the given section or property is set + * + * @param string $key The name of the section or property + * + * @return bool + */ + public function __isset($key) + { + return isset($this->data[$key]); + } + + /** + * Return the value for the given property or the config for the given section + * + * @param string $key The name of the property or section + * + * @return mixed|NULL The value or NULL in case $key does not exist + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Add a new property or section + * + * @param string $key The name of the new property or section + * @param mixed $value The value to set for the new property or section + */ + public function __set($key, $value) + { + if (is_array($value)) { + $this->data[$key] = new static($value); + } else { + $this->data[$key] = $value; + } + } + + /** + * Remove the given property or section + * + * @param string $key The property or section to remove + */ + public function __unset($key) + { + unset($this->data[$key]); + } + + /** + * Return whether the given section or property is set + * + * @param string $key The name of the section or property + * + * @return bool + */ + public function offsetExists($key) + { + return isset($this->$key); + } + + /** + * Return the value for the given property or the config for the given section + * + * @param string $key The name of the property or section + * + * @return mixed|NULL The value or NULL in case $key does not exist + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Add a new property or section + * + * @param string $key The name of the new property or section + * @param mixed $value The value to set for the new property or section + */ + public function offsetSet($key, $value) + { + if ($key === null) { + throw new LogicException('Appending values without an explicit key is not supported'); + } + + $this->$key = $value; + } + + /** + * Remove the given property or section + * + * @param string $key The property or section to remove + */ + public function offsetUnset($key) + { + unset($this->$key); + } + + /** + * Return whether this config has any data + * + * @return bool + */ + public function isEmpty() + { + return empty($this->data); + } + + /** + * Return the value for the given property or the config for the given section + * + * @param string $key The name of the property or section + * @param mixed $default The value to return in case the property or section is missing + * + * @return mixed + */ + public function get($key, $default = null) + { + if (array_key_exists($key, $this->data)) { + return $this->data[$key]; + } + + return $default !== null ? $default : null; + } + + /** + * Return all section and property names + * + * @return array + */ + public function keys() + { + return array_keys($this->data); + } + + /** + * Return this config's data as associative array + * + * @return array + */ + public function toArray() + { + $array = array(); + foreach ($this->data as $key => $value) { + if ($value instanceof self) { + $array[$key] = $value->toArray(); + } else { + $array[$key] = $value; + } + } + + return $array; + } + + /** + * Merge the given data with this config + * + * @param array|Config $data An array or a config + * + * @return self + */ + public function merge($data) + { + if ($data instanceof self) { + $data = $data->toArray(); + } + + foreach ($data as $key => $value) { + if (array_key_exists($key, $this->data)) { + if (is_array($value)) { + if ($this->data[$key] instanceof self) { + $this->data[$key]->merge($value); + } else { + $this->data[$key] = new static($value); + } + } else { + $this->data[$key] = $value; + } + } else { + $this->data[$key] = is_array($value) ? new static($value) : $value; + } + } + + return $this; + } +} diff --git a/test/php/library/Icinga/Application/ConfigTest.php b/test/php/library/Icinga/Application/ConfigTest.php index 4f4f77219..1e64da808 100644 --- a/test/php/library/Icinga/Application/ConfigTest.php +++ b/test/php/library/Icinga/Application/ConfigTest.php @@ -28,259 +28,171 @@ class ConfigTest extends BaseTestCase Config::$configDir = $this->oldConfigDir; } - public function testWhetherInitializingAConfigWithAssociativeArraysCreatesHierarchicalConfigObjects() + public function testWhetherConfigIsCountable() { - $config = new Config(array( - 'a' => 'b', - 'c' => 'd', - 'e' => array( - 'f' => 'g', - 'h' => 'i', - 'j' => array( - 'k' => 'l', - 'm' => 'n' - ) - ) - )); + $config = Config::fromArray(array('a' => 'b', 'c' => array('d' => 'e'))); - $this->assertInstanceOf( - get_class($config), - $config->e, - 'Config::__construct() does not accept two dimensional arrays' - ); - $this->assertInstanceOf( - get_class($config), - $config->e->j, - 'Config::__construct() does not accept multi dimensional arrays' - ); + $this->assertInstanceOf('Countable', $config, 'Config does not implement interface `Countable\''); + $this->assertEquals(2, count($config), 'Config does not count sections correctly'); } - /** - * @depends testWhetherInitializingAConfigWithAssociativeArraysCreatesHierarchicalConfigObjects - */ - public function testWhetherItIsPossibleToCloneConfigObjects() + public function testWhetherConfigIsTraversable() { - $config = new Config(array( - 'a' => 'b', - 'c' => array( - 'd' => 'e' - ) - )); - $newConfig = clone $config; + $config = Config::fromArray(array('a' => array(), 'c' => array())); + $config->setSection('e'); - $this->assertNotSame( - $config, - $newConfig, - 'Shallow cloning objects of type Config does not seem to work properly' - ); - $this->assertNotSame( - $config->c, - $newConfig->c, - 'Deep cloning objects of type Config does not seem to work properly' - ); - } - - public function testWhetherConfigObjectsAreCountable() - { - $config = new Config(array('a' => 'b', 'c' => array('d' => 'e'))); - - $this->assertInstanceOf('Countable', $config, 'Config objects do not implement interface `Countable\''); - $this->assertEquals(2, count($config), 'Config objects do not count properties and sections correctly'); - } - - public function testWhetherConfigObjectsAreTraversable() - { - $config = new Config(array('a' => 'b', 'c' => 'd')); - $config->e = 'f'; - - $this->assertInstanceOf('Iterator', $config, 'Config objects do not implement interface `Iterator\''); + $this->assertInstanceOf('Iterator', $config, 'Config does not implement interface `Iterator\''); $actual = array(); - foreach ($config as $key => $value) { - $actual[$key] = $value; + foreach ($config as $key => $_) { + $actual[] = $key; } $this->assertEquals( - array('a' => 'b', 'c' => 'd', 'e' => 'f'), + array('a', 'c', 'e'), $actual, - 'Config objects do not iterate properly in the order their values were inserted' + 'Config does not iterate properly in the order its sections were inserted' ); } - public function testWhetherOneCanCheckWhetherConfigObjectsHaveACertainPropertyOrSection() - { - $config = new Config(array('a' => 'b', 'c' => array('d' => 'e'))); - - $this->assertTrue(isset($config->a), 'Config objects do not seem to implement __isset() properly'); - $this->assertTrue(isset($config->c->d), 'Config objects do not seem to implement __isset() properly'); - $this->assertFalse(isset($config->d), 'Config objects do not seem to implement __isset() properly'); - $this->assertFalse(isset($config->c->e), 'Config objects do not seem to implement __isset() properly'); - $this->assertTrue(isset($config['a']), 'Config object do not seem to implement offsetExists() properly'); - $this->assertFalse(isset($config['d']), 'Config object do not seem to implement offsetExists() properly'); - } - - public function testWhetherItIsPossibleToAccessProperties() - { - $config = new Config(array('a' => 'b', 'c' => null)); - - $this->assertEquals('b', $config->a, 'Config objects do not allow property access'); - $this->assertNull($config['c'], 'Config objects do not allow offset access'); - $this->assertNull($config->d, 'Config objects do not return NULL as default'); - } - - public function testWhetherItIsPossibleToSetPropertiesAndSections() + public function testWhetherOneCanCheckIfAConfigHasAnySections() { $config = new Config(); - $config->a = 'b'; - $config['c'] = array('d' => 'e'); + $this->assertTrue($config->isEmpty(), 'Config does not report that it is empty'); - $this->assertTrue(isset($config->a), 'Config objects do not allow to set properties'); - $this->assertTrue(isset($config->c), 'Config objects do not allow to set offsets'); - $this->assertInstanceOf( - get_class($config), - $config->c, - 'Config objects do not convert arrays to config objects when set' - ); + $config->setSection('test'); + $this->assertFalse($config->isEmpty(), 'Config does report that it is empty although it is not'); } - /** - * @expectedException LogicException - */ - public function testWhetherItIsNotPossibleToAppendProperties() + public function testWhetherItIsPossibleToRetrieveAllSectionNames() { - $config = new Config(); - $config[] = 'test'; - } - - public function testWhetherItIsPossibleToUnsetPropertiesAndSections() - { - $config = new Config(array('a' => 'b', 'c' => array('d' => 'e'))); - unset($config->a); - unset($config['c']); - - $this->assertFalse(isset($config->a), 'Config objects do not allow to unset properties'); - $this->assertFalse(isset($config->c), 'Config objects do not allow to unset sections'); - } - - /** - * @depends testWhetherConfigObjectsAreCountable - */ - public function testWhetherOneCanCheckIfAConfigObjectHasAnyPropertiesOrSections() - { - $config = new Config(); - $this->assertTrue($config->isEmpty(), 'Config objects do not report that they are empty'); - - $config->test = 'test'; - $this->assertFalse($config->isEmpty(), 'Config objects do report that they are empty although they are not'); - } - - /** - * @depends testWhetherItIsPossibleToAccessProperties - */ - public function testWhetherItIsPossibleToRetrieveDefaultValuesForNonExistentPropertiesOrSections() - { - $config = new Config(array('a' => 'b')); + $config = Config::fromArray(array('a' => array('b' => 'c'), 'd' => array('e' => 'f'))); $this->assertEquals( - 'b', - $config->get('a'), - 'Config objects do not return the actual value of existing properties' - ); - $this->assertNull( - $config->get('b'), - 'Config objects do not return NULL as default for non-existent properties' - ); - $this->assertEquals( - 'test', - $config->get('test', 'test'), - 'Config objects do not allow to define the default value to return for non-existent properties' - ); - } - - public function testWhetherItIsPossibleToRetrieveAllPropertyAndSectionNames() - { - $config = new Config(array('a' => 'b', 'c' => array('d' => 'e'))); - - $this->assertEquals( - array('a', 'c'), + array('a', 'd'), $config->keys(), - 'Config objects do not list property and section names correctly' + 'Config::keys does not list section names correctly' ); } - public function testWhetherConfigObjectsCanBeConvertedToArrays() + public function testWhetherConfigCanBeConvertedToAnArray() { - $config = new Config(array('a' => 'b', 'c' => array('d' => 'e'))); + $config = Config::fromArray(array('a' => 'b', 'c' => array('d' => 'e'))); $this->assertEquals( array('a' => 'b', 'c' => array('d' => 'e')), $config->toArray(), - 'Config objects cannot be correctly converted to arrays' + 'Config::toArray does not return the correct array' ); } - /** - * @depends testWhetherConfigObjectsCanBeConvertedToArrays - */ - public function testWhetherItIsPossibleToMergeConfigObjects() - { - $config = new Config(array('a' => 'b')); - - $config->merge(array('a' => 'bb', 'c' => 'd', 'e' => array('f' => 'g'))); - $this->assertEquals( - array('a' => 'bb', 'c' => 'd', 'e' => array('f' => 'g')), - $config->toArray(), - 'Config objects cannot be extended with arrays' - ); - - $config->merge(new Config(array('c' => array('d' => 'ee'), 'e' => array('h' => 'i')))); - $this->assertEquals( - array('a' => 'bb', 'c' => array('d' => 'ee'), 'e' => array('f' => 'g', 'h' => 'i')), - $config->toArray(), - 'Config objects cannot be extended with other Config objects' - ); - } - - /** - * @depends testWhetherItIsPossibleToAccessProperties - */ public function testWhetherItIsPossibleToDirectlyRetrieveASectionProperty() { - $config = new Config(array('a' => array('b' => 'c'))); + $config = Config::fromArray(array('a' => array('b' => 'c'))); $this->assertEquals( 'c', - $config->fromSection('a', 'b'), - 'Config::fromSection does not return the actual value of a section\'s property' + $config->get('a', 'b'), + 'Config::get does not return the actual value of a section\'s property' ); $this->assertNull( - $config->fromSection('a', 'c'), - 'Config::fromSection does not return NULL as default for non-existent section properties' + $config->get('a', 'c'), + 'Config::get does not return NULL as default for non-existent section properties' ); $this->assertNull( - $config->fromSection('b', 'c'), - 'Config::fromSection does not return NULL as default for non-existent sections' + $config->get('b', 'c'), + 'Config::get does not return NULL as default for non-existent sections' ); $this->assertEquals( 'test', - $config->fromSection('a', 'c', 'test'), - 'Config::fromSection does not return the given default value for non-existent section properties' + $config->get('a', 'c', 'test'), + 'Config::get does not return the given default value for non-existent section properties' ); $this->assertEquals( 'c', - $config->fromSection('a', 'b', 'test'), - 'Config::fromSection does not return the actual value of a section\'s property in case a default is given' + $config->get('a', 'b', 'test'), + 'Config::get does not return the actual value of a section\'s property in case a default is given' + ); + } + + public function testWhetherConfigReturnsSingleSections() + { + $config = Config::fromArray(array('a' => array('b' => 'c'))); + + $this->assertInstanceOf( + 'Icinga\Data\ConfigObject', + $config->getSection('a'), + 'Config::getSection does not return a known section' + ); + } + + /** + * @depends testWhetherConfigReturnsSingleSections + */ + public function testWhetherConfigSetsSingleSections() + { + $config = new Config(); + $config->setSection('a', array('b' => 'c')); + + $this->assertInstanceOf( + 'Icinga\Data\ConfigObject', + $config->getSection('a'), + 'Config::setSection does not set a new section' + ); + + $config->setSection('a', array('bb' => 'cc')); + + $this->assertNull( + $config->getSection('a')->b, + 'Config::setSection does not overwrite existing sections' + ); + $this->assertEquals( + 'cc', + $config->getSection('a')->bb, + 'Config::setSection does not overwrite existing sections' + ); + } + + /** + * @depends testWhetherConfigIsCountable + */ + public function testWhetherConfigRemovesSingleSections() + { + $config = Config::fromArray(array('a' => array('b' => 'c'), 'd' => array('e' => 'f'))); + $config->removeSection('a'); + + $this->assertEquals( + 1, + $config->count(), + 'Config::removeSection does not remove a known section' + ); + } + + /** + * @depends testWhetherConfigSetsSingleSections + */ + public function testWhetherConfigKnowsWhichSectionsItHas() + { + $config = new Config(); + $config->setSection('a'); + + $this->assertTrue( + $config->hasSection('a'), + 'Config::hasSection does not know anything about its sections' + ); + $this->assertFalse( + $config->hasSection('b'), + 'Config::hasSection does not know anything about its sections' ); } /** * @expectedException UnexpectedValueException - * @depends testWhetherItIsPossibleToAccessProperties */ public function testWhetherAnExceptionIsThrownWhenTryingToAccessASectionPropertyOnANonSection() { - $config = new Config(array('a' => 'b')); - $config->fromSection('a', 'b'); + $config = Config::fromArray(array('a' => 'b')); + $config->get('a', 'b'); } public function testWhetherConfigResolvePathReturnsValidAbsolutePaths() @@ -293,10 +205,10 @@ class ConfigTest extends BaseTestCase } /** - * @depends testWhetherConfigObjectsCanBeConvertedToArrays + * @depends testWhetherConfigCanBeConvertedToAnArray * @depends testWhetherConfigResolvePathReturnsValidAbsolutePaths */ - public function testWhetherItIsPossibleToInitializeAConfigObjectFromAIniFile() + public function testWhetherItIsPossibleToInitializeAConfigFromAIniFile() { $config = Config::fromIni(Config::resolvePath('config.ini')); @@ -332,7 +244,7 @@ class ConfigTest extends BaseTestCase } /** - * @depends testWhetherItIsPossibleToInitializeAConfigObjectFromAIniFile + * @depends testWhetherItIsPossibleToInitializeAConfigFromAIniFile */ public function testWhetherItIsPossibleToRetrieveApplicationConfiguration() { @@ -356,7 +268,7 @@ class ConfigTest extends BaseTestCase } /** - * @depends testWhetherItIsPossibleToInitializeAConfigObjectFromAIniFile + * @depends testWhetherItIsPossibleToInitializeAConfigFromAIniFile */ public function testWhetherItIsPossibleToRetrieveModuleConfiguration() { diff --git a/test/php/library/Icinga/Data/ConfigObjectTest.php b/test/php/library/Icinga/Data/ConfigObjectTest.php new file mode 100644 index 000000000..c4b825e58 --- /dev/null +++ b/test/php/library/Icinga/Data/ConfigObjectTest.php @@ -0,0 +1,224 @@ + 'b', + 'c' => 'd', + 'e' => array( + 'f' => 'g', + 'h' => 'i', + 'j' => array( + 'k' => 'l', + 'm' => 'n' + ) + ) + )); + + $this->assertInstanceOf( + get_class($config), + $config->e, + 'ConfigObject::__construct() does not accept two dimensional arrays' + ); + $this->assertInstanceOf( + get_class($config), + $config->e->j, + 'ConfigObject::__construct() does not accept multi dimensional arrays' + ); + } + + /** + * @depends testWhetherInitializingAConfigWithAssociativeArraysCreatesHierarchicalConfigObjects + */ + public function testWhetherItIsPossibleToCloneConfigObjects() + { + $config = new ConfigObject(array( + 'a' => 'b', + 'c' => array( + 'd' => 'e' + ) + )); + $newConfig = clone $config; + + $this->assertNotSame( + $config, + $newConfig, + 'Shallow cloning objects of type ConfigObject does not seem to work properly' + ); + $this->assertNotSame( + $config->c, + $newConfig->c, + 'Deep cloning objects of type ConfigObject does not seem to work properly' + ); + } + + public function testWhetherConfigObjectsAreCountable() + { + $config = new ConfigObject(array('a' => 'b', 'c' => array('d' => 'e'))); + + $this->assertInstanceOf('Countable', $config, 'ConfigObject objects do not implement interface `Countable\''); + $this->assertEquals(2, count($config), 'ConfigObject objects do not count properties and sections correctly'); + } + + public function testWhetherConfigObjectsAreTraversable() + { + $config = new ConfigObject(array('a' => 'b', 'c' => 'd')); + $config->e = 'f'; + + $this->assertInstanceOf('Iterator', $config, 'ConfigObject objects do not implement interface `Iterator\''); + + $actual = array(); + foreach ($config as $key => $value) { + $actual[$key] = $value; + } + + $this->assertEquals( + array('a' => 'b', 'c' => 'd', 'e' => 'f'), + $actual, + 'ConfigObject objects do not iterate properly in the order their values were inserted' + ); + } + + public function testWhetherOneCanCheckWhetherConfigObjectsHaveACertainPropertyOrSection() + { + $config = new ConfigObject(array('a' => 'b', 'c' => array('d' => 'e'))); + + $this->assertTrue(isset($config->a), 'ConfigObjects do not seem to implement __isset() properly'); + $this->assertTrue(isset($config->c->d), 'ConfigObjects do not seem to implement __isset() properly'); + $this->assertFalse(isset($config->d), 'ConfigObjects do not seem to implement __isset() properly'); + $this->assertFalse(isset($config->c->e), 'ConfigObjects do not seem to implement __isset() properly'); + $this->assertTrue(isset($config['a']), 'ConfigObject do not seem to implement offsetExists() properly'); + $this->assertFalse(isset($config['d']), 'ConfigObject do not seem to implement offsetExists() properly'); + } + + public function testWhetherItIsPossibleToAccessProperties() + { + $config = new ConfigObject(array('a' => 'b', 'c' => null)); + + $this->assertEquals('b', $config->a, 'ConfigObjects do not allow property access'); + $this->assertNull($config['c'], 'ConfigObjects do not allow offset access'); + $this->assertNull($config->d, 'ConfigObjects do not return NULL as default'); + } + + public function testWhetherItIsPossibleToSetPropertiesAndSections() + { + $config = new ConfigObject(); + $config->a = 'b'; + $config['c'] = array('d' => 'e'); + + $this->assertTrue(isset($config->a), 'ConfigObjects do not allow to set properties'); + $this->assertTrue(isset($config->c), 'ConfigObjects do not allow to set offsets'); + $this->assertInstanceOf( + get_class($config), + $config->c, + 'ConfigObjects do not convert arrays to config objects when set' + ); + } + + /** + * @expectedException LogicException + */ + public function testWhetherItIsNotPossibleToAppendProperties() + { + $config = new ConfigObject(); + $config[] = 'test'; + } + + public function testWhetherItIsPossibleToUnsetPropertiesAndSections() + { + $config = new ConfigObject(array('a' => 'b', 'c' => array('d' => 'e'))); + unset($config->a); + unset($config['c']); + + $this->assertFalse(isset($config->a), 'ConfigObjects do not allow to unset properties'); + $this->assertFalse(isset($config->c), 'ConfigObjects do not allow to unset sections'); + } + + /** + * @depends testWhetherConfigObjectsAreCountable + */ + public function testWhetherOneCanCheckIfAConfigObjectHasAnyPropertiesOrSections() + { + $config = new ConfigObject(); + $this->assertTrue($config->isEmpty(), 'ConfigObjects do not report that they are empty'); + + $config->test = 'test'; + $this->assertFalse($config->isEmpty(), 'ConfigObjects do report that they are empty although they are not'); + } + + /** + * @depends testWhetherItIsPossibleToAccessProperties + */ + public function testWhetherItIsPossibleToRetrieveDefaultValuesForNonExistentPropertiesOrSections() + { + $config = new ConfigObject(array('a' => 'b')); + + $this->assertEquals( + 'b', + $config->get('a'), + 'ConfigObjects do not return the actual value of existing properties' + ); + $this->assertNull( + $config->get('b'), + 'ConfigObjects do not return NULL as default for non-existent properties' + ); + $this->assertEquals( + 'test', + $config->get('test', 'test'), + 'ConfigObjects do not allow to define the default value to return for non-existent properties' + ); + } + + public function testWhetherItIsPossibleToRetrieveAllPropertyAndSectionNames() + { + $config = new ConfigObject(array('a' => 'b', 'c' => array('d' => 'e'))); + + $this->assertEquals( + array('a', 'c'), + $config->keys(), + 'ConfigObjects do not list property and section names correctly' + ); + } + + public function testWhetherConfigObjectsCanBeConvertedToArrays() + { + $config = new ConfigObject(array('a' => 'b', 'c' => array('d' => 'e'))); + + $this->assertEquals( + array('a' => 'b', 'c' => array('d' => 'e')), + $config->toArray(), + 'ConfigObjects cannot be correctly converted to arrays' + ); + } + + /** + * @depends testWhetherConfigObjectsCanBeConvertedToArrays + */ + public function testWhetherItIsPossibleToMergeConfigObjects() + { + $config = new ConfigObject(array('a' => 'b')); + + $config->merge(array('a' => 'bb', 'c' => 'd', 'e' => array('f' => 'g'))); + $this->assertEquals( + array('a' => 'bb', 'c' => 'd', 'e' => array('f' => 'g')), + $config->toArray(), + 'ConfigObjects cannot be extended with arrays' + ); + + $config->merge(new ConfigObject(array('c' => array('d' => 'ee'), 'e' => array('h' => 'i')))); + $this->assertEquals( + array('a' => 'bb', 'c' => array('d' => 'ee'), 'e' => array('f' => 'g', 'h' => 'i')), + $config->toArray(), + 'ConfigObjects cannot be extended with other ConfigObjects' + ); + } +}