Passed
Push — master ( e70b0b...e92a23 )
by Marwan
01:20
created

Config::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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
                $info = pathinfo($file);
88 7
                return $info['extension'] == 'php' ? include($file) : [];
89
            }
90
91 1
            return self::include($file);
92 7
        };
93
94
        // load all config files
95 7
        $filenames = scandir($path) ?: [];
96 7
        foreach ($filenames as $filename) {
97 7
            $file = sprintf('%s/%s', $path, $filename);
98 7
            $config = basename($filename, '.php');
99
100
            // do not include items that have dots in their names
101
            // as this will conflict with array access separator
102 7
            if (strpos($config, '.') !== false) {
103 7
                continue;
104
            }
105
106 7
            $includes[$config] = isset($includes[$config])
107
                ? $include($file) + (array)$includes[$config]
108 7
                : $include($file);
109
        }
110
111 7
        return $includes;
112
    }
113
114
    /**
115
     * Parses the configuration to replace reference of some "{filename.config.varName}" with actual value from the passed configuration.
116
     *
117
     * @param array $config
118
     *
119
     * @return array
120
     */
121 7
    private static function parse(array $config): array
122
    {
123
        // parses all config variables
124 7
        $tries = count($config);
125 7
        for ($i = 0; $i < $tries; $i++) {
126 7
            array_walk_recursive($config, function (&$value) use (&$config) {
127 7
                if (is_string($value)) {
128 7
                    if (preg_match_all('/{([a-z0-9_\-\.]*)}/i', $value, $matches)) {
129 7
                        $variables = [];
130 7
                        array_walk($matches[1], function (&$variable) use (&$variables, &$config) {
131 7
                            $variables[$variable] = Misc::getArrayValueByKey($config, $variable, null);
132 7
                        });
133
134 7
                        $value = $value === $matches[0][0]
135 7
                            ? $variables[$matches[1][0]]
136 7
                            : Misc::interpolate($value, $variables);
137
                    }
138
                }
139 7
            });
140
        }
141
142 7
        return $config;
143
    }
144
145
    /**
146
     * Loads the configuration (directly or when available from cache) and sets class internal state.
147
     *
148
     * @return void
149
     */
150 29
    protected static function load(): void
151
    {
152 29
        $configDir       = static::CONFIG_DIR;
153 29
        $configCacheFile = static::CONFIG_CACHE_FILE;
154
155 29
        if (empty(static::$config)) {
156 7
            if (file_exists($configCacheFile)) {
157 1
                $configJson     = file_get_contents($configCacheFile);
158 1
                static::$config = json_decode($configJson, true);
159
160 1
                return;
161
            }
162
163 7
            static::$config = self::parse(self::include($configDir));
164
        }
165 29
    }
166
167
    /**
168
     * Caches the current configuration as JSON. Note that a new version will not be generated unless the cache is cleared.
169
     *
170
     * @return void
171
     */
172 3
    public static function cache(): void
173
    {
174 3
        $configDir       = static::CONFIG_DIR;
175 3
        $configCacheFile = static::CONFIG_CACHE_FILE;
176 3
        $configCacheDir  = dirname($configCacheFile);
177
178 3
        if (file_exists($configCacheFile)) {
179 1
            return;
180
        }
181
182 2
        if (!file_exists($configCacheDir)) {
183 2
            mkdir($configCacheDir, 0744, true);
184
        }
185
186 2
        $config     = self::parse(self::include($configDir));
187 2
        $configJson = json_encode($config, JSON_PRETTY_PRINT);
188
189 2
        file_put_contents($configCacheFile, $configJson, LOCK_EX);
190
191 2
        App::log(
192 2
            'Generated cache for system config, checksum (SHA-256: {checksum})',
193 2
            ['checksum' => hash('sha256', $configJson)],
194 2
            'system'
195
        );
196 2
    }
197
198
    /**
199
     * Deletes the cached configuration JSON and resets class internal state.
200
     *
201
     * @return void
202
     */
203 2
    public static function clearCache(): void
204
    {
205 2
        static::$config = [];
206
207 2
        $configCacheFile = static::CONFIG_CACHE_FILE;
208
209 2
        if (file_exists($configCacheFile)) {
210 1
            unlink($configCacheFile);
211
        }
212
213 2
        App::log('Cleared config cache', null, 'system');
214 2
    }
215
216
    /**
217
     * Checks whether a value of a key exists in the configuration via dot-notation.
218
     *
219
     * @param string $key The dotted key representation.
220
     *
221
     * @return bool
222
     */
223 1
    public static function has(string $key): bool
224
    {
225 1
        static::load();
226
227 1
        $value = Misc::getArrayValueByKey(static::$config, $key, null);
228
229 1
        return isset($value);
230
    }
231
232
    /**
233
     * Gets a value of a key from the configuration via dot-notation.
234
     *
235
     * @param string $key The dotted key representation.
236
     * @param mixed $fallback [optional] The default fallback value.
237
     *
238
     * @return mixed The requested value or null.
239
     */
240 20
    public static function get(string $key, $fallback = null)
241
    {
242 20
        static::load();
243
244 20
        return Misc::getArrayValueByKey(static::$config, $key, $fallback);
245
    }
246
247
    /**
248
     * Sets a value of a key in the configuration via dot-notation.
249
     *
250
     * @param string $key The dotted key representation.
251
     * @param mixed $value The value to set.
252
     *
253
     * @return void
254
     */
255 4
    public static function set(string $key, $value): void
256
    {
257 4
        static::load();
258
259 4
        Misc::setArrayValueByKey(static::$config, $key, $value);
260 4
    }
261
262
    /**
263
     * Returns the currently loaded configuration.
264
     *
265
     * @return array
266
     */
267 3
    public static function getAll(): ?array
268
    {
269 3
        static::load();
270
271 3
        return static::$config;
272
    }
273
}
274