Merge branch 'feature/ini-writer-for-config-4352'

resolves #4352
This commit is contained in:
Jannis Moßhammer 2013-08-07 16:31:19 +02:00
commit 4149328216
25 changed files with 1008 additions and 23 deletions

View File

@ -28,7 +28,7 @@ namespace Icinga\Web\Form;
use Icinga\Web\Form;
use Icinga\Web\Session;
use Icinga\Web\Notification;
use Icinga\Application\Config;
use Icinga\Config\Config;
/**
* Class SettingsForm

View File

@ -1,13 +1,13 @@
# Application and Module Configuration
The \Icinga\Application\Config class is a general purpose service to help you find, load and save
The \Icinga\Config\Config class is a general purpose service to help you find, load and save
configuration data. It is used both by the Icinga 2 Web modules and the framework itself. With
INI files as source it enables you to store configuration in a familiar format. Icinga 2 Web
defines some configuration files for its own purposes. Please note that both modules and framework
keep their main configuration in the INI file called config.ini. Here's some example code:
<?php
use \Icinga\Application\Config as IcingaConfig;
use \Icinga\Config\Config as IcingaConfig;
// Retrieve the default timezone using 'Europe/Berlin' in case it is not set
IcingaConfig::app()->global->get('defaultTimezone', 'Europe/Berlin');

View File

@ -31,6 +31,7 @@ namespace Icinga\Application;
use Icinga\Application\Modules\Manager as ModuleManager;
use Icinga\Application\Platform;
use Icinga\Exception\ProgrammingError;
use Icinga\Config\Config;
use Zend_Loader_Autoloader;
use Icinga\Exception\ConfigurationError;

View File

@ -29,7 +29,7 @@
namespace Icinga\Application\Modules;
use Icinga\Application\ApplicationBootstrap;
use Icinga\Application\Config;
use Icinga\Config\Config;
use Icinga\Application\Icinga;
use Icinga\Web\Hook;
use Zend_Controller_Router_Route as Route;

View File

@ -32,7 +32,7 @@ use Icinga\User;
use Icinga\Authentication\UserBackend;
use Icinga\Authentication\Credentials;
use Icinga\Protocol\Ldap;
use Icinga\Application\Config as IcingaConfig;
use Icinga\Config\Config as IcingaConfig;
/**
* User authentication backend (@see Icinga\Authentication\UserBackend) for

View File

@ -29,7 +29,7 @@
namespace Icinga\Authentication;
use Icinga\Application\Logger;
use Icinga\Application\Config as IcingaConfig;
use Icinga\Config\Config as IcingaConfig;
use Icinga\Exception\ConfigurationError as ConfigError;
use Icinga\User;

View File

@ -2,7 +2,7 @@
namespace Icinga;
use Icinga\Application\Config as IcingaConfig;
use Icinga\Config\Config as IcingaConfig;
use Icinga\Authentication\Manager as AuthManager;
class Backend

View File

@ -26,9 +26,8 @@
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Application;
namespace Icinga\Config;
use Icinga\Protocol\Ldap\Exception;
use Zend_Config_Ini;
/**
@ -71,7 +70,7 @@ class Config extends Zend_Config_Ini
public function __construct($filename)
{
if (!@is_readable($filename)) {
throw new Exception('Cannot read config file: ' . $filename);
throw new \Exception('Cannot read config file: ' . $filename);
};
$this->configFile = $filename;
$section = null;

View File

@ -0,0 +1,472 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Config;
use Icinga\Exception\ConfigurationError;
/**
* Edit the sections and keys of an ini in-place
*/
class IniEditor
{
/**
* The text that is edited
*
* @var array
*/
private $text;
/**
* The symbol that is used to separate keys
*
* @var string
*/
private $nestSeparator = '.';
/**
* Create a new IniEditor
*
* @param $content The content of the ini as string
*/
public function __construct($content)
{
$this->text = explode("\n",$content);
}
/**
* Set the value of the given key.
*
* @param array $key The key to set
* @param $value The value to set
* @param $section The section to insert to.
*/
public function set(array $key, $value, $section = null)
{
$line = $this->getKeyLine($key,$section);
if ($line === -1) {
$this->insert($key,$value,$section);
} else {
$content = $this->formatKeyValuePair($key,$value);
$this->updateLine($line,$content);
}
}
/**
* Reset the value of the given array element
*
* @param $key The key of the array value
* @param $section The section of the array.
*/
public function resetArrayElement(array $key, $section = null)
{
$line = $this->getArrayEl($key, $section);
if ($line !== -1) {
$this->deleteLine($line);
}
}
/**
* Set the value for an array element
*
* @param array $key The key of the property
* @param $value The value of the property
* @param $section The section to use
*/
public function setArrayElement(array $key, $value, $section = null)
{
$line = $this->getArrayEl($key,$section);
if ($line !== -1) {
if (isset($section)) {
$this->updateLine($line,$this->formatKeyValuePair($key,$value));
} else {
$section = $key[0];
unset($key[0]);
$this->deleteLine($line);
$this->setSection($section);
$this->insert($key,$value,$section);
}
} else {
$this->insert($key,$value,$section);
}
}
/**
* Get the line of an array element
*
* @param array $key The key of the property.
* @param $value The value
* @param $section The section to use
*
* @return The line of the array element.
*/
private function getArrayEl(array $key, $section = null)
{
$line = isset($section) ? $this->getSectionLine($section) +1 : 0;
$index = array_pop($key);
$formatted = $this->formatKey($key);
for (; $line < count($this->text); $line++) {
$l = $this->text[$line];
if ($this->isSectionDeclaration($l)) {
return -1;
}
if (strlen($formatted) > 0) {
if (preg_match('/^'.$formatted.'\[\]=/',$l) === 1 ||
preg_match(
'/^'.$formatted.$this->nestSeparator.$index.'=/',$l) === 1) {
return $line;
}
} else {
if (preg_match('/^'.$index.'=/',$l) === 1 ) {
return $line;
}
}
}
return -1;
}
/**
* When it exists, set the key back to null
*
* @param array $key The key to reset
* @param $section The section of the key
*/
public function reset(array $key, $section = null)
{
$line = $this->getKeyLine($key,$section);
if ($line === -1) {
return;
}
$this->deleteLine($line);
}
/**
* Create the section if it does not exist and set the properties
*
* @param $section The section name
* @param $extend The section that should be extended by this section
*/
public function setSection($section, $extend = null)
{
if (isset($extend)) {
$decl = '['.$section.' : '.$extend.']';
} else {
$decl = '['.$section.']';
}
$line = $this->getSectionLine($section);
if ($line !== -1) {
$this->deleteLine($line);
$this->insertAtLine($line,$decl);
} else {
$line = $this->getLastLine();
$this->insertAtLine($line,$decl);
}
}
/**
* Remove a section declaration
*
* @param $section The section name
*/
public function removeSection($section)
{
$line = $this->getSectionLine($section);
if ($line !== -1) {
$this->deleteLine($line);
}
}
/**
* Insert the key at the end of the corresponding section
*
* @param array $key The key to insert
* @param $value The value to insert
* @param array $key The key to insert
*/
private function insert(array $key, $value, $section = null)
{
$line = $this->getSectionEnd($section);
$content = $this->formatKeyValuePair($key,$value);
$this->insertAtLine($line,$content);
}
/**
* Get the edited text
*
* @return string The edited text
*/
public function getText()
{
$this->cleanUpWhitespaces();
return implode("\n",$this->text);
}
/**
* Remove all unneeded line breaks between sections
*/
private function cleanUpWhitespaces()
{
$i = count($this->text) - 1;
for (; $i >= 0; $i--) {
$line = $this->text[$i];
if ($this->isSectionDeclaration($line)) {
$i--;
$line = $this->text[$i];
/*
* Ignore comments that are glued to the section declaration
*/
while ($i > 0 && preg_match('/^;/',$line) === 1) {
$i--;
$line = $this->text[$i];
}
/*
* Remove whitespaces between the sections
*/
while ($i > 0 && preg_match('/^[\s]*$/',$line) === 1) {
$this->deleteLine($i);
$i--;
$line = $this->text[$i];
}
/*
* Add a single whitespace
*/
if ($i !== 0) {
$this->insertAtLine($i + 1,'');
}
}
}
}
/**
* Insert the text at line $lineNr
*
* @param $lineNr The line nr the inserted line should have
* @param $toInsert The text that will be inserted
*/
private function insertAtLine($lineNr, $toInsert)
{
$this->text = IniEditor::insertIntoArray($this->text,$lineNr,$toInsert);
}
/**
* Update the line $lineNr
*
* @param $lineNr The line number of the target line
* @param $toInsert The new line content
*/
private function updateLine($lineNr, $content)
{
$this->text[$lineNr] = $content;
}
/**
* Delete the line $lineNr
*
* @param $lineNr The lineNr starting at 0
*/
private function deleteLine($lineNr)
{
$this->text = $this->removeFromArray($this->text,$lineNr);
}
/**
* Format a key-value pair to an INI file-entry
*
* @param array $key The key
* @param $value The value
*
* @return string The formatted key-value pair
*/
private function formatKeyValuePair(array $key, $value)
{
return $this->formatKey($key).'='.$this->formatValue($value);
}
/**
* Format a key to an INI key
*/
private function formatKey(array $key)
{
return implode($this->nestSeparator,$key);
}
/**
* Get the first line after the given $section
*
* @param $section The name of the section
*
* @return int The line number of the section
*/
private function getSectionEnd($section = null)
{
$i = 0;
$started = isset($section) ? false: true;
foreach ($this->text as $line) {
if ($started && preg_match('/^\[/',$line) === 1) {
if ($i === 0) {
return $i;
}
/*
* ignore all comments 'glued' to the next section, to allow section
* comments in front of sections
*/
while (preg_match('/^;/',$this->text[$i - 1]) === 1) {
$i--;
}
return $i;
} elseif (preg_match('/^\['.$section.'.*\]/',$line) === 1) {
$started = true;
}
$i++;
}
if (!$started) {
return -1;
}
return $i;
}
/**
* Check if the given line contains a section declaration
*
* @param $lineContent The content of the line
* @param string $section The optional section name that will be assumed
*
* @return bool
*/
private function isSectionDeclaration($lineContent, $section = "")
{
return preg_match('/^\[/'.$section,$lineContent) === 1;
}
/**
* Get the line where the section begins
*
* @param $section The section
*
* @return int The line number
*/
private function getSectionLine($section)
{
$i = 0;
foreach ($this->text as $line) {
if (preg_match('/^\['.$section.'/',$line)) {
return $i;
}
$i++;
}
return -1;
}
/**
* Get the line number where the given key occurs
*
* @param array $keys The key and its parents
* @param $section The section of the key
*
* @return int The line number
*/
private function getKeyLine(array $keys, $section = null)
{
$key = implode($this->nestSeparator,$keys);
$inSection = isset($section) ? false : true;
$i = 0;
foreach ($this->text as $line) {
if ($inSection && preg_match('/^\[/',$line) === 1) {
return -1;
}
if ($inSection && preg_match('/^'.$key.'=/',$line) === 1) {
return $i;
}
if (!$inSection && preg_match('/^\[ *'.$section.' *[\]:]/',$line) === 1) {
$inSection = true;
}
$i++;
}
return -1;
}
/**
* Get the last line number occurring in the text
*
* @return The line number of the last line
*/
private function getLastLine()
{
return count($this->text);
}
/**
* Insert a new element into a specific position of an array
*
* @param $array The array to use
* @param $pos The target position
* @param $element The element to insert
*
* @return array The changed array
*/
private static function insertIntoArray($array, $pos, $element)
{
array_splice($array, $pos, 0, $element);
return $array;
}
/**
* Remove an element from an array
*
* @param $array The array to use
* @param $pos The position to remove
*/
private function removeFromArray($array, $pos)
{
unset($array[$pos]);
return array_values($array);
}
/**
* Prepare a value for INI
*
* @param $value The value of the string
*
* @return string The formatted value
*
* @throws Zend_Config_Exception
*/
private function formatValue($value)
{
if (is_integer($value) || is_float($value)) {
return $value;
} elseif (is_bool($value)) {
return ($value ? 'true' : 'false');
} elseif (strpos($value, '"') === false) {
return '"' . $value . '"';
} else {
throw new ConfigurationError('Value can not contain double quotes "');
}
}
}

View File

@ -0,0 +1,184 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Config;
use Zend_Config;
use Zend_Config_Ini;
/**
* A ini file adapter that respects the file structure and the comments of already
* existing ini files
*/
class PreservingIniWriter extends \Zend_Config_Writer_FileAbstract
{
/**
* Render the Zend_Config into a config file string
*
* @return string
*/
public function render()
{
$oldconfig = new Zend_Config_Ini($this->_filename);
$newconfig = $this->_config;
$editor = new IniEditor(file_get_contents($this->_filename));
$this->diffConfigs($oldconfig,$newconfig,$editor);
return $editor->getText();
}
/**
* Create a property diff and apply the changes to the editor
*
* @param Zend_Config $oldconfig The config representing the state before the change
* @param Zend_Config $newconfig The config representing the state after the change
* @param IniEditor $eeditor The editor that should be used to edit the old config file
* @param array $parents The parent keys that should be respected when editing the config
*/
private function diffConfigs(
Zend_Config $oldconfig,
Zend_Config $newconfig,
IniEditor $editor,
array $parents = array()
) {
$this->diffPropertyUpdates($oldconfig,$newconfig,$editor,$parents);
$this->diffPropertyDeletions($oldconfig,$newconfig,$editor,$parents);
}
/**
* Search for created and updated properties and use the editor to create or update these entries
*
* @param Zend_Config $oldconfig The config representing the state before the change
* @param Zend_Config $newconfig The config representing the state after the change
* @param IniEditor $eeditor The editor that should be used to edit the old config file
* @param array $parents The parent keys that should be respected when editing the config
*/
private function diffPropertyUpdates(
Zend_Config $oldconfig,
Zend_Config $newconfig,
IniEditor $editor,
array $parents = array()
) {
/*
* The current section. This value is null when processing
* the section-less root element
*/
$section = empty($parents) ? null : $parents[0];
/*
* Iterate over all properties in the new configuration file and search for changes
*/
foreach ($newconfig as $key => $value) {
$oldvalue = $oldconfig->get($key);
$nextParents = array_merge($parents,array($key));
$keyIdentifier = empty($parents) ?
array($key) : array_slice($nextParents,1,null,true);
if ($value instanceof Zend_Config) {
/*
* The value is a nested Zend_Config, handle it recursively
*/
if (!isset($section)) {
/*
* Update the section declaration
*/
$extends = $newconfig->getExtends();
$extend = array_key_exists($key,$extends) ?
$extends[$key] : null;
$editor->setSection($key,$extend);
}
if (!isset($oldvalue)) {
$oldvalue = new Zend_Config(array());
}
$this->diffConfigs($oldvalue,$value,$editor,$nextParents);
} else {
/*
* The value is a plain value, use the editor to set it
*/
if (is_numeric($key)) {
$editor->setArrayElement($keyIdentifier,$value,$section);
} else {
$editor->set($keyIdentifier,$value,$section);
}
}
}
}
/**
* Search for deleted properties and use the editor to delete these entries
*
* @param Zend_Config $oldconfig The config representing the state before the change
* @param Zend_Config $newconfig The config representing the state after the change
* @param IniEditor $eeditor The editor that should be used to edit the old config file
* @param array $parents The parent keys that should be respected when editing the config
*/
private function diffPropertyDeletions(
Zend_Config $oldconfig,
Zend_Config $newconfig,
IniEditor $editor,
array $parents = array()
) {
/*
* The current section. This value is null when processing
* the section-less root element
*/
$section = empty($parents) ? null : $parents[0];
/*
* Iterate over all properties in the old configuration file and search for
* deleted properties
*/
foreach ($oldconfig as $key => $value) {
$nextParents = array_merge($parents,array($key));
$newvalue = $newconfig->get($key);
$keyIdentifier = empty($parents) ?
array($key) : array_slice($nextParents,1,null,true);
if (!isset($newvalue)) {
if ($value instanceof Zend_Config) {
/*
* The deleted value is a nested Zend_Config, handle it recursively
*/
$this->diffConfigs($value,new Zend_Config(array()),$editor,$nextParents);
if (!isset($section)) {
$editor->removeSection($key);
}
} else {
/*
* The deleted value is a plain value, use the editor to delete it
*/
if (is_numeric($key)) {
$editor->resetArrayElement($keyIdentifier,$section);
} else {
$editor->reset($keyIdentifier,$section);
}
}
}
}
}
}

View File

@ -6,7 +6,7 @@
namespace Icinga\Protocol\Ldap;
use Icinga\Application\Platform;
use Icinga\Application\Config;
use Icinga\Config\Config;
use Icinga\Application\Logger as Log;
/**

View File

@ -31,7 +31,7 @@ namespace Icinga\Web;
use Icinga\Authentication\Manager as AuthManager;
use Icinga\Application\Benchmark;
use Icinga\Exception;
use Icinga\Application\Config;
use Icinga\Config\Config;
use Icinga\Web\Notification;
use Zend_Layout as ZfLayout;
use Zend_Controller_Action as ZfController;

View File

@ -28,7 +28,7 @@
*/
namespace Icinga\Web;
use Icinga\Application\Config as IcingaConfig;
use Icinga\Config\Config as IcingaConfig;
use Icinga\Application\Icinga;
/**

View File

@ -3,7 +3,7 @@
namespace Icinga\Web\Widget;
use Icinga\Application\Icinga;
use Icinga\Application\Config;
use Icinga\Config\Config;
use Icinga\Web\Widget;
use Icinga\Web\Widget\Dashboard\Pane;
use Icinga\Web\Url;

View File

@ -31,7 +31,7 @@
use Icinga\Application\Benchmark;
use Icinga\Application\Icinga;
use Icinga\Backend;
use Icinga\Application\Config;
use Icinga\Config\Config;
use Icinga\Application\Logger;
use Icinga\Authentication\Manager;
use Icinga\Web\Form;

View File

@ -2,7 +2,7 @@
namespace Monitoring;
use Icinga\Application\Config as IcingaConfig;
use Icinga\Config\Config as IcingaConfig;
use Icinga\Authentication\Manager as AuthManager;
use Exception;

View File

@ -2,7 +2,7 @@
namespace Monitoring;
use Icinga\Application\Config;
use Icinga\Config\Config;
use Icinga\Web\Session;
use Exception;

View File

@ -10,7 +10,7 @@ require 'Zend/Controller/Action.php';
require '../../library/Icinga/Exception/ProgrammingError.php';
require '../../library/Icinga/Application/Benchmark.php';
require '../../library/Icinga/Application/Config.php';
require '../../library/Icinga/Config/Config.php';
require '../../library/Icinga/Application/Icinga.php';
require '../../library/Icinga/Web/ActionController.php';
require '../../library/Icinga/Web/Notification.php';

View File

@ -36,7 +36,7 @@ require_once('Zend/Config/Ini.php');
require_once('Zend/Db.php');
require_once('../../library/Icinga/Authentication/UserBackend.php');
require_once('../../library/Icinga/Protocol/Ldap/Exception.php');
require_once('../../library/Icinga/Application/Config.php');
require_once('../../library/Icinga/Config/Config.php');
require_once('../../library/Icinga/Authentication/Credentials.php');
require_once('../../library/Icinga/Authentication/Backend/DbUserBackend.php');
require_once('../../library/Icinga/User.php');
@ -45,7 +45,7 @@ use Icinga\Authentication\Backend\DbUserBackend;
use Icinga\Util\Crypto;
use Icinga\Authentication\Credentials;
use Icinga\User;
use Icinga\Application\Config;
use Icinga\Config\Config;
/**
*

View File

@ -6,15 +6,15 @@
namespace Tests\Icinga\Application;
require_once 'Zend/Config/Ini.php';
require_once dirname(__FILE__) . '/../../../../../library/Icinga/Application/Config.php';
require_once '../../library/Icinga/Config/Config.php';
use Icinga\Application\Config as IcingaConfig;
use Icinga\Config\Config as IcingaConfig;
class ConfigTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
IcingaConfig::$configDir = dirname(__FILE__) . '/Config/files';
IcingaConfig::$configDir = dirname(__FILE__) . '/files';
}
public function testAppConfig()

View File

@ -0,0 +1,329 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\PreservingIniWriterTest;
require_once 'Zend/Config.php';
require_once 'Zend/Config/Ini.php';
require_once 'Zend/Config/Writer/Ini.php';
require_once('../../library/Icinga/Config/IniEditor.php');
require_once('../../library/Icinga/Config/PreservingIniWriter.php');
use Icinga\Config\PreservingIniWriter;
class PreservingIniWriterTest extends \PHPUnit_Framework_TestCase {
private $tmpfiles = array();
/**
* Set up the test fixture
*/
public function setUp()
{
$ini =
';1
trailing1="wert"
arr[]="0"
arr[]="1"
arr[]="2"
arr[]="3"
;2
;3
Trailing2=
[parent]
;4
;5
;6
;7
list[]="zero"
list[]="one"
;8
;9
many.many.nests="value"
propOne="value1"
propTwo="2"
propThree=
propFour="true"
Prop5="true"
[child : parent]
PropOne="overwritten"
;10
';
$this->writeToTmp('orig',$ini);
$this->writeToTmp('sonst','');
$emptyIni = " ";
$this->writeToTmp('empty',$emptyIni);
$editedIni =
';1
;2
;3
;4
;5
trailing1="1"
[parent]
;6
;7
;8
;9
;10
propOne="value1"
[different]
prop1="1"
prop2="2"
[nested : different]
prop2="5"
';
$this->writeToTmp('edited',$editedIni);
}
/**
* Write a string to a temporary file
*
* @param $name The name of the temporary file
* @param $content The content
*/
private function writeToTmp($name,$content)
{
$this->tmpfiles[$name] =
tempnam(dirname(__FILE__) . '/temp',$name);
$file = fopen($this->tmpfiles[$name],'w');
fwrite($file,$content);
fflush($file);
fclose($file);
}
/**
* Tear down the test fixture
*/
public function tearDown()
{
foreach ($this->tmpfiles as $filename) {
unlink($filename);
}
}
/**
* Test if the IniWriter works correctly when writing the changes back to
* the same ini file
*/
public function testPropertyChangeSameConfig()
{
$this->changeConfigAndWriteToFile('orig');
$config = new \Zend_Config_Ini(
$this->tmpfiles['orig'],null,array('allowModifications' => true)
);
$this->checkConfigProperties($config);
$this->checkConfigComments($this->tmpfiles['orig']);
}
/**
* Test if the IniWriter works correctly when writing to an empty file
*/
public function testPropertyChangeEmptyConfig()
{
$this->changeConfigAndWriteToFile('empty');
$config = new \Zend_Config_Ini(
$this->tmpfiles['empty'],null,array('allowModifications' => true)
);
$this->checkConfigProperties($config);
}
/**
* Test if the IniWriter works correctly when writing to a file with changes
*/
public function testPropertyChangeEditedConfig()
{
$original = $this->changeConfigAndWriteToFile('edited');
$config = new \Zend_Config_Ini(
$this->tmpfiles['edited'],null,array('allowModifications' => true)
);
$this->checkConfigProperties($config);
$this->checkConfigComments($this->tmpfiles['edited']);
}
/**
* Change the test config and write the changes to the temporary
* file $tmpFile
*
* @param $tmpFile
*/
private function changeConfigAndWriteToFile($tmpFile)
{
$config = $this->createTestConfig();
$this->alterConfig($config);
$writer = new PreservingIniWriter(
array('config' => $config,'filename' => $this->tmpfiles[$tmpFile])
);
$writer->write();
return $config;
}
/**
* Check if all comments are present
*
* @param $file
*/
private function checkConfigComments($file)
{
$i = 0;
foreach (explode("\n",file_get_contents($file)) as $line) {
if (preg_match('/^;/',$line)) {
$i++;
$this->assertEquals(
$i,intval(substr($line,1)),
'Comment unchanged'
);
}
}
$this->assertEquals(10,$i,'All comments exist');
}
/**
* Test if all configuration properties are set correctly
*
* @param $config
*/
private function checkConfigProperties($config)
{
$this->assertEquals('val',$config->Trailing2,
'Section-less property updated.');
$this->assertNull($config->trailing1,
'Section-less property deleted.');
$this->assertEquals('value',$config->new,
'Section-less property created.');
$this->assertEquals('0',$config->arr->{0},
'Value persisted in array');
$this->assertEquals('update',$config->arr->{2},
'Value changed in array');
$this->assertEquals('arrvalue',$config->arr->{4},
'Value added to array');
$this->assertEquals('',$config->parent->propOne,
'Section property deleted.');
$this->assertEquals("2",$config->parent->propTwo,
'Section property numerical unchanged.');
$this->assertEquals('update',$config->parent->propThree,
'Section property updated.');
$this->assertEquals("true",$config->parent->propFour,
'Section property boolean unchanged.');
$this->assertEquals("1",$config->parent->new,
'Section property numerical created.');
$this->assertNull($config->parent->list->{0},
'Section array deleted');
$this->assertEquals('new',$config->parent->list->{1},
'Section array changed.');
$this->assertEquals('changed',$config->parent->many->many->nests,
'Change strongy nested value.');
$this->assertEquals('new',$config->parent->many->many->new,
'Ccreate strongy nested value.');
$this->assertEquals('overwritten',$config->child->PropOne,
'Overridden inherited property unchanged.');
$this->assertEquals('somethingNew',$config->child->propTwo,
'Inherited property changed.');
$this->assertEquals('test',$config->child->create,
'Non-inherited property created.');
$this->assertInstanceOf('Zend_Config',$config->newsection,
'New section created.');
$extends = $config->getExtends();
$this->assertEquals('child',$extends['newsection'],
'New inheritance created.');
}
/**
* Change the content of a Zend_Config
*
* @param Zend_Config $config
*/
private function alterConfig(\Zend_Config $config)
{
$config->Trailing2 = 'val';
unset($config->trailing1);
$config->new = 'value';
$config->arr->{2} = "update";
$config->arr->{4} = "arrvalue";
$config->parent->propOne = null;
$config->parent->propThree = 'update';
$config->parent->new = 1;
unset($config->parent->list->{0});
$config->parent->list->{1} = 'new';
$config->parent->many->many->nests = "changed";
$config->parent->many->many->new = "new";
$config->child->propTwo = 'somethingNew';
$config->child->create = 'test';
$config->newsection = array();
$config->newsection->p1 = "prop";
$config->newsection->P2 = "prop";
$config->setExtend('newsection','child');
}
/**
* Create the the configuration that will be used for the tests.
*/
private function createTestConfig()
{
return new \Zend_Config_Ini(
$this->tmpfiles['orig'],
null,
array('allowModifications' => true)
);
}
}