Passed
Push — main ( eb4a74...9116b3 )
by Dimitri
08:20 queued 04:05
created

Config::load()   B

Complexity

Conditions 11
Paths 8

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 11.2865

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 11
eloc 22
c 3
b 0
f 0
nc 8
nop 4
dl 0
loc 33
ccs 13
cts 15
cp 0.8667
crap 11.2865
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
     *  La découverte des modules est-elle terminée ?
48
     */
49
    protected static bool $didDiscovery = false;
50
51
    /**
52
     *  Le module discovery fonctionne-t-il ou non ?
53
     */
54
    protected static bool $discovering = false;
55
56
    /**
57
     * Le traitement du fichier Registrar pour le message d'erreur.
58
     */
59
    protected static string $registrarFile = '';
60
61
    /**
62
     * Drapeau permettant de savoir si la config a deja ete initialiser
63
     */
64
    private static bool $initialized = false;
65
66
    private readonly Configurator $configurator;
67
68
    public function __construct()
69
    {
70
        $this->configurator = new Configurator();
0 ignored issues
show
Bug introduced by
The property configurator is declared read-only in BlitzPHP\Config\Config.
Loading history...
71
        $this->initialize();
72
    }
73
74
    /**
75
     * Détermine si une clé de configuration existe.
76
     */
77
    public function exists(string $key): bool
78
    {
79
        if (! $this->configurator->exists($key)) {
80 20
            $config = explode('.', $key);
81 20
            $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

81
            $this->load(/** @scrutinizer ignore-type */ $config[0]);
Loading history...
82
83 20
            return $this->configurator->exists(implode('.', $config));
84
        }
85
86 205
        return true;
87
    }
88
89
    /**
90
     * Détermine s'il y'a une clé de configuration.
91
     */
92
    public function has(string $key): bool
93
    {
94 4
        return $this->exists($key);
95
    }
96
97
    /**
98
     * Détermine s'il manque une clé de configuration.
99
     */
100
    public function missing(string $key): bool
101
    {
102 2
        return ! $this->exists($key);
103
    }
104
105
    /**
106
     * Renvoyer une configuration de l'application
107
     *
108
     * @return mixed
109
     */
110
    public function get(string $key, mixed $default = null)
111
    {
112
        if ($this->exists($key)) {
113 211
            return $this->configurator->get($key);
114
        }
115
116
        if (func_num_args() > 1) {
117 10
            return $default;
118
        }
119
120 2
        $path = explode('.', $key);
121
122 2
        throw ConfigException::notFound(implode(' » ', $path));
123
    }
124
125
    /**
126
     * Définir une configuration de l'application
127
     *
128
     * @param mixed $value
129
     */
130
    public function set(string $key, $value)
131
    {
132 36
        $path = explode('.', $key);
133 36
        $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

133
        $this->load(/** @scrutinizer ignore-type */ $path[0]);
Loading history...
134
135 36
        $this->configurator->set($key, $value);
136
    }
137
138
    /**
139
     * Reinitialise une configuration en fonction des donnees initiales issues des fichiers de configurations
140
     */
141
    public function reset(array|string|null $keys = null): void
142
    {
143 18
        $keys = null !== $keys ? (array) $keys : array_keys(self::$originals);
144
145
        foreach ($keys as $key) {
146 18
            $this->set($key, Arr::dataGet(self::$originals, $key));
147
148
            if (str_starts_with($key, 'app')) {
149 6
                $this->initializeAutoDetect();
150
            }
151
        }
152
    }
153
154
    /**
155
     * Rend disponible un groupe de configuration qui n'existe pas (pas de fichier de configuration)
156
     * Ceci est notament utilse pour definir des configurations à la volée
157
     */
158
    public function ghost(array|string $key, ?Schema $schema = null): static
159
    {
160 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

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

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