Passed
Push — master ( bbc3bd...e70b0b )
by Marwan
01:25
created

Config   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 230
Duplicated Lines 0 %

Test Coverage

Coverage 98.81%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 70
c 9
b 0
f 0
dl 0
loc 230
ccs 83
cts 84
cp 0.9881
rs 10
wmc 25

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