Passed
Push — main ( d71b68...a743f7 )
by Dimitri
08:12 queued 04:09
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
     * Drapeau permettant de savoir si la config a deja ete initialiser
48
     */
49
    private static bool $initialized = false;
50
51
    private readonly Configurator $configurator;
52
53
    public function __construct()
54
    {
55
        $this->configurator = new Configurator();
0 ignored issues
show
Bug introduced by
The property configurator is declared read-only in BlitzPHP\Config\Config.
Loading history...
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 16
            $config = explode('.', $key);
66 16
            $this->load($config[0]);
67
68 16
            return $this->configurator->exists(implode('.', $config));
69
        }
70
71 179
        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 185
            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 24
        $path = explode('.', $key);
118 24
        $this->load($path[0]);
119
120 24
        $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 6
        $keys = null !== $keys ? (array) $keys : array_keys(self::$originals);
129
130
        foreach ($keys as $key) {
131 6
            $this->set($key, Arr::dataGet(self::$originals, $key));
132
133
            if (str_starts_with($key, 'app')) {
134 6
                $this->initializeAutoDetect();
135
            }
136
        }
137
    }
138
139
    /**
140
     * Rend disponible un groupe de configuration qui n'existe pas (pas de fichier de configuration)
141
     * Ceci est notament utilse pour definir des configurations à la volée
142
     */
143
    public function ghost(array|string $key, ?Schema $schema = null): static
144
    {
145 4
        $this->load($key, null, $schema, true);
146
147 4
        return $this;
148
    }
149
150
    /**
151
     * Charger la configuration spécifique dans le scoope
152
     *
153
     * @param string|string[] $config
154
     */
155
    public function load($config, ?string $file = null, ?Schema $schema = null, bool $allow_empty = false)
156
    {
157
        if (is_array($config)) {
158
            foreach ($config as $key => $value) {
159
                if (is_string($key)) {
160
                    $file = $value;
161
                    $conf = $key;
162
                } else {
163 2
                    $file = null;
164 2
                    $conf = $value;
165
                }
166 2
                $this->load($conf, $file, null, $allow_empty);
167
            }
168
        } elseif (! isset(self::$loaded[$config])) {
169 16
            $file ??= self::path($config);
170 16
            $schema ??= self::schema($config);
171
172 16
            $configurations = [];
173
            if (file_exists($file) && ! in_array($file, get_included_files(), true)) {
174 10
                $configurations = (array) require $file;
175
            }
176
177 16
            $configurations = Arr::merge(self::$registrars[$config] ?? [], $configurations);
178
179
            if (empty($configurations) && ! $allow_empty && ! is_a($schema, Schema::class, true)) {
180 2
                return;
181
            }
182
183 16
            $this->configurator->addSchema($config, $schema ?: Expect::mixed(), false);
184 16
            $this->configurator->merge([$config => $configurations]);
185
186 16
            self::$loaded[$config]    = $file;
187 16
            self::$originals[$config] = $this->configurator->get($config);
188
        }
189
    }
190
191
    /**
192
     * Affiche l'exception dû à la mauvaise definition d'une configuration
193
     *
194
     * @param string $group (app, data, database, etc.)
195
     */
196
    public static function exceptBadConfigValue(string $config_key, array|string $accepts_values, string $group)
197
    {
198
        if (is_array($accepts_values)) {
0 ignored issues
show
introduced by
The condition is_array($accepts_values) is always true.
Loading history...
199
            $accepts_values = '(Accept values: ' . implode('/', $accepts_values) . ')';
200
        }
201
202
        throw new ConfigException("The '{$group}.{$config_key} configuration is not set correctly. {$accepts_values} \n Please edit '{" . self::path($group) . "}' file to correct it");
203
    }
204
205
    /**
206
     * Renvoie le chemin du fichier d'un groupe de configuration donné
207
     */
208
    public static function path(string $path): string
209
    {
210 18
        $path = preg_replace('#\.php$#', '', $path);
211
212
        if (file_exists($file = CONFIG_PATH . $path . '.php')) {
213 12
            return $file;
214
        }
215
216 8
        $paths = Services::locator()->search('Config/' . $path);
217
218
        if (isset($paths[0]) && file_exists($path[0])) {
219 8
            return $paths[0];
220
        }
221
222 8
        return '';
223
    }
224
225
    /**
226
     * Retrouve le schema de configuration d'un groupe
227
     */
228
    public static function schema(string $key): ?Schema
229
    {
230 18
        $file        = 'schemas' . DS . Helpers::ensureExt($key . '.config', 'php');
231 18
        $syst_schema = SYST_PATH . 'Constants' . DS . $file;
232 18
        $app_schema  = CONFIG_PATH . $file;
233
234
        if (file_exists($syst_schema)) {
235 8
            $schema = require $syst_schema;
236
        } elseif (file_exists($app_schema)) {
237
            $schema = require $app_schema;
238
        } else {
239 12
            $paths = Services::locator()->search('Config/schemas/' . $key);
240
241
            if (isset($paths[0]) && file_exists($paths[0])) {
242 12
                $schema = require $paths[0];
243
            }
244
        }
245
246 18
        return $schema ?? null;
247
    }
248
249
    /**
250
     * Initialiser la configuration du système avec les données des fichier de configuration
251
     */
252
    private function initialize()
253
    {
254
        if (self::$initialized) {
255
            return;
256
        }
257
258
        $this->loadRegistrar();
259
        $this->load(['app']);
260
261
        ini_set('log_errors', 1);
262
        ini_set('error_log', LOG_PATH . 'blitz-logs');
263
264
        $this->initializeAutoDetect();
265
266
        self::$initialized = true;
267
    }
268
269
    /**
270
     * Charges les registrars disponible pour l'application.
271
     * Les registrars sont des mecanismes permettant aux packages externe de definir un elements de configuration
272
     */
273
    private function loadRegistrar()
274
    {
275
        $autoloader = new Autoloader(['psr4' => [APP_NAMESPACE => APP_PATH]]);
276
        $locator    = new Locator($autoloader->initialize());
277
278
        $registrarsFiles = $locator->search('Config/Registrar.php');
279
280
        foreach ($registrarsFiles as $file) {
281
            if (false === $classname = $locator->findQualifiedNameFromPath($file)) {
282
                continue;
283
            }
284
285
            $class   = new ReflectionClass($classname);
286
            $methods = $class->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC);
287
288
            foreach ($methods as $method) {
289
                if (! ($method->isPublic() && $method->isStatic())) {
290
                    continue;
291
                }
292
293
                if (! is_array($result = $method->invoke(null))) {
294
                    continue;
295
                }
296
297
                $name                    = $method->getName();
298
                self::$registrars[$name] = Arr::merge(self::$registrars[$name] ?? [], $result);
299
            }
300
        }
301
    }
302
303
    /**
304
     * Initialise l'URL
305
     */
306
    private function initializeURL()
307
    {
308 6
        $config = $this->get('app.base_url', 'auto');
309
310
        if ($config === 'auto' || empty($config)) {
311 6
            $config = rtrim(str_replace('\\', '/', Helpers::findBaseUrl()), '/');
312
        }
313
314 6
        $this->set('app.base_url', $config);
315
    }
316
317
    /**
318
     * Initialise l'environnement d'execution de l'application
319
     */
320
    private function initializeEnvironment()
321
    {
322 6
        $environment = $config = $this->get('app.environment');
323
324
        $config = match ($config) {
325
            'auto'  => is_online() ? 'production' : 'development',
326
            'dev'   => 'development',
327
            'prod'  => 'production',
328
            'test'  => 'testing',
329
            default => $config,
330
        };
331
332
        if ($config !== $environment) {
333 6
            $this->set('app.environment', $config);
334
        }
335
336
        switch ($config) {
337
            case 'development':
338 6
                error_reporting(-1);
339
                ini_set('display_errors', 1);
340
                break;
341
342
            case 'testing':
343
            case 'production':
344 6
                ini_set('display_errors', 0);
345 6
                error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT & ~E_USER_NOTICE & ~E_USER_DEPRECATED);
346 6
                break;
347
348
            default:
349
                self::exceptBadConfigValue('environment', ['development', 'production', 'testing', 'auto'], 'app');
350
        }
351
352 6
        defined('BLITZ_DEBUG') || define('BLITZ_DEBUG', $config !== 'production');
353
    }
354
355
    /**
356
     * Initialise les paramètres de la bar de debug
357
     */
358
    private function initializeDebugbar()
359
    {
360 6
        $config = $this->get('app.show_debugbar', 'auto');
361
362
        if (! in_array($config, ['auto', true, false], true)) {
363 6
            self::exceptBadConfigValue('show_debugbar', ['auto', true, false], 'app');
364
        }
365
366
        if ($config === 'auto') {
367 4
            $this->set('app.show_debugbar', ! is_online());
368
        }
369
    }
370
371
    /**
372
     * Initialise les donnees qui ont une valeur definie a "auto" et donc dependent de certains facteurs
373
     */
374
    private function initializeAutoDetect(): void
375
    {
376 6
        $this->initializeURL();
377 6
        $this->initializeEnvironment();
378 6
        $this->initializeDebugbar();
379
    }
380
}
381