Skip to content

Commit

Permalink
Merge branch 'vpodorozh-feature/allow-to-keep-configs-untouched'
Browse files Browse the repository at this point in the history
  • Loading branch information
therouv committed Apr 18, 2024
2 parents 640a0c5 + d122159 commit f7fd4b3
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 28 deletions.
97 changes: 70 additions & 27 deletions Model/Processor/ImportProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
class ImportProcessor extends AbstractProcessor implements ImportProcessorInterface
{
private const DELETE_CONFIG_FLAG = '!!DELETE';
private const KEEP_CONFIG_FLAG = '!!KEEP';

/**
* @var WriterInterface
*/
Expand Down Expand Up @@ -85,46 +87,87 @@ public function __construct(
public function process()
{
$files = $this->finder->find();
if (0 === count($files) && false === $this->getInput()->getOption('allow-empty-directories')) {
throw new \InvalidArgumentException('No files found for format: *.' . $this->getFormat());
} else {
$this->getOutput()->writeln('No files found for format: *.' . $this->getFormat());
$this->getOutput()->writeln('Maybe this is expected behaviour, because you passed the --allow-empty-directories option.');

if (0 === count($files)) {
if (false === $this->getInput()->getOption('allow-empty-directories')) {
throw new \InvalidArgumentException('No files found for format: *.' . $this->getFormat());
} else {
$this->getOutput()->writeln('No files found for format: *.' . $this->getFormat());
$this->getOutput()->writeln('Maybe this is expected behaviour, because you passed the --allow-empty-directories option.');
$this->getOutput()->writeln(' ');
}
}

foreach ($files as $file) {
$valuesSet = 0;
$configurations = $this->getConfigurationsFromFile($file);
foreach ($configurations as $configPath => $configValues) {
$scopeConfigValues = $this->transformConfigToScopeConfig($configPath, $configValues);
foreach ($scopeConfigValues as $scopeConfigValue) {
if ($scopeConfigValue['value'] === self::DELETE_CONFIG_FLAG) {
$this->configWriter->delete(
$configPath,
$scopeConfigValue['scope'],
$this->scopeConverter->convert($scopeConfigValue['scope_id'], $scopeConfigValue['scope'])
);
$configurationValues = $this->collectConfigurationValues($files);
if (0 === count($configurationValues)) {
return;
}

$this->getOutput()->writeln(sprintf('<comment>%s => %s</comment>', $configPath, 'DELETED'));
$valuesSet++;
foreach ($configurationValues as $configPath => $configValue) {
foreach ($configValue as $scopeType => $scopeValue) {
foreach ($scopeValue as $scopeId => $value) {
if ($value === self::DELETE_CONFIG_FLAG) {
$this->configWriter->delete($configPath, $scopeType, $scopeId);
$this->getOutput()->writeln(sprintf('<comment>[%s] [%s] %s => %s</comment>', $scopeType, $scopeId, $configPath, 'DELETED'));

continue;
}

$this->configWriter->save(
$configPath,
$scopeConfigValue['value'],
$scopeConfigValue['scope'],
$this->scopeConverter->convert($scopeConfigValue['scope_id'], $scopeConfigValue['scope'])
);
if ($value === self::KEEP_CONFIG_FLAG) {
$this->getOutput()->writeln(sprintf('<comment>[%s] [%s] %s => %s</comment>', $scopeType, $scopeId, $configPath, 'KEPT'));

continue;
}

$this->configWriter->save($configPath, $value, $scopeType, $scopeId);
$this->getOutput()->writeln(sprintf('<comment>[%s] [%s] %s => %s</comment>', $scopeType, $scopeId, $configPath, $value));
}
}
}
}

/**
* @param array $files
*
* @return array
*/
private function collectConfigurationValues(array $files): array
{
$buffer = [];

$this->getOutput()->writeln(sprintf('<comment>%s => %s</comment>', $configPath, $scopeConfigValue['value']));
foreach ($files as $file) {
$valuesSet = 0;

$configurations = $this->getConfigurationsFromFile($file);
foreach ($configurations as $configPath => $configValues) {
if (!isset($buffer[$configPath])) {
$buffer[$configPath] = [];
}

$scopeConfigValues = $this->transformConfigToScopeConfig($configPath, $configValues);
foreach ($scopeConfigValues as $scopeConfigValue) {
$scopeType = $scopeConfigValue['scope'];
$scopeId = $this->scopeConverter->convert($scopeConfigValue['scope_id'], $scopeConfigValue['scope']);
$buffer[$configPath][$scopeType][$scopeId] = $scopeConfigValue['value'];
$valuesSet++;
}
}

$this->getOutput()->writeln(sprintf('<info>Processed: %s with %s value(s).</info>', $file, $valuesSet));
if (0 === $valuesSet) {
continue;
}

$this->getOutput()->writeln(sprintf(
'<info>Collected configuration values from %s with %s %s.</info>',
$file,
$valuesSet,
$valuesSet === 1 ? 'value' : 'values'
));
}

$this->getOutput()->writeln(' '); // Add empty line to make output in terminal nicer.

return $buffer;
}

/**
Expand Down
16 changes: 15 additions & 1 deletion Test/Unit/Model/Processor/ImportProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Semaio\ConfigImportExport\Model\File\Reader\YamlReader;
use Semaio\ConfigImportExport\Model\Processor\ImportProcessor;
use Semaio\ConfigImportExport\Model\Validator\ScopeValidatorInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ImportProcessorTest extends TestCase
Expand Down Expand Up @@ -67,6 +68,9 @@ public function processWithoutFiles(): void
$this->expectException(InvalidArgumentException::class);

$processor = new ImportProcessor($this->configWriterMock, $this->scopeValidatorMock, $this->scopeConverterMock, []);
$inputMock = $this->getMockBuilder(InputInterface::class)->getMock();
$inputMock->method('getOption')->with('allow-empty-directories')->willReturn(false);
$processor->setInput($inputMock);
$processor->setFinder($finderMock);
$processor->process();
}
Expand Down Expand Up @@ -126,14 +130,24 @@ public function process(): void
0 => '!!DELETE',
],
],
'test/config/custom_field_to_be_keeped' => [
'default' => [
0 => 'VALUE_THAT_SHOULD_NOT_BE_PROCESSED',
],
],
'test/config/custom_field_to_be_keeped' => [
'default' => [
0 => '!!KEEP',
],
],
];

$readerMock = $this->getMockBuilder(YamlReader::class)
->onlyMethods(['parse'])
->getMock();
$readerMock->expects($this->once())->method('parse')->willReturn($parseResult);

$this->scopeValidatorMock->expects($this->exactly(2))->method('validate')->willReturn(true);
$this->scopeValidatorMock->expects($this->exactly(3))->method('validate')->willReturn(true);
$this->configWriterMock->expects($this->once())->method('save');
$this->configWriterMock->expects($this->once())->method('delete');

Expand Down
12 changes: 12 additions & 0 deletions docs/config-import.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ vendorx/general/api_key:
0: "!!DELETE"
```
### Keep Config
To ensure that a specific configuration value will not be changed by the config importer, please use the following string as configuration value:
```yaml
vendorx/general/api_key:
default:
0: "!!KEEP"
```
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.
### 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:
Expand Down

0 comments on commit f7fd4b3

Please sign in to comment.