Skip to content

Commit 20b70d3

Browse files
author
Bohdan Korablov
committed
MAGETWO-65208: Store checksum for every section of configuration file & change behavior on read-only FS & add sorting of importers
1 parent 1f442ce commit 20b70d3

File tree

15 files changed

+352
-82
lines changed

15 files changed

+352
-82
lines changed

app/code/Magento/Deploy/Console/Command/App/ConfigImport/Importer.php

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,28 +84,29 @@ public function __construct(
8484
*/
8585
public function import(OutputInterface $output)
8686
{
87-
$output->writeln('<info>Start import:</info>');
88-
8987
try {
9088
$importers = $this->configImporterPool->getImporters();
91-
9289
if (!$importers || $this->configValidator->isValid()) {
93-
$output->writeln('<info>Nothing to import</info>');
90+
$output->writeln('<info>Nothing to import.</info>');
91+
return;
9492
} else {
95-
/**
96-
* @var string $namespace
97-
* @var ImporterInterface $importer
98-
*/
99-
foreach ($importers as $namespace => $importer) {
100-
$messages = $importer->import($this->deploymentConfig->getConfigData($namespace));
93+
$output->writeln('<info>Start import:</info>');
94+
}
95+
96+
/**
97+
* @var string $section
98+
* @var ImporterInterface $importer
99+
*/
100+
foreach ($importers as $section => $importer) {
101+
if (!$this->configValidator->isValid($section)) {
102+
$messages = $importer->import($this->deploymentConfig->getConfigData($section));
101103
$output->writeln($messages);
104+
$this->configHash->regenerate($section);
102105
}
103-
104-
$this->configHash->regenerate();
105106
}
106107
} catch (LocalizedException $exception) {
107108
$this->logger->error($exception);
108-
throw new LocalizedException(__('Import is failed'), $exception);
109+
throw new LocalizedException(__('Import is failed.'), $exception);
109110
}
110111
}
111112
}

app/code/Magento/Deploy/Console/Command/App/ConfigImportCommand.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Symfony\Component\Console\Output\OutputInterface;
1212
use Magento\Framework\Console\Cli;
1313
use Magento\Deploy\Console\Command\App\ConfigImport\Importer;
14+
use Magento\Framework\App\DeploymentConfig\Writer;
1415

1516
/**
1617
* Imports data from deployment configuration files to the DB.
@@ -26,16 +27,27 @@ class ConfigImportCommand extends Command
2627
const COMMAND_NAME = 'app:config:import';
2728

2829
/**
30+
* Configuration importer.
31+
*
2932
* @var Importer
3033
*/
3134
private $importer;
3235

3336
/**
34-
* @param Importer $importer
37+
* Configuration file writer.
38+
*
39+
* @var Writer
3540
*/
36-
public function __construct(Importer $importer)
41+
private $writer;
42+
43+
/**
44+
* @param Importer $importer the configuration importer
45+
* @param Writer $writer the configuration file writer
46+
*/
47+
public function __construct(Importer $importer, Writer $writer)
3748
{
3849
$this->importer = $importer;
50+
$this->writer = $writer;
3951

4052
parent::__construct();
4153
}
@@ -57,6 +69,11 @@ protected function configure()
5769
*/
5870
protected function execute(InputInterface $input, OutputInterface $output)
5971
{
72+
if (!$this->writer->checkIfWritable()) {
73+
$output->writeln('<error>Deployment configuration file is not writable.</error>');
74+
return Cli::RETURN_FAILURE;
75+
}
76+
6077
try {
6178
$this->importer->import($output);
6279
} catch (LocalizedException $e) {

app/code/Magento/Deploy/Model/DeploymentConfig/DataCollector.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,23 @@ public function __construct(ImporterPool $configImporterPool, DeploymentConfig $
3737
}
3838

3939
/**
40-
* Retrieves configuration data of specific section from deployment configuration files.
40+
* Retrieves configuration data of all specific sections from deployment configuration files.
41+
* Or retrieves configuration data of specific sections by its name.
4142
*
42-
* @return array
43+
* @param string $sectionName the section name for retrieving its configuration data
44+
* @return array is configurations data from deployment configuration files
4345
*/
44-
public function getConfig()
46+
public function getConfig($sectionName = null)
4547
{
4648
$result = [];
4749

48-
foreach ($this->configImporterPool->getSections() as $section) {
50+
if ($sectionName) {
51+
$sections = [$sectionName];
52+
} else {
53+
$sections = $this->configImporterPool->getSections();
54+
}
55+
56+
foreach ($sections as $section) {
4957
$data = $this->deploymentConfig->getConfigData($section);
5058
if ($data) {
5159
$result[$section] = $data;

app/code/Magento/Deploy/Model/DeploymentConfig/Hash.php

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,27 +70,33 @@ public function __construct(
7070
/**
7171
* Updates hash in the storage.
7272
*
73+
* @param string $sectionName
7374
* @return void
74-
* @throws LocalizedException
75+
* @throws LocalizedException is thrown when hash is not saved in a storage
7576
*/
76-
public function regenerate()
77+
public function regenerate($sectionName = null)
7778
{
7879
try {
79-
$config = $this->dataConfigCollector->getConfig();
80-
$hash = $this->configHashGenerator->generate($config);
81-
$this->writer->saveConfig([ConfigFilePool::APP_ENV => [self::CONFIG_KEY => $hash]]);
80+
$hashes = $this->get();
81+
$configs = $this->dataConfigCollector->getConfig($sectionName ?: null);
82+
83+
foreach ($configs as $section => $config) {
84+
$hashes[$section] = $this->configHashGenerator->generate($config);
85+
}
86+
87+
$this->writer->saveConfig([ConfigFilePool::APP_ENV => [self::CONFIG_KEY => $hashes]]);
8288
} catch (FileSystemException $exception) {
83-
throw new LocalizedException(__('Hash has not been saved'), $exception);
89+
throw new LocalizedException(__('Hash has not been saved.'), $exception);
8490
}
8591
}
8692

8793
/**
88-
* Retrieves saved hash from storage.
94+
* Retrieves saved hashes from storage.
8995
*
90-
* @return string|null
96+
* @return array
9197
*/
9298
public function get()
9399
{
94-
return $this->deploymentConfig->getConfigData(self::CONFIG_KEY);
100+
return (array) ($this->deploymentConfig->getConfigData(self::CONFIG_KEY) ?: []);
95101
}
96102
}

app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
namespace Magento\Deploy\Model\DeploymentConfig;
77

88
use Magento\Framework\Exception\ConfigurationMismatchException;
9-
use Magento\Framework\Phrase;
109
use Magento\Framework\ObjectManagerInterface;
1110
use Magento\Framework\App\DeploymentConfig\ImporterInterface;
1211

@@ -24,7 +23,14 @@ class ImporterPool
2423
* <type name="Magento\Deploy\Model\DeploymentConfig\ImporterPool">
2524
* <arguments>
2625
* <argument name="importers" xsi:type="array">
27-
* <item name="scopes" xsi:type="string">Magento\Store\Model\StoreImporter</item>
26+
* <item name="scopes" xsi:type="array">
27+
* <item name="sortOrder" xsi:type="number">20</item>
28+
* <item name="class" xsi:type="string">Magento\Store\Model\StoreImporter</item>
29+
* </item>
30+
* <item name="themes" xsi:type="array">
31+
* <item name="sortOrder" xsi:type="number">10</item>
32+
* <item name="class" xsi:type="string">Magento\Theme\Model\ThemeImporter</item>
33+
* </item>
2834
* </argument>
2935
* </arguments>
3036
* </type>
@@ -52,6 +58,13 @@ class ImporterPool
5258
*/
5359
private $importers = [];
5460

61+
/**
62+
* The same as $importers, sorted by sortOrder.
63+
*
64+
* @var array
65+
*/
66+
private $sortedImporters;
67+
5568
/**
5669
* Magento object manager.
5770
*
@@ -88,7 +101,7 @@ public function getSections()
88101
}
89102

90103
/**
91-
* Retrieves list of all sections with their importer instances.
104+
* Retrieves list of all sections with their importer instances, sorted by sortOrder.
92105
*
93106
* E.g.
94107
* ```php
@@ -105,17 +118,61 @@ public function getImporters()
105118
{
106119
$result = [];
107120

108-
foreach ($this->importers as $section => $importer) {
109-
$importerObj = $this->objectManager->get($importer);
121+
if (null == $this->sortedImporters) {
122+
$this->sortedImporters = $this->sort($this->importers);
123+
}
124+
125+
foreach ($this->sortedImporters as $section => $importer) {
126+
if (empty($importer['class'])) {
127+
throw new ConfigurationMismatchException(__('Parameter "class" must be present.'));
128+
}
129+
130+
$importerObj = $this->objectManager->get($importer['class']);
110131
if (!$importerObj instanceof ImporterInterface) {
111-
throw new ConfigurationMismatchException(new Phrase(
112-
'%1: Instance of %2 is expected, got %3 instead',
113-
[$section, ImporterInterface::class, get_class($importerObj)]
132+
throw new ConfigurationMismatchException(
133+
__(
134+
'%1: Instance of %2 is expected, got %3 instead',
135+
$section,
136+
ImporterInterface::class, get_class($importerObj
137+
)
114138
));
115139
}
116140
$result[$section] = $importerObj;
117141
}
118142

119143
return $result;
120144
}
145+
146+
/**
147+
* Sorts importers according to sort order.
148+
*
149+
* @param array $data
150+
* @return array
151+
*/
152+
private function sort(array $data)
153+
{
154+
uasort($data, function (array $a, array $b) {
155+
$a['sortOrder'] = $this->getSortOrder($a);
156+
$b['sortOrder'] = $this->getSortOrder($b);
157+
158+
if ($a['sortOrder'] == $b['sortOrder']) {
159+
return 0;
160+
}
161+
162+
return ($a['sortOrder'] < $b['sortOrder']) ? -1 : 1;
163+
});
164+
165+
return $data;
166+
}
167+
168+
/**
169+
* Retrieves sort order from array.
170+
*
171+
* @param array $variable
172+
* @return int
173+
*/
174+
private function getSortOrder(array $variable)
175+
{
176+
return !empty($variable['sortOrder']) ? $variable['sortOrder'] : 0;
177+
}
121178
}

app/code/Magento/Deploy/Model/DeploymentConfig/Validator.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,26 @@ public function __construct(
4949
}
5050

5151
/**
52-
* Check if config data in the deployment configuration files is valid.
52+
* Checks if config data in the deployment configuration files is valid.
5353
*
54+
* If the section name is set checks only its configuration data.
5455
* If config data is empty always returns true because it means that nothing to import.
5556
*
57+
* @param string $sectionName is name section for check data of the specific section
5658
* @return bool
5759
*/
58-
public function isValid()
60+
public function isValid($sectionName = null)
5961
{
60-
$config = $this->dataConfigCollector->getConfig();
62+
$configs = $this->dataConfigCollector->getConfig($sectionName ?: null);
63+
$hashes = $this->configHash->get();
6164

62-
if (!$config) {
63-
return true;
65+
foreach ($configs as $section => $config) {
66+
$hash = isset($hashes[$section]) ? $hashes[$section] : null;
67+
if ($config && $this->hashGenerator->generate($config) !== $hash) {
68+
return false;
69+
}
6470
}
6571

66-
return $this->hashGenerator->generate($config) === $this->configHash->get();
72+
return true;
6773
}
6874
}

app/code/Magento/Deploy/Test/Unit/Console/Command/App/ConfigImport/ImporterTest.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,14 @@ public function testImport()
133133
public function testImportWithException()
134134
{
135135
$exception = new LocalizedException(__('Some error'));
136-
$this->outputMock->expects($this->at(0))
137-
->method('writeln')
138-
->with('<info>Start import:</info>');
136+
$this->outputMock->expects($this->never())
137+
->method('writeln');
138+
$this->configHashMock->expects($this->never())
139+
->method('regenerate');
140+
$this->configValidatorMock->expects($this->never())
141+
->method('isValid');
142+
$this->deploymentConfigMock->expects($this->never())
143+
->method('getConfigData');
139144
$this->configImporterPoolMock->expects($this->once())
140145
->method('getImporters')
141146
->willThrowException($exception);
@@ -169,10 +174,7 @@ public function testImportNothingToImport(array $importers, $isValid)
169174

170175
$this->outputMock->expects($this->at(0))
171176
->method('writeln')
172-
->with('<info>Start import:</info>');
173-
$this->outputMock->expects($this->at(1))
174-
->method('writeln')
175-
->with('<info>Nothing to import</info>');
177+
->with('<info>Nothing to import.</info>');
176178

177179
$this->importer->import($this->outputMock);
178180
}

0 commit comments

Comments
 (0)