135 lines
5.1 KiB
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 final 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 final 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);
|
||
|
}
|
||
|
}
|