Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 126
0.00% covered (danger)
0.00%
0 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
PlatformConfiguration
0.00% covered (danger)
0.00%
0 / 126
0.00% covered (danger)
0.00%
0 / 18
2256
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getConfig
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setConfig
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
56
 getParsedConfig
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDefinition
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setDefinitionForPlugin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 set
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
30
 exists
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
56
 unset
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 create
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
6
 getConfigDefinition
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 parsedConfigKey
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 loadConfiguration
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
30
 configForKey
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 getCustmConfigurationField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 parseCustomField
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setEncodedValue
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php declare(strict_types=1);
2
3namespace Satag\AmicronEntityBundle\Service;
4
5use Doctrine\ORM\EntityManagerInterface;
6use Satag\AmicronEntityBundle\Entity\Setupbin;
7use Satag\AmicronEntityBundle\Util\PlattformKonfig\SetupBinConfigKeyStruct;
8
9class PlatformConfiguration
10{
11    /**
12     * @var array<string, mixed>
13     */
14    protected array $configArray = [];
15
16    /**
17     * @var array<string, mixed>
18     */
19    protected array $parsedConfig = [];
20
21    /**
22     * @var array<string, mixed>
23     */
24    protected array $parsedConfigDefinition = [];
25
26    public function __construct(
27        protected EntityManagerInterface $entityManager
28    ) {
29        $this->loadConfiguration();
30    }
31
32    /**
33     * @return array<string, mixed>
34     *
35     * @throws PlattformKonfigException
36     */
37    public function getConfig(string $pluginName): array
38    {
39        return $this->get($pluginName . '.config');
40    }
41
42    public function setConfig(string $pluginName, mixed $value): void
43    {
44        $this->set($pluginName . '.config', $value);
45    }
46
47    public function get(string $configKey, mixed $default = null): mixed
48    {
49        if ($default !== null) {
50            if (!$this->exists($configKey)) {
51                return $default;
52            }
53        }
54        $parsedConfigKey = $this->parsedConfigKey($configKey);
55        if (!isset($this->configArray[$parsedConfigKey->getSchluessel()])) {
56            throw new PlattformKonfigException(\sprintf("Konfiguration  %s (%s) nicht gefunden\r\n", $parsedConfigKey->getSchluessel(), $configKey));
57        }
58
59        if (!isset($this->parsedConfig[$parsedConfigKey->getSchluessel()]) && $this->configArray[$parsedConfigKey->getSchluessel()] instanceof Setupbin) {
60            $this->parsedConfig[$parsedConfigKey->getSchluessel()] = $this->parseCustomField($this->configArray[$parsedConfigKey->getSchluessel()]);
61        }
62
63        if (!isset($this->parsedConfig[$parsedConfigKey->getSchluessel()][$parsedConfigKey->getDataIdentifier()])) {
64            throw new PlattformKonfigException(\sprintf("Konfiguration %s nicht gefunden\r\n", $configKey));
65        }
66
67        return $this->configForKey($this->parsedConfig[$parsedConfigKey->getSchluessel()][$parsedConfigKey->getDataIdentifier()], $parsedConfigKey->getParts());
68    }
69
70    /**
71     * @return array<string, mixed>
72     */
73    public function getParsedConfig(): array
74    {
75        return $this->parsedConfig;
76    }
77
78    /**
79     * @return array<string, mixed>
80     *
81     * @throws PlattformKonfigException
82     */
83    public function getDefinition(string $pluginName): array
84    {
85        return $this->get($pluginName . '.definition');
86    }
87
88    public function setDefinitionForPlugin(string $pluginName, mixed $value): void
89    {
90        $this->set($pluginName . '.definition', $value);
91    }
92
93    public function set(string $configKey, mixed $value): void
94    {
95        $parsedConfigKey = $this->parsedConfigKey($configKey);
96        if (!isset($this->configArray[$parsedConfigKey->getSchluessel()])) {
97            throw new PlattformKonfigException(\sprintf("Konfiguration für Anlass %s (%s) nicht gefunden\r\n", $parsedConfigKey->getSchluessel(), $configKey));
98        }
99        if (\count($parsedConfigKey->getParts()) === 0 && !\is_array($value)) {
100            throw new PlattformKonfigException(\sprintf("Konfiguration %s konnte nicht gesetzt werden\r\n", $configKey));
101        }
102
103        $configEntity = $this->configArray[$parsedConfigKey->getSchluessel()];
104        $currentConfig = $this->parseCustomField($configEntity);
105
106        $parts = [$parsedConfigKey->getDataIdentifier()];
107        $parts = array_merge($parts, $parsedConfigKey->getParts());
108
109        $newValue = [];
110        $ref = &$newValue;
111        foreach ($parts as $part) {
112            $ref[$part] = [];
113            $ref = &$ref[$part];
114        }
115        $ref = $value;
116        $currentConfig = array_replace_recursive($currentConfig, $newValue);
117
118        $this->setEncodedValue($configEntity, $currentConfig);
119
120        $this->entityManager->persist($configEntity);
121        $this->entityManager->flush();
122
123        $this->parsedConfig[$parsedConfigKey->getSchluessel()] = $this->parseCustomField($configEntity);
124    }
125
126    public function exists(string $configKey): bool
127    {
128        $parsedConfigKey = $this->parsedConfigKey($configKey);
129        if (!isset($this->configArray[$parsedConfigKey->getSchluessel()])) {
130            return false;
131        }
132        if (!isset($this->parsedConfig[$parsedConfigKey->getSchluessel()]) && $this->configArray[$parsedConfigKey->getSchluessel()] instanceof Setupbin) {
133            $this->parsedConfig[$parsedConfigKey->getSchluessel()] = $this->parseCustomField($this->configArray[$parsedConfigKey->getSchluessel()]);
134        }
135        if (!isset($this->parsedConfig[$parsedConfigKey->getSchluessel()][$parsedConfigKey->getDataIdentifier()])) {
136            return false;
137        }
138        if (\count($parsedConfigKey->getParts()) === 0) {
139            return true;
140        }
141        $value = $this->configForKey($this->parsedConfig[$parsedConfigKey->getSchluessel()][$parsedConfigKey->getDataIdentifier()], $parsedConfigKey->getParts());
142        if ($value === null) {
143            return false;
144        }
145
146        return true;
147    }
148
149    public function unset(string $configKey): void
150    {
151        $parsedConfigKey = $this->parsedConfigKey($configKey);
152        $configEntity = $this->configArray[$parsedConfigKey->getSchluessel()];
153        if (\count($parsedConfigKey->getParts()) === 0) {
154            unset($this->parsedConfig[$parsedConfigKey->getSchluessel()][$parsedConfigKey->getDataIdentifier()]);
155        } else {
156            $parts = [$parsedConfigKey->getDataIdentifier()];
157            $parts = array_merge($parts, $parsedConfigKey->getParts());
158
159            $currentConfig = $this->parseCustomField($configEntity);
160
161            $ref = &$currentConfig;
162            foreach ($parts as $part) {
163                $ref = &$ref[$part];
164            }
165            $ref = null;
166            $this->setEncodedValue($configEntity, $parsedConfigKey->getDataIdentifier());
167            $this->parsedConfig[$parsedConfigKey->getSchluessel()] = $this->parseCustomField($configEntity);
168        }
169        $this->entityManager->persist($configEntity);
170        $this->entityManager->flush();
171    }
172
173    public function create(string $configDomain, string $key, mixed $init): void
174    {
175        $setupBinRepository = $this->entityManager->getRepository(Setupbin::class);
176
177        $setupBinKey = "platform.$configDomain";
178        $user = 'SYS';
179        $exists = $setupBinRepository->findOneBy(
180            [
181                'schluessel' => $setupBinKey,
182                'benutzerkuerzel' => $user,
183            ]
184        );
185        if ($exists) {
186            throw new PlattformKonfigException(\sprintf("Konfiguration %s kann nicht angelegt werden, bereits vorhanden\r\n", $configDomain));
187        }
188
189        $key = "$key";
190
191        $newConfig = new Setupbin();
192        $newConfig->setBenutzerkuerzel($user);
193        $newConfig->setSchluessel($setupBinKey);
194        $this->setEncodedValue($newConfig, [$key => $init]);
195
196        $this->entityManager->persist($newConfig);
197        $this->entityManager->flush();
198
199        $this->configArray[(string) $newConfig->getSchluessel()] = $newConfig;
200        $this->parsedConfig[(string) $newConfig->getSchluessel()] = $this->parseCustomField($newConfig);
201    }
202
203    /**
204     * @return array<string, mixed>
205     *
206     * @throws \JsonException
207     */
208    public function getConfigDefinition(string $schluessel): ?array
209    {
210        if (isset($this->configArray[$schluessel])) {
211            return $this->parseCustomField($this->configArray[$schluessel]);
212        }
213
214        return null;
215    }
216
217    protected function parsedConfigKey(string $configKey): SetupBinConfigKeyStruct
218    {
219        if (stripos($configKey, 'platform.') === false) {
220            $configKey = 'platform.' . $configKey;
221        }
222
223        return new SetupBinConfigKeyStruct($configKey);
224    }
225
226    protected function loadConfiguration(): void
227    {
228        $con = $this->entityManager->getConnection();
229
230        // Ensure connection is actually usable - the Firebird driver may report
231        // isConnected()=true but the connection handle can be invalid after exceptions.
232        // We do a simple ping query to verify the connection is truly functional.
233        // Note: We rely on lazy connection establishment instead of explicit connect()
234        // calls, as Connection::connect() is deprecated for public use.
235        try {
236            // Ping test - execute minimal query to verify connection is functional
237            // This will establish the connection if not already connected (lazy connection)
238            $con->executeQuery('SELECT 1 FROM RDB$DATABASE')->fetchOne();
239        } catch (\Exception) {
240            // Connection is invalid - try to reconnect by closing and letting next query reconnect
241            try {
242                $con->close();
243                // Verify reconnection worked - executeQuery will re-establish connection
244                $con->executeQuery('SELECT 1 FROM RDB$DATABASE')->fetchOne();
245            } catch (\Exception) {
246                // Cannot establish connection - return empty config
247                // This prevents cascade failures during error handling
248                return;
249            }
250        }
251
252        try {
253            $kontaktRepository = $this->entityManager->getRepository(Setupbin::class);
254
255            $entities = $kontaktRepository->createQueryBuilder('s')
256                ->where('s.benutzerkuerzel = :user')
257                ->andWhere('s.schluessel like :key')
258                ->setParameter('user', 'SYS')
259                ->setParameter('key', 'platform.%')
260                ->getQuery()
261                ->getResult();
262        } catch (\Exception) {
263            // Query failed - possibly invalid connection
264            // Return empty config to prevent cascade failures
265            return;
266        }
267
268        /**
269         * @var Setupbin $config
270         */
271        foreach ($entities as $config) {
272            $this->configArray[(string) $config->getSchluessel()] = $config;
273            $this->parsedConfig[(string) $config->getSchluessel()] = $this->parseCustomField($config);
274        }
275    }
276
277    /**
278     * @param array<string, mixed> $config
279     * @param string[] $parts
280     *
281     * @return mixed|null
282     */
283    protected function configForKey(array $config, array $parts): mixed
284    {
285        $current = $config;
286        foreach ($parts as $part) {
287            if (!\is_array($current)) {
288                return null;
289            }
290            if (\array_key_exists($part, $current)) {
291                $current = $current[$part];
292
293                continue;
294            }
295
296            return null;
297        }
298
299        return $current;
300    }
301
302    protected function getCustmConfigurationField(): string
303    {
304        return 'BIN';
305    }
306
307    /**
308     * @return array<string, mixed>
309     *
310     * @throws \JsonException
311     */
312    protected function parseCustomField(Setupbin $configEntity): array
313    {
314        $json = base64_decode((string) $configEntity->getBin(), true);
315        if ($json === false) {
316            return [];
317        }
318
319        return \json_decode($json, true, 512, \JSON_THROW_ON_ERROR);
320    }
321
322    protected function setEncodedValue(Setupbin $configEntity, mixed $data): void
323    {
324        $configEntity->setBin(base64_encode(\json_encode($data, \JSON_THROW_ON_ERROR)));
325    }
326}