Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.04% covered (success)
98.04%
50 / 51
90.91% covered (success)
90.91%
10 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Config
98.04% covered (success)
98.04%
50 / 51
90.91% covered (success)
90.91%
10 / 11
28
0.00% covered (danger)
0.00%
0 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 load
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clearCache
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 getCommaSeparatedValues
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getArray
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
8
 isCached
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFullPath
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 rootPath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getArrayItemValue
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 cacheAndReturn
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 replaceEnvValue
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3namespace Dynart\Micro;
4
5class Config implements ConfigInterface {
6
7    private array $config = [];
8    private array $cached = [];
9
10    public function __construct() {}
11
12    public function load(string $path): void {
13        $this->config = array_merge($this->config, parse_ini_file($path, false, INI_SCANNER_TYPED));
14    }
15
16    public function clearCache(): void {
17        $this->cached = [];
18    }
19
20    public function get(string $name, mixed $default = null, bool $useCache = true): mixed {
21        if ($useCache && array_key_exists($name, $this->cached)) {
22            return $this->cached[$name];
23        }
24        if (getenv($name) !== false) {
25            return $this->cacheAndReturn($name, getenv($name), $useCache);
26        }
27        $value = array_key_exists($name, $this->config) ? $this->config[$name] : $default;
28        return $this->cacheAndReturn($name, $this->replaceEnvValue($value), $useCache);
29    }
30
31    public function getCommaSeparatedValues(string $name, bool $useCache = true): array {
32        $values = explode(',', $this->get($name));
33        $result = array_map([$this, 'getArrayItemValue'], $values);
34        return $this->cacheAndReturn($name, $result, $useCache);
35    }
36
37    public function getArray(string $prefix, array $default = [], bool $useCache = true): array {
38        global $_ENV;
39        if ($useCache && array_key_exists($prefix, $this->cached)) {
40            return $this->cached[$prefix];
41        }
42        $result = $default;
43        $len = strlen($prefix);
44        $keys = array_merge(array_keys($this->config), array_keys($_ENV));
45        foreach ($keys as $key) {
46            if (substr($key, 0, $len) != $prefix) {
47                continue;
48            }
49            $configKey = substr($key, $len + 1, strlen($key));
50            $parts = explode('.', $configKey);
51            $current = &$result;
52            foreach ($parts as $part) {
53                if (ctype_digit($part)) {
54                    $part = (int)$part;
55                }
56                if (!array_key_exists($part, $current)) {
57                    $current[$part] = [];
58                }
59                $current = &$current[$part];
60            }
61            $current = $this->get($key, null, false);
62        }
63        return $this->cacheAndReturn($prefix, $result, $useCache);
64    }
65
66    public function isCached(string $name): bool {
67        return array_key_exists($name, $this->cached);
68    }
69
70    public function getFullPath(string $path): string {
71        if (str_starts_with($path, '~')) {
72            return $this->get(AbstractApp::CONFIG_ROOT_PATH) . substr($path, 1);
73        }
74        return $path;
75    }
76
77    public function rootPath(): string {
78        return $this->get(AbstractApp::CONFIG_ROOT_PATH, '');
79    }
80
81    /**
82     * Trims and replaces variables to environment variable values in a string
83     *
84     * @param string $value The value for trim and replace
85     * @return string The trimmed and replaced value
86     */
87    protected function getArrayItemValue(string $value): string {
88        $result = trim($value);
89        $this->replaceEnvValue($result);
90        return $result;
91    }
92
93    /**
94     * Caches a value if the `$useCache` is true and returns with it
95     *
96     * @param string|null $name The config name
97     * @param mixed $value The value
98     * @param bool $useCache Use the cache?
99     * @return mixed
100     */
101    protected function cacheAndReturn(?string $name, mixed $value, bool $useCache = true): mixed {
102        if ($useCache) {
103            $this->cached[$name] = $value;
104        }
105        return $value;
106    }
107
108    /**
109     * Replaces the {{name}} formatted variables in a string with environment variable values
110     *
111     * @param mixed $value The value
112     * @return mixed The value or the replaced string
113     */
114    protected function replaceEnvValue(mixed $value): mixed {
115        if (!is_string($value)) {
116            return $value; // don't touch non-strings
117        }
118        $matches = [];
119        if (!preg_match_all('/{{\s*(\w+)\s*}}/', $value, $matches)) {
120            return $value;
121        }
122        $vars = array_unique($matches[1]);
123        foreach ($vars as $var) {
124            $value = str_replace('{{' . $var . '}}', getenv($var), $value);
125        }
126        return $value;
127    }
128
129}