Config   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 392
Duplicated Lines 0 %

Test Coverage

Coverage 63.83%

Importance

Changes 10
Bugs 2 Features 0
Metric Value
eloc 147
c 10
b 2
f 0
dl 0
loc 392
ccs 60
cts 94
cp 0.6383
rs 3.44
wmc 62

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 reset() 0 9 4
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
B initializeEnvironment() 0 33 7
B loadRegistrar() 0 49 9
A initializeDebugbar() 0 10 3
A schema() 0 19 5
B load() 0 33 11
A initialize() 0 15 2
A exceptBadConfigValue() 0 7 2
A ghost() 0 7 2

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\Exceptions\ConfigException;
17
use BlitzPHP\Utilities\Helpers;
18
use BlitzPHP\Utilities\Iterable\Arr;
19
use Nette\Schema\Expect;
20
use Nette\Schema\Schema;
21
use ReflectionClass;
22
use ReflectionMethod;
23
24
class Config
25
{
26
    /**
27
     * Fichier de configuration déjà chargé
28
     */
29
    private static array $loaded = [];
30
31
    /**
32
     * Configurations originales issues des fichiers de configuration
33
     *
34
     * Permet de réinitialiser les configuration par défaut au cas où on aurrait fait des modifications à la volée
35
     */
36
    private static array $originals = [];
37
38
    /**
39
     * Different registrars decouverts.
40
     *
41
     * Les registrars sont des mecanismes permettant aux packages externe de definir un elements de configuration
42
     */
43
    private static array $registrars = [];
44
45
    /**
46
     *  La découverte des modules est-elle terminée ?
47
     */
48
    protected static bool $didDiscovery = false;
49
50
    /**
51
     *  Le module discovery fonctionne-t-il ou non ?
52
     */
53
    protected static bool $discovering = false;
54
55
    /**
56
     * Le traitement du fichier Registrar pour le message d'erreur.
57
     */
58
    protected static string $registrarFile = '';
59
60
    /**
61
     * Drapeau permettant de savoir si la config a deja ete initialiser
62
     */
63
    private static bool $initialized = false;
64
65
    private readonly Configurator $configurator;
66
67
    public function __construct()
68
    {
69
        $this->configurator = new Configurator();
0 ignored issues
show
Bug introduced by
The property configurator is declared read-only in BlitzPHP\Config\Config.
Loading history...
70
        $this->initialize();
71
    }
72
73
    /**
74
     * Détermine si une clé de configuration existe.
75
     */
76
    public function exists(string $key): bool
77
    {
78
        if (! $this->configurator->exists($key)) {
79 24
            $config = explode('.', $key);
80 24
            $this->load($config[0]);
0 ignored issues
show
Bug introduced by
$config[0] of type string is incompatible with the type BlitzPHP\Config\list expected by parameter $config of BlitzPHP\Config\Config::load(). ( Ignorable by Annotation )

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

80
            $this->load(/** @scrutinizer ignore-type */ $config[0]);
Loading history...
81
82 24
            return $this->configurator->exists(implode('.', $config));
83
        }
84
85 206
        return true;
86
    }
87
88
    /**
89
     * Détermine s'il y'a une clé de configuration.
90
     */
91
    public function has(string $key): bool
92
    {
93 4
        return $this->exists($key);
94
    }
95
96
    /**
97
     * Détermine s'il manque une clé de configuration.
98
     */
99
    public function missing(string $key): bool
100
    {
101 2
        return ! $this->exists($key);
102
    }
103
104
    /**
105
     * Renvoyer une configuration de l'application
106
     *
107
     * @return mixed
108
     */
109
    public function get(string $key, mixed $default = null)
110
    {
111
        if ($this->exists($key)) {
112 210
            return $this->configurator->get($key);
113
        }
114
115
        if (func_num_args() > 1) {
116 10
            return $default;
117
        }
118
119 2
        $path = explode('.', $key);
120
121 2
        throw ConfigException::notFound(implode(' » ', $path));
122
    }
123
124
    /**
125
     * Définir une configuration de l'application
126
     *
127
     * @param mixed $value
128
     */
129
    public function set(string $key, $value)
130
    {
131 28
        $path = explode('.', $key);
132 28
        $this->load($path[0]);
0 ignored issues
show
Bug introduced by
$path[0] of type string is incompatible with the type BlitzPHP\Config\list expected by parameter $config of BlitzPHP\Config\Config::load(). ( Ignorable by Annotation )

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

132
        $this->load(/** @scrutinizer ignore-type */ $path[0]);
Loading history...
133
134 28
        $this->configurator->set($key, $value);
135
    }
136
137
    /**
138
     * Reinitialise une configuration en fonction des donnees initiales issues des fichiers de configurations
139
     */
140
    public function reset(array|string|null $keys = null): void
141
    {
142 18
        $keys = null !== $keys ? (array) $keys : array_keys(self::$originals);
143
144
        foreach ($keys as $key) {
145 18
            $this->set($key, Arr::dataGet(self::$originals, $key));
146
147
            if (str_starts_with($key, 'app')) {
148 6
                $this->initializeAutoDetect();
149
            }
150
        }
151
    }
152
153
    /**
154
     * Rend disponible un groupe de configuration qui n'existe pas (pas de fichier de configuration)
155
     * Ceci est notament utilse pour definir des configurations à la volée
156
     */
157
    public function ghost(array|string $key, array|Schema|null $structure = null): static
158
    {
159 4
        $schema = is_array($structure) ? Expect::mixed($structure) : $structure;
160
161 4
        $this->load($key, null, $schema, true);
0 ignored issues
show
Bug introduced by
$key of type array is incompatible with the type BlitzPHP\Config\list expected by parameter $config of BlitzPHP\Config\Config::load(). ( Ignorable by Annotation )

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

161
        $this->load(/** @scrutinizer ignore-type */ $key, null, $schema, true);
Loading history...
162
163 4
        return $this;
164
    }
165
166
    /**
167
     * Charger la configuration spécifique dans le scoope
168
     *
169
     * @param list<string>|string $config
0 ignored issues
show
Bug introduced by
The type BlitzPHP\Config\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
170
     */
171
    public function load($config, ?string $file = null, ?Schema $schema = null, bool $allow_empty = false)
172
    {
173
        if (is_array($config)) {
0 ignored issues
show
introduced by
The condition is_array($config) is always false.
Loading history...
174
            foreach ($config as $key => $value) {
175
                if (is_string($key)) {
176
                    $file = $value;
177
                    $conf = $key;
178
                } else {
179 2
                    $file = null;
180 2
                    $conf = $value;
181
                }
182 2
                $this->load($conf, $file, null, $allow_empty);
183
            }
184
        } elseif (! isset(self::$loaded[$config])) {
185 20
            $file ??= self::path($config);
186 20
            $schema ??= self::schema($config);
187
188 20
            $configurations = [];
189
            if (file_exists($file) && ! in_array($file, get_included_files(), true)) {
190 12
                $configurations = (array) require $file;
191
            }
192
193 20
            $configurations = Arr::merge(self::$registrars[$config] ?? [], $configurations);
194
195
            if (empty($configurations) && ! $allow_empty && ! is_a($schema, Schema::class, true)) {
196 2
                return;
197
            }
198
199 20
            $this->configurator->addSchema($config, $schema ?: Expect::mixed(), false);
200 20
            $this->configurator->merge([$config => $configurations]);
201
202 20
            self::$loaded[$config]    = $file;
203 20
            self::$originals[$config] = $this->configurator->get($config);
204
        }
205
    }
206
207
    /**
208
     * Affiche l'exception dû à la mauvaise definition d'une configuration
209
     *
210
     * @param string $group (app, data, database, etc.)
211
     */
212
    public static function exceptBadConfigValue(string $config_key, array|string $accepts_values, string $group)
213
    {
214
        if (is_array($accepts_values)) {
0 ignored issues
show
introduced by
The condition is_array($accepts_values) is always true.
Loading history...
215
            $accepts_values = '(Accept values: ' . implode('/', $accepts_values) . ')';
216
        }
217
218
        throw new ConfigException("The '{$group}.{$config_key} configuration is not set correctly. {$accepts_values} \n Please edit '{" . self::path($group) . "}' file to correct it");
219
    }
220
221
    /**
222
     * Renvoie le chemin du fichier d'un groupe de configuration donné
223
     */
224
    public static function path(string $path): string
225
    {
226 22
        $path = preg_replace('#\.php$#', '', $path);
227
228
        if (file_exists($file = CONFIG_PATH . $path . '.php')) {
229 14
            return $file;
230
        }
231
232 10
        $paths = service('locator')->search('Config/' . $path);
233
234
        if (isset($paths[0]) && file_exists($path[0])) {
235 10
            return $paths[0];
236
        }
237
238 10
        return '';
239
    }
240
241
    /**
242
     * Retrouve le schema de configuration d'un groupe
243
     */
244
    public static function schema(string $key): ?Schema
245
    {
246 22
        $file        = 'schemas' . DS . Helpers::ensureExt($key . '.config', 'php');
247 22
        $syst_schema = SYST_PATH . 'Constants' . DS . $file;
248 22
        $app_schema  = CONFIG_PATH . $file;
249
250
        if (file_exists($syst_schema)) {
251 12
            $schema = require $syst_schema;
252
        } elseif (file_exists($app_schema)) {
253
            $schema = require $app_schema;
254
        } else {
255 12
            $paths = service('locator')->search('Config/schemas/' . $key);
256
257
            if (isset($paths[0]) && file_exists($paths[0])) {
258 12
                $schema = require $paths[0];
259
            }
260
        }
261
262 22
        return $schema ?? null;
263
    }
264
265
    /**
266
     * Initialiser la configuration du système avec les données des fichier de configuration
267
     */
268
    private function initialize()
269
    {
270
        if (self::$initialized) {
271
            return;
272
        }
273
274
        $this->loadRegistrar();
275
        $this->load(['app']);
0 ignored issues
show
Bug introduced by
array('app') of type array<integer,string> is incompatible with the type BlitzPHP\Config\list expected by parameter $config of BlitzPHP\Config\Config::load(). ( Ignorable by Annotation )

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

275
        $this->load(/** @scrutinizer ignore-type */ ['app']);
Loading history...
276
277
        ini_set('log_errors', 1);
278
        ini_set('error_log', LOG_PATH . 'blitz-logs');
279
280
        $this->initializeAutoDetect();
281
282
        self::$initialized = true;
283
    }
284
285
    /**
286
     * Charges les registrars disponible pour l'application.
287
     * Les registrars sont des mecanismes permettant aux packages externe de definir un elements de configuration
288
     */
289
    private function loadRegistrar()
290
    {
291
        if (static::$didDiscovery) {
292
            return;
293
        }
294
295
        // La decouverte doit etre complete pres la premiere initalisation de la classe.
296
        if (static::$discovering) {
297
            throw new ConfigException(
298
                'Pendant la découverte automatique des Registrars,'
299
                . ' "' . static::class . '" a été re-éxecuté.'
300
                . ' "' . clean_path(static::$registrarFile) . '" doit avoir un mauvais code.'
301
            );
302
        }
303
304
        static::$discovering = true;
305
306
        $autoloader = new Autoloader(['psr4' => [APP_NAMESPACE => APP_PATH]]);
307
        $locator    = new Locator($autoloader->initialize());
308
309
        $registrarsFiles = $locator->search('Config/Registrar.php');
310
311
        foreach ($registrarsFiles as $file) {
312
            // Enregistre le fichier pour le message d'erreur.
313
            static::$registrarFile = $file;
314
315
            if (false === $classname = $locator->findQualifiedNameFromPath($file)) {
316
                continue;
317
            }
318
319
            $class   = new ReflectionClass($classname);
320
            $methods = $class->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC);
321
322
            foreach ($methods as $method) {
323
                if (! ($method->isPublic() && $method->isStatic())) {
324
                    continue;
325
                }
326
327
                if (! is_array($result = $method->invoke(null))) {
328
                    continue;
329
                }
330
331
                $name                    = $method->getName();
332
                self::$registrars[$name] = Arr::merge(self::$registrars[$name] ?? [], $result);
333
            }
334
        }
335
336
        static::$didDiscovery = true;
337
        static::$discovering  = false;
338
    }
339
340
    /**
341
     * Initialise l'URL
342
     */
343
    private function initializeURL()
344
    {
345 6
        $config = $this->get('app.base_url', 'auto');
346
347
        if ($config === 'auto' || empty($config)) {
348 6
            $config = rtrim(str_replace('\\', '/', Helpers::findBaseUrl()), '/');
349
        }
350
351 6
        $this->set('app.base_url', $config);
352
    }
353
354
    /**
355
     * Initialise l'environnement d'execution de l'application
356
     */
357
    private function initializeEnvironment()
358
    {
359 6
        $environment = $config = $this->get('app.environment');
360
361
        $config = match ($config) {
362
            'auto'  => is_online() ? 'production' : 'development',
363
            'dev'   => 'development',
364
            'prod'  => 'production',
365
            'test'  => 'testing',
366
            default => $config,
367
        };
368
369
        if ($config !== $environment) {
370 6
            $this->set('app.environment', $config);
371
        }
372
373
        switch ($config) {
374
            case 'development':
375 6
                error_reporting(-1);
376
                ini_set('display_errors', 1);
377
                break;
378
379
            case 'testing':
380
            case 'production':
381 6
                ini_set('display_errors', 0);
382 6
                error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
383 6
                break;
384
385
            default:
386
                self::exceptBadConfigValue('environment', ['development', 'production', 'testing', 'auto'], 'app');
387
        }
388
389 6
        defined('BLITZ_DEBUG') || define('BLITZ_DEBUG', $config !== 'production');
390
    }
391
392
    /**
393
     * Initialise les paramètres de la bar de debug
394
     */
395
    private function initializeDebugbar()
396
    {
397 6
        $config = $this->get('app.show_debugbar', 'auto');
398
399
        if (! in_array($config, ['auto', true, false], true)) {
400 6
            self::exceptBadConfigValue('show_debugbar', ['auto', true, false], 'app');
401
        }
402
403
        if ($config === 'auto') {
404 4
            $this->set('app.show_debugbar', ! is_online());
405
        }
406
    }
407
408
    /**
409
     * Initialise les donnees qui ont une valeur definie a "auto" et donc dependent de certains facteurs
410
     */
411
    private function initializeAutoDetect(): void
412
    {
413 6
        $this->initializeURL();
414 6
        $this->initializeEnvironment();
415 6
        $this->initializeDebugbar();
416
    }
417
}
418