diff options
-rw-r--r-- | core/command/config/system/setconfig.php | 128 | ||||
-rw-r--r-- | tests/core/command/config/system/setconfigtest.php | 156 |
2 files changed, 232 insertions, 52 deletions
diff --git a/core/command/config/system/setconfig.php b/core/command/config/system/setconfig.php index 0100ad2dfb0..83a969fd55d 100644 --- a/core/command/config/system/setconfig.php +++ b/core/command/config/system/setconfig.php @@ -48,8 +48,15 @@ class SetConfig extends Base { ->setDescription('Set a system config value') ->addArgument( 'name', - InputArgument::REQUIRED, - 'Name of the config to set' + InputArgument::REQUIRED | InputArgument::IS_ARRAY, + 'Name of the config parameter, specify multiple for array parameter' + ) + ->addOption( + 'type', + null, + InputOption::VALUE_REQUIRED, + 'Value type [string, integer, double, boolean]', + 'string' ) ->addOption( 'value', @@ -67,16 +74,119 @@ class SetConfig extends Base { } protected function execute(InputInterface $input, OutputInterface $output) { - $configName = $input->getArgument('name'); + $configNames = $input->getArgument('name'); + $configName = $configNames[0]; + $configValue = $this->castValue($input->getOption('value'), $input->getOption('type')); + $updateOnly = $input->getOption('update-only'); + + if (count($configNames) > 1) { + $existingValue = $this->systemConfig->getValue($configName); + + $newValue = $this->mergeArrayValue( + array_slice($configNames, 1), $existingValue, $configValue, $updateOnly + ); + + $this->systemConfig->setValue($configName, $newValue); + } else { + if ($updateOnly && !in_array($configName, $this->systemConfig->getKeys(), true)) { + throw new \UnexpectedValueException('Config parameter does not exist'); + } - if (!in_array($configName, $this->systemConfig->getKeys()) && $input->hasParameterOption('--update-only')) { - $output->writeln('<comment>Config value ' . $configName . ' not updated, as it has not been set before.</comment>'); - return 1; + $this->systemConfig->setValue($configName, $configValue); } - $configValue = $input->getOption('value'); - $this->systemConfig->setValue($configName, $configValue); - $output->writeln('<info>System config value ' . $configName . ' set to ' . $configValue . '</info>'); + $output->writeln('<info>System config value ' . implode(' => ', $configNames) . ' set to ' . $configValue . '</info>'); return 0; } + + /** + * @param string $value + * @param string $type + * @return mixed + * @throws \InvalidArgumentException + */ + protected function castValue($value, $type) { + if ($value === null) { + return null; + } + + $type = strtolower($type); + switch ($type) { + case 'string': + case 'str': + case 's': + return $value; + + case 'integer': + case 'int': + case 'i': + if (!is_numeric($value)) { + throw new \InvalidArgumentException('Non-numeric value specified'); + } + return (int) $value; + + case 'double': + case 'd': + case 'float': + case 'f': + if (!is_numeric($value)) { + throw new \InvalidArgumentException('Non-numeric value specified'); + } + return (double) $value; + + case 'boolean': + case 'bool': + case 'b': + $value = strtolower($value); + switch ($value) { + case 'true': + case 'yes': + case 'y': + case '1': + return true; + + case 'false': + case 'no': + case 'n': + case '0': + return false; + + default: + throw new \InvalidArgumentException('Unable to parse value as boolean'); + } + + default: + throw new \InvalidArgumentException('Invalid type'); + } + } + + /** + * @param array $configNames + * @param mixed $existingValues + * @param mixed $value + * @param bool $updateOnly + * @return array merged value + * @throws \UnexpectedValueException + */ + protected function mergeArrayValue(array $configNames, $existingValues, $value, $updateOnly) { + $configName = array_shift($configNames); + if (!is_array($existingValues)) { + $existingValues = []; + } + if (!empty($configNames)) { + if (isset($existingValues[$configName])) { + $existingValue = $existingValues[$configName]; + } else { + $existingValue = []; + } + $existingValues[$configName] = $this->mergeArrayValue($configNames, $existingValue, $value, $updateOnly); + } else { + if (!isset($existingValues[$configName]) && $updateOnly) { + throw new \UnexpectedValueException('Config parameter does not exist'); + } + $existingValues[$configName] = $value; + } + return $existingValues; + } + } diff --git a/tests/core/command/config/system/setconfigtest.php b/tests/core/command/config/system/setconfigtest.php index bbabee9b18f..14a6a980938 100644 --- a/tests/core/command/config/system/setconfigtest.php +++ b/tests/core/command/config/system/setconfigtest.php @@ -53,63 +53,133 @@ class SetConfigTest extends TestCase { public function setData() { return [ - [ - 'name', - 'newvalue', - true, - true, - true, - 'info', - ], - [ - 'name', - 'newvalue', - false, - true, - false, - 'comment', - ], + [['name'], 'newvalue', null, 'newvalue'], + [['a', 'b', 'c'], 'foobar', null, ['b' => ['c' => 'foobar']]], + [['a', 'b', 'c'], 'foobar', ['b' => ['d' => 'barfoo']], ['b' => ['d' => 'barfoo', 'c' => 'foobar']]], ]; } /** * @dataProvider setData * - * @param string $configName - * @param mixed $newValue - * @param bool $configExists - * @param bool $updateOnly - * @param bool $updated - * @param string $expectedMessage + * @param array $configNames + * @param string $newValue + * @param mixed $existingData + * @param mixed $expectedValue */ - public function testSet($configName, $newValue, $configExists, $updateOnly, $updated, $expectedMessage) { + public function testSet($configNames, $newValue, $existingData, $expectedValue) { $this->systemConfig->expects($this->once()) - ->method('getKeys') - ->willReturn($configExists ? [$configName] : []); + ->method('setValue') + ->with($configNames[0], $expectedValue); + $this->systemConfig->method('getValue') + ->with($configNames[0]) + ->willReturn($existingData); - if ($updated) { - $this->systemConfig->expects($this->once()) - ->method('setValue') - ->with($configName, $newValue); - } + $this->consoleInput->expects($this->once()) + ->method('getArgument') + ->with('name') + ->willReturn($configNames); + $this->consoleInput->method('getOption') + ->will($this->returnValueMap([ + ['value', $newValue], + ['type', 'string'], + ])); + + $this->invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); + } + + public function setUpdateOnlyProvider() { + return [ + [['name'], null], + [['a', 'b', 'c'], null], + [['a', 'b', 'c'], ['b' => 'foobar']], + [['a', 'b', 'c'], ['b' => ['d' => 'foobar']]], + ]; + } + + /** + * @dataProvider setUpdateOnlyProvider + * @expectedException \UnexpectedValueException + */ + public function testSetUpdateOnly($configNames, $existingData) { + $this->systemConfig->expects($this->never()) + ->method('setValue'); + $this->systemConfig->method('getValue') + ->with($configNames[0]) + ->willReturn($existingData); + $this->systemConfig->method('getKeys') + ->willReturn($existingData ? $configNames[0] : []); $this->consoleInput->expects($this->once()) ->method('getArgument') ->with('name') - ->willReturn($configName); - $this->consoleInput->expects($this->any()) - ->method('getOption') - ->with('value') - ->willReturn($newValue); - $this->consoleInput->expects($this->any()) - ->method('hasParameterOption') - ->with('--update-only') - ->willReturn($updateOnly); - - $this->consoleOutput->expects($this->any()) - ->method('writeln') - ->with($this->stringContains($expectedMessage)); + ->willReturn($configNames); + $this->consoleInput->method('getOption') + ->will($this->returnValueMap([ + ['value', 'foobar'], + ['type', 'string'], + ['update-only', true], + ])); $this->invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); } + + public function castValueProvider() { + return [ + [null, 'integer', null], + [null, 'string', null], + + ['abc', 'string', 'abc'], + ['dEF', 'str', 'dEF'], + ['123', 's', '123'], + + ['123', 'integer', 123], + ['456', 'int', 456], + ['-666', 'i', -666], + + // only use powers of 2 to avoid precision errors + ['2', 'double', 2.0], + ['0.25', 'd', 0.25], + ['0.5', 'float', 0.5], + ['0.125', 'f', 0.125], + + ['true', 'boolean', true], + ['false', 'bool', false], + ['yes', 'b', true], + ['no', 'b', false], + ['y', 'b', true], + ['n', 'b', false], + ['1', 'b', true], + ['0', 'b', false], + ]; + } + + /** + * @dataProvider castValueProvider + */ + public function testCastValue($value, $type, $expectedValue) { + $this->assertSame($expectedValue, + $this->invokePrivate($this->command, 'castValue', [$value, $type]) + ); + } + + public function castValueInvalidProvider() { + return [ + ['123', 'foobar'], + + ['abc', 'integer'], + ['76ggg', 'double'], + ['true', 'float'], + ['foobar', 'boolean'], + ]; + } + + /** + * @dataProvider castValueInvalidProvider + * @expectedException \InvalidArgumentException + */ + public function testCastValueInvalid($value, $type) { + $this->invokePrivate($this->command, 'castValue', [$value, $type]); + } + } |