From 3ef5f6e73c5b9ad7924ba219e688df57bd1a44af Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Wed, 18 Dec 2024 23:47:00 +0100 Subject: [PATCH] Add if-not-set modifier Add 'if-not-set' and 'if: not-set' modifiers for configuration paths. * **Model/Processor/ImportProcessor.php** - Add logic to handle 'if-not-set' and 'if: not-set' modifiers in the `process` method. - Add a private method `shouldSkipConfig` to check if a configuration value should be skipped based on the new modifiers. * **docs/config-import.md** - Add a section to document the new 'if-not-set' and 'if: not-set' modifiers. * **Test/Unit/Model/Processor/ImportProcessorTest.php** - Add tests to verify the functionality of the new 'if-not-set' and 'if: not-set' modifiers. - Add tests for scenarios with and without existing values for the new modifiers. --- Model/Processor/ImportProcessor.php | 24 +++ .../Model/Processor/ImportProcessorTest.php | 148 ++++++++++++++++++ docs/config-import.md | 22 +++ 3 files changed, 194 insertions(+) diff --git a/Model/Processor/ImportProcessor.php b/Model/Processor/ImportProcessor.php index 56ac276..c7730f6 100644 --- a/Model/Processor/ImportProcessor.php +++ b/Model/Processor/ImportProcessor.php @@ -119,6 +119,12 @@ public function process() continue; } + if ($this->shouldSkipConfig($configPath, $scopeType, $scopeId, $value)) { + $this->getOutput()->writeln(sprintf('[%s] [%s] %s => %s', $scopeType, $scopeId, $configPath, 'SKIPPED')); + + continue; + } + $this->configWriter->save($configPath, $value, $scopeType, $scopeId); $this->getOutput()->writeln(sprintf('[%s] [%s] %s => %s', $scopeType, $scopeId, $configPath, $value)); } @@ -126,6 +132,24 @@ public function process() } } + /** + * @param string $configPath + * @param string $scopeType + * @param int $scopeId + * @param mixed $value + * + * @return bool + */ + private function shouldSkipConfig($configPath, $scopeType, $scopeId, $value) + { + if (is_array($value) && (isset($value['if-not-set']) || (isset($value['if']) && $value['if'] === 'not-set'))) { + $existingValue = $this->configWriter->get($configPath, $scopeType, $scopeId); + return $existingValue !== null; + } + + return false; + } + /** * @param array $files * diff --git a/Test/Unit/Model/Processor/ImportProcessorTest.php b/Test/Unit/Model/Processor/ImportProcessorTest.php index c54362a..d9bdd9a 100644 --- a/Test/Unit/Model/Processor/ImportProcessorTest.php +++ b/Test/Unit/Model/Processor/ImportProcessorTest.php @@ -157,4 +157,152 @@ public function process(): void $processor->setReader($readerMock); $processor->process(); } + + /** + * @test + */ + public function processWithIfNotSetModifier(): void + { + $finderMock = $this->getMockBuilder(Finder::class) + ->onlyMethods(['find']) + ->getMock(); + $finderMock->expects($this->once())->method('find')->willReturn(['abc.yaml']); + + $parseResult = [ + 'test/config/custom_field_if_not_set' => [ + 'default' => [ + 0 => [ + 'if-not-set' => true, + 'default' => 1, + ], + ], + ], + ]; + + $readerMock = $this->getMockBuilder(YamlReader::class) + ->onlyMethods(['parse']) + ->getMock(); + $readerMock->expects($this->once())->method('parse')->willReturn($parseResult); + + $this->scopeValidatorMock->expects($this->once())->method('validate')->willReturn(true); + $this->configWriterMock->expects($this->once())->method('save'); + $this->configWriterMock->expects($this->once())->method('get')->willReturn(null); + + $processor = new ImportProcessor($this->configWriterMock, $this->scopeValidatorMock, $this->scopeConverterMock, []); + $processor->setOutput($this->outputMock); + $processor->setFinder($finderMock); + $processor->setReader($readerMock); + $processor->process(); + } + + /** + * @test + */ + public function processWithIfNotSetModifierAndExistingValue(): void + { + $finderMock = $this->getMockBuilder(Finder::class) + ->onlyMethods(['find']) + ->getMock(); + $finderMock->expects($this->once())->method('find')->willReturn(['abc.yaml']); + + $parseResult = [ + 'test/config/custom_field_if_not_set' => [ + 'default' => [ + 0 => [ + 'if-not-set' => true, + 'default' => 1, + ], + ], + ], + ]; + + $readerMock = $this->getMockBuilder(YamlReader::class) + ->onlyMethods(['parse']) + ->getMock(); + $readerMock->expects($this->once())->method('parse')->willReturn($parseResult); + + $this->scopeValidatorMock->expects($this->once())->method('validate')->willReturn(true); + $this->configWriterMock->expects($this->never())->method('save'); + $this->configWriterMock->expects($this->once())->method('get')->willReturn('existing_value'); + + $processor = new ImportProcessor($this->configWriterMock, $this->scopeValidatorMock, $this->scopeConverterMock, []); + $processor->setOutput($this->outputMock); + $processor->setFinder($finderMock); + $processor->setReader($readerMock); + $processor->process(); + } + + /** + * @test + */ + public function processWithIfNotSetModifierAlternativeSyntax(): void + { + $finderMock = $this->getMockBuilder(Finder::class) + ->onlyMethods(['find']) + ->getMock(); + $finderMock->expects($this->once())->method('find')->willReturn(['abc.yaml']); + + $parseResult = [ + 'test/config/custom_field_if_not_set' => [ + 'default' => [ + 0 => [ + 'if' => 'not-set', + 'default' => 1, + ], + ], + ], + ]; + + $readerMock = $this->getMockBuilder(YamlReader::class) + ->onlyMethods(['parse']) + ->getMock(); + $readerMock->expects($this->once())->method('parse')->willReturn($parseResult); + + $this->scopeValidatorMock->expects($this->once())->method('validate')->willReturn(true); + $this->configWriterMock->expects($this->once())->method('save'); + $this->configWriterMock->expects($this->once())->method('get')->willReturn(null); + + $processor = new ImportProcessor($this->configWriterMock, $this->scopeValidatorMock, $this->scopeConverterMock, []); + $processor->setOutput($this->outputMock); + $processor->setFinder($finderMock); + $processor->setReader($readerMock); + $processor->process(); + } + + /** + * @test + */ + public function processWithIfNotSetModifierAlternativeSyntaxAndExistingValue(): void + { + $finderMock = $this->getMockBuilder(Finder::class) + ->onlyMethods(['find']) + ->getMock(); + $finderMock->expects($this->once())->method('find')->willReturn(['abc.yaml']); + + $parseResult = [ + 'test/config/custom_field_if_not_set' => [ + 'default' => [ + 0 => [ + 'if' => 'not-set', + 'default' => 1, + ], + ], + ], + ]; + + $readerMock = $this->getMockBuilder(YamlReader::class) + ->onlyMethods(['parse']) + ->getMock(); + $readerMock->expects($this->once())->method('parse')->willReturn($parseResult); + + $this->scopeValidatorMock->expects($this->once())->method('validate')->willReturn(true); + $this->configWriterMock->expects($this->never())->method('save'); + $this->configWriterMock->expects($this->once())->method('get')->willReturn('existing_value'); + + $processor = new ImportProcessor($this->configWriterMock, $this->scopeValidatorMock, $this->scopeConverterMock, []); + $processor->setOutput($this->outputMock); + $processor->setFinder($finderMock); + $processor->setReader($readerMock); + $processor->process(); + } } diff --git a/docs/config-import.md b/docs/config-import.md index 4b4e8c4..bbe0bb1 100644 --- a/docs/config-import.md +++ b/docs/config-import.md @@ -123,6 +123,28 @@ vendorx/general/api_key: This is helpful when you've got the same settings across different environments but want to keep one environment ( `X` env) unchanged without showing the exact value in the config file. It's a common scenario, especially when dealing with sensitive data. You really should only keep that kind of info in the environment’s database, not in your GIT repo. +### If-Not-Set Modifier + +To conditionally set a configuration value only if it has not been set previously, use the `if-not-set` or `if: not-set` modifier. This is useful for setting initial configuration values for third-party extensions. + +For example, this might be the content of your config file: + +``` +path/to/config: + if-not-set: true + default: + 0: 1 +``` + +Or + +``` +path/to/config: + if: not-set + default: + 0: 1 +``` + ### Recursive folder setup If you choose to store your configuration files in subdirectories, e.g. per vendor, the recommended folder setup should look like this: