Passed
Push — master ( 70bf0a...4ee951 )
by Marwan
01:37
created

Config   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 237
Duplicated Lines 0 %

Test Coverage

Coverage 98.86%

Importance

Changes 11
Bugs 0 Features 0
Metric Value
eloc 74
c 11
b 0
f 0
dl 0
loc 237
ccs 87
cts 88
cp 0.9886
rs 10
wmc 26

11 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 5 1
A getAll() 0 5 1
A set() 0 5 1
A parse() 0 22 5
B include() 0 30 7
A __toString() 0 3 1
A load() 0 16 3
A clearCache() 0 13 2
A cache() 0 25 3
A __construct() 0 3 1
A has() 0 7 1
1
<?php
2
3
/**
4
 * @author Marwan Al-Soltany <[email protected]>
5
 * @copyright Marwan Al-Soltany 2021
6
 * For the full copyright and license information, please view
7
 * the LICENSE file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace MAKS\Velox\Backend;
13
14
use MAKS\Velox\App;
15
use MAKS\Velox\Backend\Event;
16
use MAKS\Velox\Helper\Misc;
17
18
/**
19
 * A class that loads everything from the "/config" directory and make it as an array that is accessible via dot-notation.
20
 *
21
 * Example:
22
 * ```
23
 * // get the entire config
24
 * $entireConfig = Config::getAll();
25
 *
26
 * // check for config value availability
27
 * $varNameExists = Config::has('filename.config.varName');
28
 *
29
 * // get a specific config value or fall back to a default value
30
 * $varName = Config::get('filename.config.varName', 'fallbackValue');
31
 *
32
 * // set a specific config value at runtime
33
 * Config::set('filename.config.varName', 'varValue');
34
 *
35
 * // delete cached config
36
 * Config::clearCache();
37
 * ```
38
 *
39
 * @since 1.0.0
40
 * @api
41
 */
42
class Config
43
{
44
    /**
45
     * The default directory of the configuration files.
46
     *
47
     * @var string
48
     */
49
    public const CONFIG_DIR = BASE_PATH . '/config';
50
51
    /**
52
     * The path of the cached configuration file.
53
     *
54
     * @var string
55
     */
56
    public const CONFIG_CACHE_FILE = BASE_PATH . '/storage/cache/config/config.json';
57
58
59
    /**
60
     * The currently loaded configuration.
61
     */
62
    protected static array $config;
63
64
65 17
    public function __construct()
66
    {
67 17
        $this->load();
68 17
    }
69
70 1
    public function __toString()
71
    {
72 1
        return static::CONFIG_DIR;
73
    }
74
75
76
    /**
77
     * Includes all files in a directory.
78
     *
79
     * @param string $path
80
     *
81
     * @return array
82
     */
83 6
    private static function include(string $path): array
84
    {
85 6
        $includes = [];
86 6
        $include  = static function ($file) {
87 6
            if (is_file($file)) {
88 6
                $info = pathinfo($file);
89 6
                return $info['extension'] == 'php' ? include($file) : [];
90
            }
91
92 1
            return self::include($file);
93 6
        };
94
95
        // load all config files
96 6
        $filenames = scandir($path) ?: [];
97 6
        foreach ($filenames as $filename) {
98 6
            $file = sprintf('%s/%s', $path, $filename);
99 6
            $config = basename($filename, '.php');
100
101
            // do not include items that have dots in their names
102
            // as this will conflict with array access separator
103 6
            if (strpos($config, '.') !== false) {
104 6
                continue;
105
            }
106
107 6
            $includes[$config] = isset($includes[$config])
108
                ? $include($file) + (array)$includes[$config]
109 6
                : $include($file);
110
        }
111
112 6
        return $includes;
113
    }
114
115
    /**
116
     * Parses the configuration to replace reference of some "{filename.config.varName}" with actual value from the passed configuration.
117
     *
118
     * @param array $config
119
     *
120
     * @return array
121
     */
122 6
    private static function parse(array $config): array
123
    {
124
        // parses all config variables
125 6
        $tries = count($config);
126 6
        for ($i = 0; $i < $tries; $i++) {
127 6
            array_walk_recursive($config, function (&$value) use (&$config) {
128 6
                if (is_string($value)) {
129 6
                    if (preg_match_all('/{([a-z0-9_\-\.]*)}/i', $value, $matches)) {
130 6
                        $variables = [];
131 6
                        array_walk($matches[1], function (&$variable) use (&$variables, &$config) {
132 6
                            $variables[$variable] = Misc::getArrayValueByKey($config, $variable, null);
133 6
                        });
134
135 6
                        $value = $value === $matches[0][0]
136 6
                            ? $variables[$matches[1][0]]
137 6
                            : Misc::interpolate($value, $variables);
138
                    }
139
                }
140 6
            });
141
        }
142
143 6
        return $config;
144
    }
145
146
    /**
147
     * Loads the configuration (directly or when available from cache) and sets class internal state.
148
     *
149
     * @return void
150
     */
151 36
    protected static function load(): void
152
    {
153 36
        $configDir       = static::CONFIG_DIR;
154 36
        $configCacheFile = static::CONFIG_CACHE_FILE;
155
156 36
        if (empty(static::$config)) {
157 6
            if (file_exists($configCacheFile)) {
158 1
                $configJson     = file_get_contents($configCacheFile);
159 1
                static::$config = json_decode($configJson, true);
160
161 1
                return;
162
            }
163
164 6
            static::$config = self::parse(self::include($configDir));
165
166 6
            Event::dispatch('config.on.load', [&static::$config]);
167
        }
168 36
    }
169
170
    /**
171
     * Caches the current configuration as JSON. Note that a new version will not be generated unless the cache is cleared.
172
     *
173
     * @return void
174
     */
175 3
    public static function cache(): void
176
    {
177 3
        $configDir       = static::CONFIG_DIR;
178 3
        $configCacheFile = static::CONFIG_CACHE_FILE;
179 3
        $configCacheDir  = dirname($configCacheFile);
180
181 3
        if (file_exists($configCacheFile)) {
182 1
            return;
183
        }
184
185 2
        if (!file_exists($configCacheDir)) {
186 2
            mkdir($configCacheDir, 0744, true);
187
        }
188
189 2
        $config     = self::parse(self::include($configDir));
190 2
        $configJson = json_encode($config, JSON_PRETTY_PRINT);
191
192 2
        file_put_contents($configCacheFile, $configJson, LOCK_EX);
193
194 2
        Event::dispatch('config.on.cache');
195
196 2
        App::log(
197 2
            'Generated cache for system config, checksum (SHA-256: {checksum})',
198 2
            ['checksum' => hash('sha256', $configJson)],
199 2
            'system'
200
        );
201 2
    }
202
203
    /**
204
     * Deletes the cached configuration JSON and resets class internal state.
205
     *
206
     * @return void
207
     */
208 2
    public static function clearCache(): void
209
    {
210 2
        static::$config = [];
211
212 2
        $configCacheFile = static::CONFIG_CACHE_FILE;
213
214 2
        if (file_exists($configCacheFile)) {
215 1
            unlink($configCacheFile);
216
        }
217
218 2
        Event::dispatch('config.on.clearCache');
219
220 2
        App::log('Cleared config cache', null, 'system');
221 2
    }
222
223
    /**
224
     * Checks whether a value of a key exists in the configuration via dot-notation.
225
     *
226
     * @param string $key The dotted key representation.
227
     *
228
     * @return bool
229
     */
230 1
    public static function has(string $key): bool
231
    {
232 1
        static::load();
233
234 1
        $value = Misc::getArrayValueByKey(static::$config, $key, null);
235
236 1
        return isset($value);
237
    }
238
239
    /**
240
     * Gets a value of a key from the configuration via dot-notation.
241
     *
242
     * @param string $key The dotted key representation.
243
     * @param mixed $fallback [optional] The default fallback value.
244
     *
245
     * @return mixed The requested value or null.
246
     */
247 34
    public static function get(string $key, $fallback = null)
248
    {
249 34
        static::load();
250
251 34
        return Misc::getArrayValueByKey(static::$config, $key, $fallback);
252
    }
253
254
    /**
255
     * Sets a value of a key in the configuration via dot-notation.
256
     *
257
     * @param string $key The dotted key representation.
258
     * @param mixed $value The value to set.
259
     *
260
     * @return void
261
     */
262 4
    public static function set(string $key, $value): void
263
    {
264 4
        static::load();
265
266 4
        Misc::setArrayValueByKey(static::$config, $key, $value);
267 4
    }
268
269
    /**
270
     * Returns the currently loaded configuration.
271
     *
272
     * @return array
273
     */
274 3
    public static function getAll(): ?array
275
    {
276 3
        static::load();
277
278 3
        return static::$config;
279
    }
280
}
281