Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
Config
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
10 / 10
27
100.00% covered (success)
100.00%
1 / 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
 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
5/**
6 * Config handler
7 *
8 * Loads INI files, caches the retrieved values
9 *
10 * @package Dynart\Micro
11 */
12class Config {
13
14    private array $config = [];
15    private array $cached = [];
16
17    public function __construct() {}
18
19    /**
20     * Loads an INI file and merges with current config
21     *
22     * It does NOT process sections! The "true", "false", "no", "yes", "on", "off" values
23     * will be replaced with true and false values.
24     *
25     * @param string $path The path of the INI file
26     */
27    public function load(string $path): void {
28        $this->config = array_merge($this->config, parse_ini_file($path, false, INI_SCANNER_TYPED));
29    }
30
31    /**
32     * Clears the in-memory cache
33     *
34     * @return void
35     */
36    public function clearCache(): void {
37        $this->cached = [];
38    }
39
40    /**
41     * Returns with value from the configuration
42     *
43     * @param string $name The config name
44     * @param mixed $default The return value if the name does not exist in the configuration
45     * @param bool $useCache Use the cache for retrieving the value?
46     * @return mixed The value
47     */
48    public function get(string $name, mixed $default = null, bool $useCache = true): mixed {
49        if ($useCache && array_key_exists($name, $this->cached)) {
50            return $this->cached[$name];
51        }
52        if (getenv($name) !== false) {
53            return $this->cacheAndReturn($name, getenv($name), $useCache);
54        }
55        $value = array_key_exists($name, $this->config) ? $this->config[$name] : $default;
56        return $this->cacheAndReturn($name, $this->replaceEnvValue($value), $useCache);
57    }
58
59    /**
60     * Returns with an array from a comma separated string config value
61     *
62     * For example: "1, 2, 3" will result in ['1', '2', '3']
63     *
64     * @param string $name The config name
65     * @param bool $useCache Use the cache for retrieving the value?
66     * @return array The value in array
67     */
68    public function getCommaSeparatedValues(string $name, bool $useCache = true): array {
69        $values = explode(',', $this->get($name));
70        $result = array_map([$this, 'getArrayItemValue'], $values);
71        return $this->cacheAndReturn($name, $result, $useCache);
72    }
73
74    /**
75     * Returns with an array from the config
76     *
77     * For example: with the following config:
78     *
79     * <pre>
80     * persons.0.name = "name1"
81     * persons.0.age = "32"
82     * persons.1.name = "name2"
83     * persons.1.age = "42"
84     * </pre>
85     *
86     * the result will be for `$config->getArray('persons')`:
87     *
88     * <pre>
89     * [
90     *   0 => [
91     *      "name" => "name1",
92     *      "age" => "32"
93     *   ],
94     *   1 => [
95     *      "name" => "name2",
96     *      "age" => "42"
97     *   ]
98     * ]
99     * </pre>
100     *
101     * @param string $prefix
102     * @param array $default
103     * @param bool $useCache
104     * @return array
105     */
106    public function getArray(string $prefix, array $default = [], bool $useCache = true): array {
107        global $_ENV;
108        if ($useCache && array_key_exists($prefix, $this->cached)) {
109            return $this->cached[$prefix];
110        }
111        $result = $default;
112        $len = strlen($prefix);
113        $keys = array_merge(array_keys($this->config), array_keys($_ENV));
114        foreach ($keys as $key) {
115            if (substr($key, 0, $len) != $prefix) {
116                continue;
117            }
118            $configKey = substr($key, $len + 1, strlen($key));
119            $parts = explode('.', $configKey);
120            $current = &$result;
121            foreach ($parts as $part) {
122                if (ctype_digit($part)) {
123                    $part = (int)$part;
124                }
125                if (!array_key_exists($part, $current)) {
126                    $current[$part] = [];
127                }
128                $current = &$current[$part];
129            }
130            $current = $this->get($key, null, false);
131        }
132        return $this->cacheAndReturn($prefix, $result, $useCache);
133    }
134
135    /**
136     * Returns true if the config value is cached
137     *
138     * @param string $name Name of the config
139     * @return bool Is the config value cached?
140     */
141    public function isCached(string $name): bool {
142        return array_key_exists($name, $this->cached);
143    }
144
145    /**
146     * Replaces the ~ symbol at the beginning of the path
147     * with the `app.root_path` config value
148     *
149     * @param string $path
150     * @return string
151     */
152    public function getFullPath(string $path): string {
153        if (str_starts_with($path, '~')) {
154            return $this->get(App::CONFIG_ROOT_PATH) . substr($path, 1);
155        }
156        return $path;
157    }
158
159    /**
160     * Trims and replaces variables to environment variable values in a string
161     *
162     * @param string $value The value for trim and replace
163     * @return string The trimmed and replaced value
164     */
165    protected function getArrayItemValue(string $value): string {
166        $result = trim($value);
167        $this->replaceEnvValue($result);
168        return $result;
169    }
170
171    /**
172     * Caches a value if the `$useCache` is true and returns with it
173     *
174     * @param string|null $name The config name
175     * @param mixed $value The value
176     * @param bool $useCache Use the cache?
177     * @return mixed
178     */
179    protected function cacheAndReturn(?string $name, mixed $value, bool $useCache = true): mixed {
180        if ($useCache) {
181            $this->cached[$name] = $value;
182        }
183        return $value;
184    }
185
186    /**
187     * Replaces the {{name}} formatted variables in a string with environment variable values
188     *
189     * @param mixed $value The value
190     * @return mixed The value or the replaced string
191     */
192    protected function replaceEnvValue(mixed $value): mixed {
193        if (!is_string($value)) {
194            return $value; // don’t touch non-strings
195        }
196        $matches = [];
197        if (!preg_match_all('/{{\s*(\w+)\s*}}/', $value, $matches)) {
198            return $value;
199        }
200        $vars = array_unique($matches[1]);
201        foreach ($vars as $var) {
202            $value = str_replace('{{' . $var . '}}', getenv($var), $value);
203        }
204        return $value;
205    }
206
207}