<?php namespace nur\config; use Exception; use nur\A; use nur\str; use Throwable; /** * Class EnvConfig: configuration extraite depuis les variables d'environnement * * les variables doivent être de la forme {PREFIX}_{PROFILE}_{PKEY} où: * - PREFIX vaut CONFIG, FILE_CONFIG, JSON_CONFIG ou JSON_FILE_CONFIG * - PROFILE est le profil dans lequel la configuration est valide. Le nom du * profil par défaut est dépendant de l'implémentation de IConfigManager * utilisée. Dans l'implémentation actuelle, 'ALL' est le profil par défaut. * - PKEY est le chemin de clé dans lequel les caractères '.' sont remplacés * par '__' et '-' par '_' (celà signifie qu'il n'est pas possible de définir * un chemin de clé qui contient le caractère '_') * * par exemple, la valeur dbs.my-auth.type du profil par défaut est pris dans * la variable 'CONFIG_ALL_dbs__my_auth__type'. pour le profil prod c'est la * variable 'CONFIG_prod_dbs__my_auth__type' * * pour représenter le tableau suivant: * [ "type" => "mysql", "name" => "mysql:host=authdb;dbname=auth;charset=utf8", * "user" => "auth_int", "pass" => "auth" ] * situé au chemin de clé dbs.auth dans le profil prod, on peut par exemple * définir les variables suivantes: * CONFIG_prod_dbs__auth__type="mysql" * CONFIG_prod_dbs__auth__name="mysql:host=authdb;dbname=auth;charset=utf8" * CONFIG_prod_dbs__auth__user="auth_int" * CONFIG_prod_dbs__auth__pass="auth" * ou alternativement: * JSON_CONFIG_prod_dbs__auth='{"type":"mysql","name":"mysql:host=authdb;dbname=auth;charset=utf8","user":"auth_int","pass":"auth"}' * * Les préfixes supportés sont, dans l'ordre de précédence: * - JSON_FILE_CONFIG -- une valeur au format JSON inscrite dans un fichier * - JSON_CONFIG -- une valeur au format JSON * - FILE_CONFIG -- une valeur inscrite dans un fichier * - CONFIG -- une valeur scalaire */ class EnvConfig extends DynConfig { /** @var array */ protected $config; /** analyser $name et retourner [$pkey, $profile] */ private static function parse_pkey_profile($name): array { $i = strpos($name, "_"); if ($i === false) return [false, false]; $profile = substr($name, 0, $i); $name = substr($name, $i + 1); $pkey = str_replace("__", ".", $name); $pkey = str_replace("_", "-", $pkey); return [$pkey, $profile]; } private static function invalid_json_data(string $pkey, string $profile, ?string $prefix, ?Throwable $e): ConfigException { if ($prefix !== null) $prefix = "$prefix: "; return new ConfigException("${prefix}invalid json data for $profile:$pkey", 0, $e); } /** charger la configuration depuis l'environnement */ protected function ensureConfigLoaded() { if ($this->config !== null) return; $config = []; $json_files = []; $jsons = []; $files = []; $vars = []; foreach (getenv() as $name => $value) { if (str::starts_with("JSON_FILE_CONFIG_", $name)) { $json_files[str::without_prefix("JSON_FILE_CONFIG_", $name)] = $value; } elseif (str::starts_with("JSON_CONFIG_", $name)) { $jsons[str::without_prefix("JSON_CONFIG_", $name)] = $value; } elseif (str::starts_with("FILE_CONFIG_", $name)) { $files[str::without_prefix("FILE_CONFIG_", $name)] = $value; } elseif (str::starts_with("CONFIG_", $name)) { $vars[str::without_prefix("CONFIG_", $name)] = $value; } } foreach ($json_files as $name => $file) { [$pkey, $profile] = self::parse_pkey_profile($name); $data = file_get_contents($file); if ($data === false) { throw new ConfigException("$file: unable to read json file for $profile:$pkey"); } try { $value = json_decode($data, true, 512, JSON_THROW_ON_ERROR); } catch (Exception $e) { throw self::invalid_json_data($pkey, $profile, $file, $e); } A::pset($config, "$profile.$pkey", $value); } foreach ($jsons as $name => $data) { [$pkey, $profile] = self::parse_pkey_profile($name); try { $value = json_decode($data, true, 512, JSON_THROW_ON_ERROR); } catch (Exception $e) { throw self::invalid_json_data($pkey, $profile, null, $e); } A::pset($config, "$profile.$pkey", $value); } foreach ($files as $name => $file) { [$pkey, $profile] = self::parse_pkey_profile($name); $value = file_get_contents($file); if ($value === false) { throw new ConfigException("$file: unable to read file for $profile:$pkey"); } A::pset($config, "$profile.$pkey", $value); } foreach ($vars as $name => $value) { [$pkey, $profile] = self::parse_pkey_profile($name); A::pset($config, "$profile.$pkey", $value); } $this->config = $config; } function has(string $pkey, string $profile): bool { $this->ensureConfigLoaded(); return A::phas($this->config, "$profile.$pkey"); } function get(string $pkey, string $profile) { $this->ensureConfigLoaded(); return A::pget($this->config, "$profile.$pkey"); } function set(string $pkey, $value, string $profile): void { $this->ensureConfigLoaded(); A::pset($this->config, "$profile.$pkey", $value); } }