Test Failed
Push — main ( 57b19c...1fe2cd )
by Dimitri
15:57
created

Config   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 352
Duplicated Lines 0 %

Test Coverage

Coverage 67.44%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 131
c 5
b 0
f 0
dl 0
loc 352
ccs 58
cts 86
cp 0.6744
rs 4.5599
wmc 58

18 Methods

Rating   Name   Duplication   Size   Complexity  
A missing() 0 3 1
A has() 0 3 1
A __construct() 0 4 1
A set() 0 6 1
A exists() 0 10 2
A get() 0 13 3
A initializeURL() 0 9 3
A path() 0 15 4
A initializeAutoDetect() 0 5 1
A ghost() 0 5 1
B initializeEnvironment() 0 33 7
B loadRegistrar() 0 26 7
A initializeDebugbar() 0 10 3
A schema() 0 13 3
C load() 0 33 12
A initialize() 0 15 2
A exceptBadConfigValue() 0 7 2
A reset() 0 13 4

How to fix   Complexity   

Complex Class

Complex classes like Config often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Config, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Config;
13
14
use BlitzPHP\Autoloader\Autoloader;
15
use BlitzPHP\Autoloader\Locator;
16
use BlitzPHP\Container\Services;
17
use BlitzPHP\Exceptions\ConfigException;
18
use BlitzPHP\Utilities\Helpers;
19
use BlitzPHP\Utilities\Iterable\Arr;
20
use Nette\Schema\Expect;
21
use Nette\Schema\Schema;
22
use ReflectionClass;
23
use ReflectionMethod;
24
25
class Config
26
{
27
    /**
28
     * Fichier de configuration déjà chargé
29
     */
30
    private static array $loaded = [];
31
32
    /**
33
     * Configurations originales issues des fichiers de configuration
34
     *
35
     * Permet de réinitialiser les configuration par défaut au cas où on aurrait fait des modifications à la volée
36
     */
37
    private static array $originals = [];
38
39
    /**
40
     * Different registrars decouverts.
41
     *
42
     * Les registrars sont des mecanismes permettant aux packages externe de definir un elements de configuration
43
     */
44
    private static array $registrars = [];
45
46
    /**
47
     * Drapeau permettant de savoir si la config a deja ete initialiser
48
     */
49
    private static bool $initialized = false;
50
51
    private Configurator $configurator;
52
53
    public function __construct()
54
    {
55
        $this->configurator = new Configurator();
56
        $this->initialize();
57
    }
58
59
    /**
60
     * Détermine si une clé de configuration existe.
61
     */
62
    public function exists(string $key): bool
63
    {
64
        if (! $this->configurator->exists($key)) {
65 8
            $config = explode('.', $key);
66 8
            $this->load($config[0]);
67
68 8
            return $this->configurator->exists(implode('.', $config));
69
        }
70
71 20
        return true;
72
    }
73
74
    /**
75
     * Détermine s'il y'a une clé de configuration.
76
     */
77
    public function has(string $key): bool
78
    {
79 4
        return $this->exists($key);
80
    }
81
82
    /**
83
     * Détermine s'il manque une clé de configuration.
84
     */
85
    public function missing(string $key): bool
86
    {
87 2
        return ! $this->exists($key);
88
    }
89
90
    /**
91
     * Renvoyer une configuration de l'application
92
     *
93
     * @return mixed
94
     */
95
    public function get(string $key, mixed $default = null)
96
    {
97
        if ($this->exists($key)) {
98 20
            return $this->configurator->get($key);
99
        }
100
101
        if (func_num_args() > 1) {
102 6
            return $default;
103
        }
104
105 2
        $path = explode('.', $key);
106
107 2
        throw ConfigException::notFound(implode(' » ', $path));
108
    }
109
110
    /**
111
     * Définir une configuration de l'application
112
     *
113
     * @param mixed $value
114
     */
115
    public function set(string $key, $value)
116
    {
117 6
        $path = explode('.', $key);
118 6
        $this->load($path[0]);
119
120 6
        $this->configurator->set($key, $value);
121
    }
122
123
    /**
124
     * Reinitialise une configuration en fonction des donnees initiales issues des fichiers de configurations
125
     */
126
    public function reset(null|array|string $keys = null): void
127
    {
128
        if (null !== $keys) {
129 2
            $keys = (array) $keys;
130
        } else {
131 2
            $keys = array_keys(self::$originals);
132
        }
133
134
        foreach ($keys as $key) {
135 4
            $this->set($key, Arr::dataGet(self::$originals, $key));
136
137
            if (str_starts_with($key, 'app')) {
138 4
                $this->initializeAutoDetect();
139
            }
140
        }
141
    }
142
143
    /**
144
     * Rend disponible un groupe de configuration qui n'existe pas (pas de fichier de configuration)
145
     * Ceci est notament utilse pour definir des configurations à la volée
146
     */
147
    public function ghost(array|string $key, ?Schema $schema = null): static
148
    {
149 2
        $this->load($key, null, $schema, true);
150
151 2
        return $this;
152
    }
153
154
    /**
155
     * Charger la configuration spécifique dans le scoope
156
     *
157
     * @param string|string[] $config
158
     */
159
    public function load($config, ?string $file = null, ?Schema $schema = null, bool $allow_empty = false)
160
    {
161
        if (is_array($config)) {
162
            foreach ($config as $key => $value) {
163
                if (is_string($key)) {
164
                    $file = $value;
165
                    $conf = $key;
166
                } else {
167 2
                    $file = null;
168 2
                    $conf = $value;
169
                }
170 2
                $this->load($conf, $file, null, $allow_empty);
171
            }
172
        } elseif (! isset(self::$loaded[$config])) {
173 6
            $file ??= self::path($config);
174 6
            $schema ??= self::schema($config);
175
176 6
            $configurations = [];
177
            if (file_exists($file) && ! in_array($file, get_included_files(), true)) {
178 4
                $configurations = (array) require $file;
179
            }
180
181 6
            $configurations = Arr::merge(self::$registrars[$config] ?? [], $configurations);
182
183
            if (empty($configurations) && ! $allow_empty && (empty($schema) || ! is_a($schema, Schema::class))) {
184 2
                return;
185
            }
186
187 6
            $this->configurator->addSchema($config, $schema ?: Expect::mixed(), false);
188 6
            $this->configurator->merge([$config => $configurations]);
189
190 6
            self::$loaded[$config]    = $file;
191 6
            self::$originals[$config] = $this->configurator->get($config);
192
        }
193
    }
194
195
    /**
196
     * Affiche l'exception dû à la mauvaise definition d'une configuration
197
     *
198
     * @param string $group (app, data, database, etc.)
199
     */
200
    public static function exceptBadConfigValue(string $config_key, array|string $accepts_values, string $group)
201
    {
202
        if (is_array($accepts_values)) {
0 ignored issues
show
introduced by
The condition is_array($accepts_values) is always true.
Loading history...
203
            $accepts_values = '(Accept values: ' . implode('/', $accepts_values) . ')';
204
        }
205
206
        throw new ConfigException("The '{$group}.{$config_key} configuration is not set correctly. {$accepts_values} \n Please edit '{" . self::path($group) . "}' file to correct it");
207
    }
208
209
    /**
210
     * Renvoie le chemin du fichier d'un groupe de configuration donné
211
     */
212
    public static function path(string $path): string
213
    {
214 8
        $path = preg_replace('#\.php$#', '', $path);
215
216
        if (file_exists($file = CONFIG_PATH . $path . '.php')) {
217 6
            return $file;
218
        }
219
220 4
        $paths = Services::locator()->search('Config/' . $path);
221
222
        if (isset($paths[0]) && file_exists($path[0])) {
223 4
            return $paths[0];
224
        }
225
226 4
        return '';
227
    }
228
229
    /**
230
     * Retrouve le schema de configuration d'un groupe
231
     */
232
    public static function schema(string $key): ?Schema
233
    {
234 8
        $file        = 'schemas' . DS . Helpers::ensureExt($key . '.config', 'php');
235 8
        $syst_schema = SYST_PATH . 'Constants' . DS . $file;
236 8
        $app_schema  = CONFIG_PATH . $file;
237
238
        if (file_exists($syst_schema)) {
239 2
            $schema = require $syst_schema;
240
        } elseif (file_exists($app_schema)) {
241
            $schema = require $app_schema;
242
        }
243
244 8
        return $schema ?? null;
245
    }
246
247
    /**
248
     * Initialiser la configuration du système avec les données des fichier de configuration
249
     */
250
    private function initialize()
251
    {
252
        if (self::$initialized) {
253
            return;
254
        }
255
256
        $this->loadRegistrar();
257
        $this->load(['app']);
258
259
        ini_set('log_errors', 1);
260
        ini_set('error_log', LOG_PATH . 'blitz-logs');
261
262
        $this->initializeAutoDetect();
263
264
        self::$initialized = true;
265
    }
266
267
    /**
268
     * Charges les registrars disponible pour l'application.
269
     * Les registrars sont des mecanismes permettant aux packages externe de definir un elements de configuration
270
     */
271
    private function loadRegistrar()
272
    {
273
        $autoloader = new Autoloader(['psr4' => [APP_NAMESPACE => APP_PATH]]);
274
        $locator    = new Locator($autoloader->initialize());
275
276
        $registrarsFiles = $locator->search('Config/Registrar.php');
277
278
        foreach ($registrarsFiles as $file) {
279
            if (false === $classname = $locator->findQualifiedNameFromPath($file)) {
280
                continue;
281
            }
282
283
            $class   = new ReflectionClass($classname);
284
            $methods = $class->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC);
285
286
            foreach ($methods as $method) {
287
                if (! ($method->isPublic() && $method->isStatic())) {
288
                    continue;
289
                }
290
291
                if (! is_array($result = $method->invoke(null))) {
292
                    continue;
293
                }
294
295
                $name                    = $method->getName();
296
                self::$registrars[$name] = Arr::merge(self::$registrars[$name] ?? [], $result);
297
            }
298
        }
299
    }
300
301
    /**
302
     * Initialise l'URL
303
     */
304
    private function initializeURL()
305
    {
306 4
        $config = $this->get('app.base_url', 'auto');
307
308
        if ($config === 'auto' || empty($config)) {
309 4
            $config = rtrim(str_replace('\\', '/', Helpers::findBaseUrl()), '/');
310
        }
311
312 4
        $this->set('app.base_url', $config);
313
    }
314
315
    /**
316
     * Initialise l'environnement d'execution de l'application
317
     */
318
    private function initializeEnvironment()
319
    {
320 4
        $environment = $config = $this->get('app.environment');
321
322
        $config = match ($config) {
323
            'auto'  => is_online() ? 'production' : 'development',
324
            'dev'   => 'development',
325
            'prod'  => 'production',
326
            'test'  => 'testing',
327
            default => $config,
328
        };
329
330
        if ($config !== $environment) {
331 4
            $this->set('app.environment', $config);
332
        }
333
334
        switch ($config) {
335
            case 'development':
336 4
                error_reporting(-1);
337
                ini_set('display_errors', 1);
338
                break;
339
340
            case 'testing':
341
            case 'production':
342 4
                ini_set('display_errors', 0);
343 4
                error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
344 4
                break;
345
346
            default:
347
                self::exceptBadConfigValue('environment', ['development', 'production', 'testing', 'auto'], 'app');
348
        }
349
350 4
        defined('BLITZ_DEBUG') || define('BLITZ_DEBUG', $config !== 'production');
351
    }
352
353
    /**
354
     * Initialise les paramètres de la bar de debug
355
     */
356
    private function initializeDebugbar()
357
    {
358 4
        $config = $this->get('app.show_debugbar', 'auto');
359
360
        if (! in_array($config, ['auto', true, false], true)) {
361 4
            self::exceptBadConfigValue('show_debugbar', ['auto', true, false], 'app');
362
        }
363
364
        if ($config === 'auto') {
365 4
            $this->set('app.show_debugbar', ! is_online());
366
        }
367
    }
368
369
    /**
370
     * Initialise les donnees qui ont une valeur definie a "auto" et donc dependent de certains facteurs
371
     */
372
    private function initializeAutoDetect(): void
373
    {
374 4
        $this->initializeURL();
375 4
        $this->initializeEnvironment();
376 4
        $this->initializeDebugbar();
377
    }
378
}
379