state config

This commit is contained in:
Yuri Kuznetsov
2025-09-30 15:28:52 +03:00
parent 3c81bf93e3
commit 20474a9d3e
6 changed files with 285 additions and 28 deletions

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
/data/config-internal.php /data/config-internal.php
/data/config-override.php /data/config-override.php
/data/config-internal-override.php /data/config-internal-override.php
/data/state.php
/data/tmp/* /data/tmp/*
/build /build
/node_modules /node_modules

View File

@@ -46,6 +46,7 @@ class Config
private string $internalConfigPath = 'data/config-internal.php'; private string $internalConfigPath = 'data/config-internal.php';
private string $overrideConfigPath = 'data/config-override.php'; private string $overrideConfigPath = 'data/config-override.php';
private string $internalOverrideConfigPath = 'data/config-internal-override.php'; private string $internalOverrideConfigPath = 'data/config-internal-override.php';
private string $stateConfigPath = 'data/state.php';
private string $cacheTimestamp = 'cacheTimestamp'; private string $cacheTimestamp = 'cacheTimestamp';
/** @var string[] */ /** @var string[] */
protected $associativeArrayAttributeList = [ protected $associativeArrayAttributeList = [
@@ -81,12 +82,22 @@ class Config
* A path to the internal config file. * A path to the internal config file.
* *
* @todo Move to ConfigData. * @todo Move to ConfigData.
* @internal
*/ */
public function getInternalConfigPath(): string public function getInternalConfigPath(): string
{ {
return $this->internalConfigPath; return $this->internalConfigPath;
} }
/**
* @todo Move to ConfigData.
* @internal
*/
public function getStateConfigPath(): string
{
return $this->stateConfigPath;
}
/** /**
* Get a parameter value. * Get a parameter value.
* *
@@ -302,13 +313,15 @@ class Config
$internalData = $this->readFile($this->internalConfigPath); $internalData = $this->readFile($this->internalConfigPath);
$overrideData = $this->readFile($this->overrideConfigPath); $overrideData = $this->readFile($this->overrideConfigPath);
$internalOverrideData = $this->readFile($this->internalOverrideConfigPath); $internalOverrideData = $this->readFile($this->internalOverrideConfigPath);
$stateConfigData = $this->readFile($this->stateConfigPath);
$this->data = $this->mergeData( $this->data = $this->mergeData(
$systemData, systemData: $systemData,
$data, data: $data,
$internalData, internalData: $internalData,
$overrideData, overrideData: $overrideData,
$internalOverrideData internalOverrideData: $internalOverrideData,
stateData: $stateConfigData,
); );
$this->internalParamList = array_values(array_merge( $this->internalParamList = array_values(array_merge(
@@ -325,6 +338,7 @@ class Config
* @param array<string, mixed> $internalData * @param array<string, mixed> $internalData
* @param array<string, mixed> $overrideData * @param array<string, mixed> $overrideData
* @param array<string, mixed> $internalOverrideData * @param array<string, mixed> $internalOverrideData
* @param array<string, mixed> $stateData
* @return array<string, mixed> * @return array<string, mixed>
*/ */
private function mergeData( private function mergeData(
@@ -332,7 +346,8 @@ class Config
array $data, array $data,
array $internalData, array $internalData,
array $overrideData, array $overrideData,
array $internalOverrideData array $internalOverrideData,
array $stateData,
): array { ): array {
/** @var array<string, mixed> $mergedData */ /** @var array<string, mixed> $mergedData */
@@ -344,8 +359,13 @@ class Config
/** @var array<string, mixed> $mergedData */ /** @var array<string, mixed> $mergedData */
$mergedData = Util::merge($mergedData, $overrideData); $mergedData = Util::merge($mergedData, $overrideData);
/** @var array<string, mixed> */ /** @var array<string, mixed> $mergedData */
return Util::merge($mergedData, $internalOverrideData); $mergedData = Util::merge($mergedData, $internalOverrideData);
/** @var array<string, mixed> $mergedData */
$mergedData = Util::merge($mergedData, $stateData);
return $mergedData;
} }
/** /**

View File

@@ -107,6 +107,7 @@ class ConfigWriter
$configPath = $this->config->getConfigPath(); $configPath = $this->config->getConfigPath();
$internalConfigPath = $this->config->getInternalConfigPath(); $internalConfigPath = $this->config->getInternalConfigPath();
$stateConfigPath = $this->config->getStateConfigPath();
if (!$this->fileManager->isFile($configPath)) { if (!$this->fileManager->isFile($configPath)) {
throw new RuntimeException("Config file '$configPath' not found."); throw new RuntimeException("Config file '$configPath' not found.");
@@ -117,6 +118,9 @@ class ConfigWriter
$dataInternal = $this->fileManager->isFile($internalConfigPath) ? $dataInternal = $this->fileManager->isFile($internalConfigPath) ?
$this->fileManager->getPhpContents($internalConfigPath) : []; $this->fileManager->getPhpContents($internalConfigPath) : [];
$dataState = $this->fileManager->isFile($stateConfigPath) ?
$this->fileManager->getPhpContents($stateConfigPath) : [];
if (!is_array($data)) { if (!is_array($data)) {
throw new RuntimeException("Could not read config."); throw new RuntimeException("Could not read config.");
} }
@@ -125,12 +129,45 @@ class ConfigWriter
throw new RuntimeException("Could not read config-internal."); throw new RuntimeException("Could not read config-internal.");
} }
if (!is_array($dataState)) {
throw new RuntimeException("Could not read state.");
}
$toSaveInternal = false; $toSaveInternal = false;
$toSaveState = false;
$toSaveMain = false;
foreach (array_merge(array_keys($changedData), $this->removeParamList) as $key) {
if (array_key_exists($key, $data)) {
$toSaveMain = true;
}
if (array_key_exists($key, $dataInternal)) {
$toSaveInternal = true;
}
if (array_key_exists($key, $dataState)) {
$toSaveState = true;
}
}
foreach ($changedData as $key => $value) { foreach ($changedData as $key => $value) {
if ($this->internalConfigHelper->isParamForStateConfig($key)) {
$dataState[$key] = $value;
unset($data[$key]);
unset($dataInternal[$key]);
$toSaveState = true;
continue;
}
if ($this->internalConfigHelper->isParamForInternalConfig($key)) { if ($this->internalConfigHelper->isParamForInternalConfig($key)) {
$dataInternal[$key] = $value; $dataInternal[$key] = $value;
unset($data[$key]); unset($data[$key]);
unset($dataState[$key]);
$toSaveInternal = true; $toSaveInternal = true;
@@ -138,25 +175,38 @@ class ConfigWriter
} }
$data[$key] = $value; $data[$key] = $value;
unset($dataState[$key]);
unset($dataInternal[$key]);
$toSaveMain = true;
} }
foreach ($this->removeParamList as $key) { foreach ($this->removeParamList as $key) {
if ($this->internalConfigHelper->isParamForInternalConfig($key)) { if (array_key_exists($key, $data)) {
unset($dataInternal[$key]); unset($data[$key]);
$toSaveInternal = true;
continue;
} }
unset($data[$key]); if (array_key_exists($key, $dataInternal)) {
unset($dataInternal[$key]);
}
if (array_key_exists($key, $dataState)) {
unset($dataState[$key]);
}
} }
if ($toSaveInternal) { if ($toSaveInternal) {
$this->saveData($internalConfigPath, $dataInternal, 'microtimeInternal'); $this->saveData($internalConfigPath, $dataInternal, 'microtimeInternal');
} }
$this->saveData($configPath, $data, 'microtime'); if ($toSaveMain) {
$this->saveData($configPath, $data, 'microtime');
}
if ($toSaveState) {
$this->saveData($stateConfigPath, $dataState, 'microtimeState');
}
$this->changedData = []; $this->changedData = [];
$this->removeParamList = []; $this->removeParamList = [];

View File

@@ -34,9 +34,23 @@ use Espo\Core\Utils\Metadata;
class InternalConfigHelper class InternalConfigHelper
{ {
/** @var string[] */
private array $stateParamList = [
'appTimestamp',
'cacheTimestamp',
'version',
'latestVersion',
'latestExtensionVersions',
];
public function __construct(private Config $config, private Metadata $metadata) public function __construct(private Config $config, private Metadata $metadata)
{} {}
public function isParamForStateConfig(string $name): bool
{
return in_array($name, $this->stateParamList);
}
public function isParamForInternalConfig(string $name): bool public function isParamForInternalConfig(string $name): bool
{ {
if ($this->config->isInternal($name)) { if ($this->config->isInternal($name)) {

View File

@@ -46,6 +46,7 @@ class ConfigWriterTest extends TestCase
private $configPath; private $configPath;
private $internalConfigPath; private $internalConfigPath;
private $stateConfigPath;
protected function setUp(): void protected function setUp(): void
{ {
@@ -61,8 +62,9 @@ class ConfigWriterTest extends TestCase
$this->internalConfigHelper $this->internalConfigHelper
); );
$this->configPath = 'somepath'; $this->configPath = 'somePath';
$this->internalConfigPath = 'internalSomepath'; $this->internalConfigPath = 'internalSomePath';
$this->stateConfigPath = 'stateSomePath';
$this->config $this->config
->expects($this->any()) ->expects($this->any())
@@ -73,6 +75,11 @@ class ConfigWriterTest extends TestCase
->expects($this->any()) ->expects($this->any())
->method('getInternalConfigPath') ->method('getInternalConfigPath')
->willReturn($this->internalConfigPath); ->willReturn($this->internalConfigPath);
$this->config
->expects($this->any())
->method('getStateConfigPath')
->willReturn($this->stateConfigPath);
} }
public function testSave1(): void public function testSave1(): void
@@ -121,6 +128,7 @@ class ConfigWriterTest extends TestCase
->willReturnMap([ ->willReturnMap([
[$this->configPath, true], [$this->configPath, true],
[$this->internalConfigPath, false], [$this->internalConfigPath, false],
[$this->stateConfigPath, false],
]); ]);
$this->fileManager $this->fileManager
@@ -145,6 +153,7 @@ class ConfigWriterTest extends TestCase
$this->configWriter->set('k2', 'v2'); $this->configWriter->set('k2', 'v2');
$this->internalConfigHelper $this->internalConfigHelper
->expects($this->any())
->method('isParamForInternalConfig') ->method('isParamForInternalConfig')
->willReturnMap( ->willReturnMap(
[ [
@@ -154,8 +163,19 @@ class ConfigWriterTest extends TestCase
] ]
); );
$this->internalConfigHelper
->expects($this->any())
->method('isParamForStateConfig')
->willReturnMap(
[
['k1', false],
['k2', false],
['cacheTimestamp', true],
]
);
$this->helper $this->helper
->expects($this->exactly(2)) ->expects($this->exactly(3))
->method('generateMicrotime') ->method('generateMicrotime')
->willReturn(1.0); ->willReturn(1.0);
@@ -170,16 +190,16 @@ class ConfigWriterTest extends TestCase
->willReturnMap([ ->willReturnMap([
[$this->configPath, true], [$this->configPath, true],
[$this->internalConfigPath, true], [$this->internalConfigPath, true],
[$this->stateConfigPath, true],
]); ]);
$this->fileManager $this->fileManager
->expects($this->exactly(4)) ->expects($this->exactly(6))
->method('getPhpContents') ->method('getPhpContents')
->willReturnMap([ ->willReturnMap([
[$this->configPath, []], [$this->configPath, []],
[$this->internalConfigPath, []], [$this->internalConfigPath, []],
[$this->internalConfigPath, []], [$this->stateConfigPath, []],
[$this->configPath, []],
]); ]);
$this->fileManager $this->fileManager
@@ -192,7 +212,156 @@ class ConfigWriterTest extends TestCase
], ],
[ [
$this->configPath, $this->configPath,
['k1' => 'v1', 'cacheTimestamp' => 1, 'microtime' => 1.0] ['k1' => 'v1', 'microtime' => 1.0]
],
[
$this->stateConfigPath,
['cacheTimestamp' => 1, 'microtimeState' => 1.0]
],
]);
$this->configWriter->save();
}
public function testSave3(): void
{
$this->configWriter->set('k1', 'v1');
$this->configWriter->set('k2', 'v2');
$this->internalConfigHelper
->expects($this->any())
->method('isParamForInternalConfig')
->willReturnMap(
[
['k1', false],
['k2', true],
['cacheTimestamp', false],
]
);
$this->internalConfigHelper
->expects($this->any())
->method('isParamForStateConfig')
->willReturnMap(
[
['k1', false],
['k2', false],
['cacheTimestamp', true],
]
);
$this->helper
->expects($this->exactly(3))
->method('generateMicrotime')
->willReturn(1.0);
$this->helper
->expects($this->once())
->method('generateCacheTimestamp')
->willReturn(1);
$this->fileManager
->expects(self::any())
->method('isFile')
->willReturnMap([
[$this->configPath, true],
[$this->internalConfigPath, true],
[$this->stateConfigPath, true],
]);
$this->fileManager
->expects($this->exactly(6))
->method('getPhpContents')
->willReturnMap([
[$this->configPath, []],
[$this->internalConfigPath, []],
[$this->stateConfigPath, []],
]);
$this->fileManager
->expects(self::any())
->method('putPhpContents')
->willReturnMap([
[
$this->internalConfigPath,
['k2' => 'v2', 'microtimeInternal' => 1.0]
],
[
$this->configPath,
['k1' => 'v1', 'microtime' => 1.0]
],
[
$this->stateConfigPath,
['cacheTimestamp' => 1, 'microtimeState' => 1.0]
],
]);
$this->configWriter->save();
}
public function testsRemove1(): void
{
$this->configWriter->remove('k1');
$this->internalConfigHelper
->expects($this->any())
->method('isParamForInternalConfig')
->willReturnMap(
[
['k1', false],
['cacheTimestamp', false],
]
);
$this->internalConfigHelper
->expects($this->any())
->method('isParamForStateConfig')
->willReturnMap(
[
['k1', false],
['cacheTimestamp', false],
]
);
$this->helper
->expects($this->once())
->method('generateCacheTimestamp')
->willReturn(1);
$this->fileManager
->expects(self::any())
->method('isFile')
->willReturnMap([
[$this->configPath, true],
[$this->internalConfigPath, true],
[$this->stateConfigPath, true],
]);
$this->fileManager
->expects(self::any())
->method('isFile')
->willReturnMap([
[$this->configPath, true],
[$this->internalConfigPath, true],
[$this->stateConfigPath, true],
]);
$this->fileManager
->expects($this->exactly(4))
->method('getPhpContents')
->willReturnMap([
[$this->configPath, ['k1' => 'v1', 'k2' => 'v2']],
[$this->internalConfigPath, []],
[$this->stateConfigPath, []],
]);
$this->fileManager
->expects($this->exactly(1))
->method('putPhpContents')
->willReturnMap([
[
$this->configPath,
['k2' => 'v2', 'microtime' => 1.0]
], ],
]); ]);

View File

@@ -43,6 +43,7 @@ class ConfigTest extends TestCase
private $configPath = 'tests/unit/testData/cache/config.php'; private $configPath = 'tests/unit/testData/cache/config.php';
private $systemConfigPath = 'tests/unit/testData/Utils/Config/systemConfig.php'; private $systemConfigPath = 'tests/unit/testData/Utils/Config/systemConfig.php';
private $internalConfigPath = 'tests/unit/testData/cache/config-internal.php'; private $internalConfigPath = 'tests/unit/testData/cache/config-internal.php';
private $stateConfigPath = 'tests/unit/testData/cache/state.php';
private $reflection; private $reflection;
private $fileManager; private $fileManager;
@@ -63,11 +64,7 @@ class ConfigTest extends TestCase
$this->reflection->setProperty('configPath', $this->configPath); $this->reflection->setProperty('configPath', $this->configPath);
$this->reflection->setProperty('systemConfigPath', $this->systemConfigPath); $this->reflection->setProperty('systemConfigPath', $this->systemConfigPath);
$this->reflection->setProperty('internalConfigPath', $this->internalConfigPath); $this->reflection->setProperty('internalConfigPath', $this->internalConfigPath);
} $this->reflection->setProperty('stateConfigPath', $this->stateConfigPath);
protected function tearDown(): void
{
$this->config = NULL;
} }
public function testLoadConfig() public function testLoadConfig()
@@ -123,6 +120,7 @@ class ConfigTest extends TestCase
['data/config-internal.php', true], ['data/config-internal.php', true],
['application/Espo/Resources/defaults/systemConfig.php', true], ['application/Espo/Resources/defaults/systemConfig.php', true],
['data/config-override.php', false], ['data/config-override.php', false],
['data/state.php', true],
['data/config-internal-override.php', false], ['data/config-internal-override.php', false],
] ]
); );
@@ -145,6 +143,7 @@ class ConfigTest extends TestCase
[ [
['data/config.php', $data], ['data/config.php', $data],
['data/config-internal.php', $dataInternal], ['data/config-internal.php', $dataInternal],
['data/state.php', []],
['application/Espo/Resources/defaults/systemConfig.php', $dataSystem], ['application/Espo/Resources/defaults/systemConfig.php', $dataSystem],
] ]
); );
@@ -181,6 +180,7 @@ class ConfigTest extends TestCase
['data/config-internal.php', true], ['data/config-internal.php', true],
['application/Espo/Resources/defaults/systemConfig.php', true], ['application/Espo/Resources/defaults/systemConfig.php', true],
['data/config-override.php', false], ['data/config-override.php', false],
['data/state.php', true],
['data/config-internal-override.php', false], ['data/config-internal-override.php', false],
] ]
); );
@@ -208,6 +208,7 @@ class ConfigTest extends TestCase
[ [
['data/config.php', $data], ['data/config.php', $data],
['data/config-internal.php', $dataInternal], ['data/config-internal.php', $dataInternal],
['data/state.php', []],
['application/Espo/Resources/defaults/systemConfig.php', $dataSystem], ['application/Espo/Resources/defaults/systemConfig.php', $dataSystem],
] ]
); );
@@ -239,6 +240,7 @@ class ConfigTest extends TestCase
['data/config.php', true], ['data/config.php', true],
['data/config-internal.php', true], ['data/config-internal.php', true],
['data/config-override.php', true], ['data/config-override.php', true],
['data/state.php', true],
['data/config-internal-override.php', true], ['data/config-internal-override.php', true],
] ]
); );
@@ -273,6 +275,7 @@ class ConfigTest extends TestCase
['data/config.php', $data], ['data/config.php', $data],
['data/config-internal.php', $dataInternal], ['data/config-internal.php', $dataInternal],
['data/config-override.php', $dataOverride], ['data/config-override.php', $dataOverride],
['data/state.php', []],
['data/config-internal-override.php', $dataInternalOverride], ['data/config-internal-override.php', $dataInternalOverride],
] ]
); );