gestion des configurations
This commit is contained in:
parent
373910b170
commit
85f18bd292
@ -110,11 +110,38 @@ class app {
|
||||
static function get_profile(?bool &$productionMode=null): string {
|
||||
return self::get()->getProfile($productionMode);
|
||||
}
|
||||
|
||||
static function is_prod(): bool {
|
||||
return self::get_profile() === "prod";
|
||||
}
|
||||
|
||||
static function is_devel(): bool {
|
||||
return self::get_profile() === "devel";
|
||||
}
|
||||
|
||||
static function set_profile(?string $profile=null, ?bool $productionMode=null): void {
|
||||
self::get()->setProfile($profile, $productionMode);
|
||||
}
|
||||
|
||||
const FACT_WEB_APP = "web-app";
|
||||
const FACT_CLI_APP = "cli-app";
|
||||
|
||||
static final function is_fact(string $fact): bool {
|
||||
return self::get()->isFact($fact);
|
||||
}
|
||||
|
||||
static final function set_fact(string $fact, $value=true): void {
|
||||
self::get()->setFact($fact, $value);
|
||||
}
|
||||
|
||||
static function is_debug(): bool {
|
||||
return self::get()->isDebug();
|
||||
}
|
||||
|
||||
static function set_debug(?bool $debug=true): void {
|
||||
self::get()->setDebug($debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array répertoires vendor exprimés relativement à PROJDIR
|
||||
*/
|
||||
@ -295,6 +322,26 @@ class app {
|
||||
$this->profileManager->setProfile($profile, $productionMode);
|
||||
}
|
||||
|
||||
protected ?array $facts;
|
||||
|
||||
function isFact(string $fact): bool {
|
||||
return $this->facts[$fact] ?? false;
|
||||
}
|
||||
|
||||
function setFact(string $fact, bool $value=true): void {
|
||||
$this->facts[$fact] = $value;
|
||||
}
|
||||
|
||||
protected bool $debug = false;
|
||||
|
||||
function isDebug(): bool {
|
||||
return $this->debug;
|
||||
}
|
||||
|
||||
function setDebug(bool $debug=true): void {
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string|false $profile
|
||||
*
|
||||
|
@ -2,7 +2,6 @@
|
||||
namespace nulib\app;
|
||||
|
||||
use nulib\app\config\ConfigManager;
|
||||
use nulib\app\config\ProfileManager;
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
@ -12,12 +11,27 @@ class config {
|
||||
protected static ConfigManager $config;
|
||||
|
||||
static function init_configurator($configurators): void {
|
||||
self::$config->addConfigurators(cl::with($configurators));
|
||||
self::$config->addConfigurator($configurators);
|
||||
}
|
||||
|
||||
# certains types de configurations sont normalisés
|
||||
/** ne configurer que le minimum pour que l'application puisse s'initialiser */
|
||||
const CONFIGURE_INITIAL_ONLY = ["include" => "initial"];
|
||||
/** ne configurer que les routes */
|
||||
const CONFIGURE_ROUTES_ONLY = ["include" => "routes"];
|
||||
/** configurer uniquement ce qui ne nécessite pas d'avoir une session */
|
||||
const CONFIGURE_NO_SESSION = ["exclude" => "session"];
|
||||
|
||||
static function configure(?array $params=null): void {
|
||||
self::$config->configure($params);
|
||||
}
|
||||
|
||||
static final function add($config, string ...$profiles): void { self::$config->addConfig($config, $profiles); }
|
||||
static final function get(string $pkey, $default=null, ?string $profile=null) { return self::$config->getValue($pkey, $default, $profile); }
|
||||
static final function k(string $pkey, $default=null) { return self::$config->getValue("app.$pkey", $default); }
|
||||
static final function db(string $pkey, $default=null) { return self::$config->getValue("dbs.$pkey", $default); }
|
||||
static final function m(string $pkey, $default=null) { return self::$config->getValue("msgs.$pkey", $default); }
|
||||
static final function l(string $pkey, $default=null) { return self::$config->getValue("mails.$pkey", $default); }
|
||||
}
|
||||
|
||||
new class extends config {
|
||||
|
50
src/app/config/ArrayConfig.php
Normal file
50
src/app/config/ArrayConfig.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace nulib\app\config;
|
||||
|
||||
use nulib\cl;
|
||||
|
||||
class ArrayConfig implements IConfig {
|
||||
protected function APP(): array {
|
||||
return static::APP;
|
||||
} const APP = [];
|
||||
|
||||
protected function DBS(): array {
|
||||
return static::DBS;
|
||||
} const DBS = [];
|
||||
|
||||
protected function MSGS(): array {
|
||||
return static::MSGS;
|
||||
} const MSGS = [];
|
||||
|
||||
protected function MAILS(): array {
|
||||
return static::MAILS;
|
||||
} const MAILS = [];
|
||||
|
||||
function __construct(?array $config) {
|
||||
foreach (self::CONFIG_KEYS as $key) {
|
||||
switch ($key) {
|
||||
case "app": $default = $this->APP(); break;
|
||||
case "dbs": $default = $this->DBS(); break;
|
||||
case "msgs": $default = $this->MSGS(); break;
|
||||
case "mails": $default = $this->MAILS(); break;
|
||||
default: $default = [];
|
||||
}
|
||||
$config[$key] ??= $default;
|
||||
}
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
protected array $config;
|
||||
|
||||
function has(string $pkey, string $profile): bool {
|
||||
return cl::phas($this->config, $pkey);
|
||||
}
|
||||
|
||||
function get(string $pkey, string $profile) {
|
||||
return cl::pget($this->config, $pkey);
|
||||
}
|
||||
|
||||
function set(string $pkey, $value, string $profile): void {
|
||||
cl::pset($this->config, $pkey, $value);
|
||||
}
|
||||
}
|
@ -1,5 +1,150 @@
|
||||
<?php
|
||||
namespace nulib\app\config;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\app\app;
|
||||
use nulib\cl;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
use ReflectionClass;
|
||||
|
||||
class ConfigManager {
|
||||
protected array $configurators = [];
|
||||
|
||||
/** ajouter une classe ou un objet à la liste des configurateurs */
|
||||
function addConfigurator($configurators): void {
|
||||
A::merge($this->configurators, cl::with($configurators));
|
||||
}
|
||||
|
||||
protected array $configured = [];
|
||||
|
||||
/**
|
||||
* configurer les objets et les classes qui ne l'ont pas encore été. la liste
|
||||
* des objets et des classes à configurer est fournie en appelant la méthode
|
||||
* {@link addConfigurator()}
|
||||
*
|
||||
* par défaut, la configuration se fait en appelant toutes les méthodes
|
||||
* publiques des objets et toutes les méthodes statiques des classes qui
|
||||
* commencent par 'configure', e.g 'configureThis()' ou 'configure_db()',
|
||||
* si elles n'ont pas déjà été appelées
|
||||
*
|
||||
* Il est possible de modifier la liste des méthodes appelées avec le tableau
|
||||
* $params, qui doit être conforme au schema de {@link func::CALL_ALL_SCHEMA}
|
||||
*/
|
||||
function configure(?array $params=null): void {
|
||||
$params["prefix"] ??= "configure";
|
||||
foreach ($this->configurators as $key => $configurator) {
|
||||
$configured =& $this->configured[$key];
|
||||
/** @var func[] $methods */
|
||||
$methods = func::get_all($configurator, $params);
|
||||
foreach ($methods as $method) {
|
||||
$name = $method->getName() ?? "(no name)";
|
||||
$done = $configured[$name] ?? false;
|
||||
if (!$done) {
|
||||
$method->invoke();
|
||||
$configured[$name] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
protected $cache = [];
|
||||
|
||||
protected function resetCache(): void {
|
||||
$this->cache = [];
|
||||
}
|
||||
|
||||
protected function cacheHas(string $pkey, string $profile) {
|
||||
return array_key_exists("$profile.$pkey", $this->cache);
|
||||
}
|
||||
|
||||
protected function cacheGet(string $pkey, string $profile) {
|
||||
return cl::get($this->cache, "$profile.$pkey");
|
||||
}
|
||||
|
||||
protected function cacheSet(string $pkey, $value, string $profile): void {
|
||||
$this->cache["$profile.$pkey"] = $value;
|
||||
}
|
||||
|
||||
protected array $profileConfigs = [];
|
||||
|
||||
/**
|
||||
* Ajouter une configuration valide pour le(s) profil(s) spécifié(s)
|
||||
*
|
||||
* $config est un objet ou une classe qui définit une ou plusieurs des
|
||||
* constantes APP, DBS, MSGS, MAILS
|
||||
*
|
||||
* si $inProfiles===null, la configuration est valide dans tous les profils
|
||||
*/
|
||||
function addConfig($config, ?array $inProfiles=null): void {
|
||||
if (is_string($config)) {
|
||||
$c = new ReflectionClass($config);
|
||||
if ($c->implementsInterface(IConfig::class)) {
|
||||
$config = $c->newInstance();
|
||||
} else {
|
||||
$config = [];
|
||||
foreach (IConfig::CONFIG_KEYS as $key) {
|
||||
$config[$key] = cl::with($c->getConstant(strtoupper($key)));
|
||||
}
|
||||
$config = new ArrayConfig($config);
|
||||
}
|
||||
} elseif (is_array($config)) {
|
||||
$config = new ArrayConfig($config);
|
||||
} elseif (!($config instanceof IConfig)) {
|
||||
throw ValueException::invalid_type($config, "array|IConfig");
|
||||
}
|
||||
|
||||
$inProfiles ??= [IConfig::PROFILE_ALL];
|
||||
foreach ($inProfiles as $profile) {
|
||||
$this->profileConfigs[$profile][] = $config;
|
||||
}
|
||||
|
||||
$this->resetCache();
|
||||
}
|
||||
|
||||
function _getValue(string $pkey, $default, string $inProfile) {
|
||||
$profiles = [$inProfile];
|
||||
if ($inProfile !== IConfig::PROFILE_ALL) $profiles[] = IConfig::PROFILE_ALL;
|
||||
$value = $default;
|
||||
foreach ($profiles as $profile) {
|
||||
/** @var IConfig[] $configs */
|
||||
$configs = $this->profileConfigs[$profile] ?? [];
|
||||
foreach (array_reverse($configs) as $config) {
|
||||
if ($config->has($pkey, $profile)) {
|
||||
$value = $config->get($pkey, $profile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* obtenir la valeur au chemin de clé $pkey dans le profil spécifié
|
||||
*
|
||||
* le $inProfile===null, prendre le profil par défaut.
|
||||
*/
|
||||
function getValue(string $pkey, $default=null, ?string $inProfile=null) {
|
||||
$inProfile ??= app::get_profile();
|
||||
|
||||
if ($this->cacheHas($pkey, $inProfile)) {
|
||||
return $this->cacheGet($pkey, $inProfile);
|
||||
}
|
||||
|
||||
$value = $this->_getValue($pkey, $default, $inProfile);
|
||||
$this->cacheSet($pkey, $default, $inProfile);
|
||||
return $value;
|
||||
}
|
||||
|
||||
function setValue(string $pkey, $value, ?string $inProfile=null): void {
|
||||
$inProfile ??= app::get_profile();
|
||||
/** @var IConfig[] $configs */
|
||||
$configs =& $this->profileConfigs[$inProfile];
|
||||
if ($configs === null) $key = 0;
|
||||
else $key = array_key_last($configs);
|
||||
$configs[$key] ??= new ArrayConfig([]);
|
||||
$configs[$key]->set($pkey, $value, $inProfile);
|
||||
}
|
||||
}
|
||||
|
112
src/app/config/EnvConfig.php
Normal file
112
src/app/config/EnvConfig.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
namespace nulib\app\config;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ext\json;
|
||||
use nulib\file;
|
||||
use nulib\str;
|
||||
|
||||
/**
|
||||
* 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, ou ALL si
|
||||
* la valeur doit être valide dans tous les profils
|
||||
* - PKEY est le chemin de clé dans lequel les caractères '.' sont remplacés
|
||||
* par '__'
|
||||
*
|
||||
* 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 implements IConfig{
|
||||
function __construct() {
|
||||
$this->loadEnvConfig();
|
||||
}
|
||||
|
||||
protected array $profileConfigs;
|
||||
|
||||
/** 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);
|
||||
if ($profile === "ALL") $profile = IConfig::PROFILE_ALL;
|
||||
$name = substr($name, $i + 1);
|
||||
$pkey = str_replace("__", ".", $name);
|
||||
return [$pkey, $profile];
|
||||
}
|
||||
|
||||
function loadEnvConfig(): void {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
$profileConfigs = [];
|
||||
foreach ($json_files as $name => $file) {
|
||||
[$pkey, $profile] = self::parse_pkey_profile($name);
|
||||
$value = json::load($file);
|
||||
cl::pset($profileConfigs, "$profile.$pkey", $value);
|
||||
}
|
||||
foreach ($jsons as $name => $json) {
|
||||
[$pkey, $profile] = self::parse_pkey_profile($name);
|
||||
$value = json::decode($json);
|
||||
cl::pset($profileConfigs, "$profile.$pkey", $value);
|
||||
}
|
||||
foreach ($files as $name => $file) {
|
||||
[$pkey, $profile] = self::parse_pkey_profile($name);
|
||||
$value = file::reader($file)->getContents();
|
||||
cl::pset($profileConfigs, "$profile.$pkey", $value);
|
||||
}
|
||||
foreach ($vars as $name => $value) {
|
||||
[$pkey, $profile] = self::parse_pkey_profile($name);
|
||||
cl::pset($profileConfigs, "$profile.$pkey", $value);
|
||||
}
|
||||
$this->profileConfigs = $profileConfigs;
|
||||
}
|
||||
|
||||
function has(string $pkey, string $profile): bool {
|
||||
$config = $this->profileConfigs[$profile] ?? null;
|
||||
return cl::phas($config, $pkey);
|
||||
}
|
||||
|
||||
function get(string $pkey, string $profile) {
|
||||
$config = $this->profileConfigs[$profile] ?? null;
|
||||
return cl::pget($config, $pkey);
|
||||
}
|
||||
|
||||
function set(string $pkey, $value, string $profile): void {
|
||||
$config =& $this->profileConfigs[$profile];
|
||||
cl::pset($config, $pkey, $value);
|
||||
}
|
||||
}
|
24
src/app/config/IConfig.php
Normal file
24
src/app/config/IConfig.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace nulib\app\config;
|
||||
|
||||
/**
|
||||
* Interface IConfig: un objet fournissant une configuration
|
||||
*/
|
||||
interface IConfig {
|
||||
/**
|
||||
* @var string profil indiquant qu'une configuration est valide dans tous les
|
||||
* profils
|
||||
*/
|
||||
const PROFILE_ALL = "-ALL-";
|
||||
|
||||
const CONFIG_KEYS = [
|
||||
"app",
|
||||
"dbs", "msgs", "mails",
|
||||
];
|
||||
|
||||
function has(string $pkey, string $profile): bool;
|
||||
|
||||
function get(string $pkey, string $profile);
|
||||
|
||||
function set(string $pkey, $value, string $profile): void;
|
||||
}
|
13
src/app/config/JsonConfig.php
Normal file
13
src/app/config/JsonConfig.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace nulib\app\config;
|
||||
|
||||
use nulib\ext\json;
|
||||
|
||||
/**
|
||||
* Class JsonConfig: une configuration chargée depuis un fichier JSON
|
||||
*/
|
||||
class JsonConfig extends ArrayConfig {
|
||||
function __construct(string $input) {
|
||||
parent::__construct(json::load($input));
|
||||
}
|
||||
}
|
13
src/app/config/YamlConfig.php
Normal file
13
src/app/config/YamlConfig.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace nulib\app\config;
|
||||
|
||||
use nulib\ext\yaml;
|
||||
|
||||
/**
|
||||
* Class YamlConfig: une configuration chargée depuis un fichier yaml
|
||||
*/
|
||||
class YamlConfig extends ArrayConfig {
|
||||
function __construct(string $input) {
|
||||
parent::__construct(yaml::load($input));
|
||||
}
|
||||
}
|
124
tests/app/config/ConfigManagerTest.php
Normal file
124
tests/app/config/ConfigManagerTest.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
namespace nulib\app\config {
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
use nulib\app\config\impl\result;
|
||||
use nulib\app\config\impl\config1;
|
||||
use nulib\app\config\impl\config2;
|
||||
|
||||
class ConfigManagerTest extends TestCase {
|
||||
function testConfigurators() {
|
||||
$config = new ConfigManager();
|
||||
|
||||
result::reset();
|
||||
$config->addConfigurator(config1::class);
|
||||
$config->configure();
|
||||
self::assertSame([
|
||||
"config1::static configure1",
|
||||
], impl\result::$configured);
|
||||
|
||||
result::reset();
|
||||
$config->addConfigurator(config1::class);
|
||||
$config->configure();
|
||||
$config->configure();
|
||||
$config->configure();
|
||||
self::assertSame([
|
||||
"config1::static configure1",
|
||||
], impl\result::$configured);
|
||||
|
||||
result::reset();
|
||||
$config->addConfigurator(new config1());
|
||||
$config->configure();
|
||||
self::assertSame([
|
||||
"config1::static configure1",
|
||||
"config1::configure2",
|
||||
], impl\result::$configured);
|
||||
|
||||
result::reset();
|
||||
$config->addConfigurator(new config1());
|
||||
$config->configure(["include" => "2"]);
|
||||
self::assertSame([
|
||||
"config1::configure2",
|
||||
], impl\result::$configured);
|
||||
$config->configure(["include" => "1"]);
|
||||
self::assertSame([
|
||||
"config1::configure2",
|
||||
"config1::static configure1",
|
||||
], impl\result::$configured);
|
||||
|
||||
result::reset();
|
||||
$config->addConfigurator([
|
||||
config1::class,
|
||||
new config2(),
|
||||
]);
|
||||
$config->configure();
|
||||
self::assertSame([
|
||||
"config1::static configure1",
|
||||
"config2::static configure1",
|
||||
"config2::configure2",
|
||||
], impl\result::$configured);
|
||||
}
|
||||
|
||||
function testConfig() {
|
||||
$config = new ConfigManager();
|
||||
|
||||
$config->addConfig([
|
||||
"app" => [
|
||||
"var" => "array",
|
||||
]
|
||||
]);
|
||||
self::assertSame("array", $config->getValue("app.var"));
|
||||
|
||||
$config->addConfig(new ArrayConfig([
|
||||
"app" => [
|
||||
"var" => "instance",
|
||||
]
|
||||
]));
|
||||
self::assertSame("instance", $config->getValue("app.var"));
|
||||
|
||||
$config->addConfig(config1::class);
|
||||
self::assertSame("class1", $config->getValue("app.var"));
|
||||
|
||||
$config->addConfig(config2::class);
|
||||
self::assertSame("class2", $config->getValue("app.var"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace nulib\app\config\impl {
|
||||
class result {
|
||||
static array $configured = [];
|
||||
|
||||
static function reset() {
|
||||
self::$configured = [];
|
||||
}
|
||||
}
|
||||
|
||||
class config1 {
|
||||
const APP = [
|
||||
"var" => "class1",
|
||||
];
|
||||
|
||||
static function configure1() {
|
||||
result::$configured[] = "config1::static configure1";
|
||||
}
|
||||
|
||||
function configure2() {
|
||||
result::$configured[] = "config1::configure2";
|
||||
}
|
||||
}
|
||||
|
||||
class config2 {
|
||||
const APP = [
|
||||
"var" => "class2",
|
||||
];
|
||||
|
||||
static function configure1() {
|
||||
result::$configured[] = "config2::static configure1";
|
||||
}
|
||||
|
||||
function configure2() {
|
||||
result::$configured[] = "config2::configure2";
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user