Lines 60.00% 48 / 80
Methods 28.57% 2 / 7
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 updateConfigValue 92.30% 12 / 13 0.00% 0 / 1 4.01
 fetchAll 86.36% 19 / 22 0.00% 0 / 1 8.16
 insert 46.15% 6 / 13 0.00% 0 / 1 6.50
 delete 46.15% 6 / 13 0.00% 0 / 1 6.50
 renameKey 0.00% 0 / 14 0.00% 0 / 1 20
 resolveRedisStore 100.00% 4 / 4 100.00% 1 / 1 2
25readonly class HybridConfigurationStore implements ConfigurationStoreInterface
26{
27    public function __construct(
28        private DatabaseConfigurationStore $databaseConfigurationStore,
29        private ConfigurationStorageSettingsResolver $settingsResolver,
30        private LoggerInterface $logger,
31        private ?FilesystemConfigurationCache $filesystemConfigurationCache = null,
32    ) {
33    }
34
35    public function updateConfigValue(string $key, string $value): bool
36    {
37        $result = $this->databaseConfigurationStore->updateConfigValue($key, $value);
38        if (!$result) {
39            return false;
40        }
41
42        $redisStore = $this->resolveRedisStore();
43        if ($redisStore !== null) {
44            try {
45                $redisStore->updateConfigValue($key, $value);
46            } catch (RuntimeException $exception) {
47                $this->logger->warning('Failed to update configuration key in Redis storage.', [
48                    'key' => $key,
49                    'error' => $exception->getMessage(),
50                ]);
51            }
52        }
53
54        $this->filesystemConfigurationCache?->clear();
55
56        return true;
57    }
58
59    /**
60     * @return array<int, \stdClass>
61     */
62    public function fetchAll(): array
63    {
64        $cachedRows = $this->filesystemConfigurationCache?->read();
65        if ($cachedRows !== null) {
66            return $cachedRows;
67        }
68
69        $redisStore = $this->resolveRedisStore();
70        if ($redisStore !== null) {
71            try {
72                $rows = $redisStore->fetchAll();
73                if ($rows !== []) {
74                    $this->filesystemConfigurationCache?->warm($rows);
75                    return $rows;
76                }
77            } catch (RuntimeException $exception) {
78                $this->logger->warning('Failed to fetch configuration from Redis storage.', [
79                    'error' => $exception->getMessage(),
80                ]);
81            }
82        }
83
84        $rows = $this->databaseConfigurationStore->fetchAll();
85
86        if ($redisStore !== null && $rows !== []) {
87            try {
88                $redisStore->warmFromRows($rows);
89            } catch (RuntimeException $exception) {
90                $this->logger->warning('Failed to warm Redis configuration storage from database.', [
91                    'error' => $exception->getMessage(),
92                ]);
93            }
94        }
95
96        $this->filesystemConfigurationCache?->warm($rows);
97
98        return $rows;
99    }
100
101    public function insert(string $name, string $value): bool
102    {
103        $result = $this->databaseConfigurationStore->insert($name, $value);
104        if (!$result) {
105            return false;
106        }
107
108        $redisStore = $this->resolveRedisStore();
109        if ($redisStore !== null) {
110            try {
111                $redisStore->insert($name, $value);
112            } catch (RuntimeException $exception) {
113                $this->logger->warning('Failed to insert configuration key into Redis storage.', [
114                    'key' => $name,
115                    'error' => $exception->getMessage(),
116                ]);
117            }
118        }
119
120        $this->filesystemConfigurationCache?->clear();
121
122        return true;
123    }
124
125    public function delete(string $name): bool
126    {
127        $result = $this->databaseConfigurationStore->delete($name);
128        if (!$result) {
129            return false;
130        }
131
132        $redisStore = $this->resolveRedisStore();
133        if ($redisStore !== null) {
134            try {
135                $redisStore->delete($name);
136            } catch (RuntimeException $exception) {
137                $this->logger->warning('Failed to delete configuration key from Redis storage.', [
138                    'key' => $name,
139                    'error' => $exception->getMessage(),
140                ]);
141            }
142        }
143
144        $this->filesystemConfigurationCache?->clear();
145
146        return true;
147    }
148
149    public function renameKey(string $currentKey, string $newKey): bool
150    {
151        $result = $this->databaseConfigurationStore->renameKey($currentKey, $newKey);
152        if (!$result) {
153            return false;
154        }
155
156        $redisStore = $this->resolveRedisStore();
157        if ($redisStore !== null) {
158            try {
159                $redisStore->renameKey($currentKey, $newKey);
160            } catch (RuntimeException $exception) {
161                $this->logger->warning('Failed to rename configuration key in Redis storage.', [
162                    'currentKey' => $currentKey,
163                    'newKey' => $newKey,
164                    'error' => $exception->getMessage(),
165                ]);
166            }
167        }
168
169        $this->filesystemConfigurationCache?->clear();
170
171        return true;
172    }
173
174    private function resolveRedisStore(): ?RedisConfigurationStore
175    {
176        $settings = $this->settingsResolver->resolve();
177        if (!$settings->enabled) {
178            return null;
179        }
180
181        return new RedisConfigurationStore($settings);
182    }
183}