Passed
Push — main ( d0f73f...902994 )
by Dimitri
04:06
created

Config   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Test Coverage

Coverage 64.37%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 134
dl 0
loc 356
ccs 56
cts 87
cp 0.6437
rs 3.44
c 5
b 0
f 0
wmc 62

18 Methods

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

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 InvalidArgumentException;
21
use Nette\Schema\Expect;
22
use Nette\Schema\Schema;
23
use ReflectionClass;
24
use ReflectionMethod;
25
26
class Config
27
{
28
    /**
29
     * Fichier de configuration déjà chargé
30
     */
31
    private static array $loaded = [];
32
33
    /**
34
     * Configurations originales issues des fichiers de configuration
35
     *
36
     * Permet de réinitialiser les configuration par défaut au cas où on aurrait fait des modifications à la volée
37
     */
38
    private static array $originals = [];
39
40
    /**
41
     * Different registrars decouverts.
42
     *
43
     * Les registrars sont des mecanismes permettant aux packages externe de definir un elements de configuration
44
     */
45
    private static array $registrars = [];
46
47
    /**
48
     * Drapeau permettant de savoir si la config a deja ete initialiser
49
     */
50
    private static bool $initialized = false;
51
52
    private Configurator $configurator;
53
54
    public function __construct()
55
    {
56
        $this->configurator = new Configurator();
57
        $this->initialize();
58
    }
59
60
    /**
61
     * Détermine si une clé de configuration existe.
62
     */
63
    public function exists(string $key): bool
64
    {
65
        if (! $this->configurator->exists($key)) {
66 12
            $config = explode('.', $key);
67 12
            $this->load($config[0]);
68
69 12
            return $this->configurator->exists(implode('.', $config));
70
        }
71
72 115
        return true;
73
    }
74
75
    /**
76
     * Détermine s'il y'a une clé de configuration.
77
     */
78
    public function has(string $key): bool
79
    {
80 2
        return $this->exists($key);
81
    }
82
83
    /**
84
     * Détermine s'il manque une clé de configuration.
85
     */
86
    public function missing(string $key): bool
87
    {
88 2
        return ! $this->exists($key);
89
    }
90
91
    /**
92
     * Renvoyer une configuration de l'application
93
     *
94
     * @return mixed
95
     */
96
    public function get(string $key, mixed $default = null)
97
    {
98
        if ($this->exists($key)) {
99 119
            return $this->configurator->get($key);
100
        }
101
102
        if (func_num_args() > 1) {
103 4
            return $default;
104
        }
105
106 2
        $path = explode('.', $key);
107
108 2
        throw ConfigException::notFound(implode(' » ', $path));
109
    }
110
111
    /**
112
     * Définir une configuration de l'application
113
     *
114
     * @param mixed $value
115
     */
116
    public function set(string $key, $value)
117
    {
118 15
        $path = explode('.', $key);
119 15
        $this->load($path[0]);
120
121 15
        $this->configurator->set($key, $value);
122
    }
123
124
    /**
125
     * Reinitialise une configuration en fonction des donnees initiales issues des fichiers de configurations
126
     */
127
    public function reset(null|array|string $keys = null): void
128
    {
129
        if (null !== $keys) {
130 2
            $keys = (array) $keys;
131
        } else {
132
            $keys = array_keys(self::$originals);
133
        }
134
135
        foreach ($keys as $key) {
136 2
            $this->set($key, Arr::dataGet(self::$originals, $key));
137
138
            if (str_starts_with($key, 'app')) {
139 2
                $this->initializeAutoDetect();
140
            }
141
        }
142
    }
143
144
    /**
145
     * Rend disponible un groupe de configuration qui n'existe pas (pas de fichier de configuration)
146
     * Ceci est notament utilse pour definir des configurations à la volée
147
     */
148
    public function ghost(array|string $key, ?Schema $schema = null): void
149
    {
150 2
        $this->load($key, null, $schema, true);
151
    }
152
153
    /**
154
     * Charger la configuration spécifique dans le scoope
155
     *
156
     * @param string|string[] $config
157
     */
158
    public function load($config, ?string $file = null, ?Schema $schema = null, bool $allow_empty = false)
159
    {
160
        if (is_array($config)) {
161
            foreach ($config as $key => $value) {
162
                if (! is_string($value) || empty($value)) {
163
                    continue;
164
                }
165
                if (is_string($key)) {
166
                    $file = $value;
167
                    $conf = $key;
168
                } else {
169 2
                    $file = null;
170 2
                    $conf = $value;
171
                }
172 2
                self::load($conf, $file, null, $allow_empty);
0 ignored issues
show
Bug Best Practice introduced by
The method BlitzPHP\Config\Config::load() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

172
                self::/** @scrutinizer ignore-call */ 
173
                      load($conf, $file, null, $allow_empty);
Loading history...
173
            }
174
        } elseif (is_string($config) && ! isset(self::$loaded[$config])) {
175 12
            $file ??= self::path($config);
176 12
            $schema ??= self::schema($config);
177
178 12
            $configurations = [];
179
            if (file_exists($file) && ! in_array($file, get_included_files(), true)) {
180 8
                $configurations = (array) require $file;
181
            }
182
183 12
            $configurations = Arr::merge(self::$registrars[$config] ?? [], $configurations);
184
185
            if (empty($configurations) && ! $allow_empty && (empty($schema) || ! is_a($schema, Schema::class))) {
186 2
                return;
187
            }
188
189 12
            $this->configurator->addSchema($config, $schema ?: Expect::mixed(), false);
190 12
            $this->configurator->merge([$config => $configurations]);
191
192 12
            self::$loaded[$config]    = $file;
193 12
            self::$originals[$config] = $this->configurator->get($config);
194
        }
195
    }
196
197
    /**
198
     * Affiche l'exception dû à la mauvaise definition d'une configuration
199
     *
200
     * @param array|string $accepts_values
201
     * @param string       $group          (app, data, database, etc.)
202
     */
203
    public static function exceptBadConfigValue(string $config_key, $accepts_values, string $group)
204
    {
205
        if (is_array($accepts_values)) {
206
            $accepts_values = '(Accept values: ' . implode('/', $accepts_values) . ')';
207
        } elseif (! is_string($accepts_values)) {
0 ignored issues
show
introduced by
The condition is_string($accepts_values) is always true.
Loading history...
208
            throw new InvalidArgumentException('Misuse of the method ' . __METHOD__);
209
        }
210
211
        throw new ConfigException("The '{$group}.{$config_key} configuration is not set correctly. {$accepts_values} \n Please edit '{" . self::path($group) . "}' file to correct it");
212
    }
213
214
    /**
215
     * Renvoie le chemin du fichier d'un groupe de configuration donné
216
     */
217
    public static function path(string $path): string
218
    {
219 14
        $path = preg_replace('#\.php$#', '', $path);
220
221
        if (file_exists($file = CONFIG_PATH . $path . '.php')) {
222 10
            return $file;
223
        }
224
225 6
        $paths = Services::locator()->search('Config/' . $path);
226
227
        if (isset($paths[0]) && file_exists($path[0])) {
228 6
            return $paths[0];
229
        }
230
231 6
        return '';
232
    }
233
234
    /**
235
     * Retrouve le schema de configuration d'un groupe
236
     */
237
    public static function schema(string $key): ?Schema
238
    {
239 14
        $file        = 'schemas' . DS . Helpers::ensureExt($key . '.config', 'php');
240 14
        $syst_schema = SYST_PATH . 'Constants' . DS . $file;
241 14
        $app_schema  = CONFIG_PATH . $file;
242
243
        if (file_exists($syst_schema)) {
244 6
            $schema = require $syst_schema;
245
        } elseif (file_exists($app_schema)) {
246
            $schema = require $app_schema;
247
        }
248
249 14
        return $schema ?? null;
250
    }
251
252
    /**
253
     * Initialiser la configuration du système avec les données des fichier de configuration
254
     */
255
    private function initialize()
256
    {
257
        if (self::$initialized) {
258
            return;
259
        }
260
261
        $this->loadRegistrar();
262
        $this->load(['app']);
263
264
        ini_set('log_errors', 1);
265
        ini_set('error_log', LOG_PATH . 'blitz-logs');
266
267
        $this->initializeAutoDetect();
268
269
        self::$initialized = true;
270
    }
271
272
    /**
273
     * Charges les registrars disponible pour l'application.
274
     * Les registrars sont des mecanismes permettant aux packages externe de definir un elements de configuration
275
     */
276
    private function loadRegistrar()
277
    {
278
        $autoloader = new Autoloader(['psr4' => [APP_NAMESPACE => APP_PATH]]);
279
        $locator    = new Locator($autoloader->initialize());
280
281
        $registrarsFiles = $locator->search('Config/Registrar.php');
282
283
        foreach ($registrarsFiles as $file) {
284
            if (false === $classname = $locator->findQualifiedNameFromPath($file)) {
285
                continue;
286
            }
287
288
            $class   = new ReflectionClass($classname);
289
            $methods = $class->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC);
290
291
            foreach ($methods as $method) {
292
                if (! ($method->isPublic() && $method->isStatic())) {
293
                    continue;
294
                }
295
296
                if (! is_array($result = $method->invoke(null))) {
297
                    continue;
298
                }
299
300
                $name                    = $method->getName();
301
                self::$registrars[$name] = Arr::merge(self::$registrars[$name] ?? [], $result);
302
            }
303
        }
304
    }
305
306
    /**
307
     * Initialise l'URL
308
     */
309
    private function initializeURL()
310
    {
311 2
        $config = $this->get('app.base_url', 'auto');
312
313
        if ($config === 'auto' || empty($config)) {
314 2
            $config = rtrim(str_replace('\\', '/', Helpers::findBaseUrl()), '/');
315
        }
316
317 2
        $this->set('app.base_url', $config);
318
    }
319
320
    /**
321
     * Initialise l'environnement d'execution de l'application
322
     */
323
    private function initializeEnvironment()
324
    {
325 2
        $environment = $config = $this->get('app.environment');
326
327
        $config = match ($config) {
328
            'auto'  => is_online() ? 'production' : 'development',
329
            'dev'   => 'development',
330
            'prod'  => 'production',
331
            'test'  => 'testing',
332
            default => $config,
333
        };
334
335
        if ($config !== $environment) {
336 2
            $this->set('app.environment', $config);
337
        }
338
339
        switch ($config) {
340
            case 'development':
341 2
                error_reporting(-1);
342
                ini_set('display_errors', 1);
343
                break;
344
345
            case 'testing':
346
            case 'production':
347 2
                ini_set('display_errors', 0);
348 2
                error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
349 2
                break;
350
351
            default:
352
                self::exceptBadConfigValue('environment', ['development', 'production', 'testing', 'auto'], 'app');
353
        }
354
355 2
        defined('BLITZ_DEBUG') || define('BLITZ_DEBUG', $config !== 'production');
356
    }
357
358
    /**
359
     * Initialise les paramètres de la bar de debug
360
     */
361
    private function initializeDebugbar()
362
    {
363 2
        $config = $this->get('app.show_debugbar', 'auto');
364
365
        if (! in_array($config, ['auto', true, false], true)) {
366 2
            self::exceptBadConfigValue('show_debugbar', ['auto', true, false], 'app');
367
        }
368
369
        if ($config === 'auto') {
370 2
            $this->set('app.show_debugbar', ! is_online());
371
        }
372
    }
373
374
    /**
375
     * Initialise les donnees qui ont une valeur definie a "auto" et donc dependent de certains facteurs
376
     */
377
    private function initializeAutoDetect(): void
378
    {
379 2
        $this->initializeURL();
380 2
        $this->initializeEnvironment();
381 2
        $this->initializeDebugbar();
382
    }
383
}
384