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
![]() |
|||||
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
$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
![]() |
|||||
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
$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
![]() |
|||||
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
$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
![]() |
|||||
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
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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
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
|
|||||
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
|
|||||
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
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
![]() |
|||||
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 |