nur-sery/nur_src/config/EnvConfig.php

135 lines
5.1 KiB
PHP

<?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);
}
}