zhamao-framework/src/ZM/Config/RefactoredConfig.php

291 lines
8.2 KiB
PHP
Raw Normal View History

2022-08-19 23:10:43 +08:00
<?php
declare(strict_types=1);
namespace ZM\Config;
use Onebot\V12\Config\Config;
use ZM\Exception\ConfigException;
class RefactoredConfig
{
/**
* @var array 支持的文件扩展名
*/
public const ALLOWED_FILE_EXTENSIONS = ['php', 'yaml', 'yml', 'json', 'toml'];
2022-08-20 15:58:46 +08:00
/**
* @var array 配置文件加载顺序,后覆盖前
*/
public const LOAD_ORDER = ['global', 'environment', 'patch'];
2022-08-19 23:10:43 +08:00
/**
* @var array 已加载的配置文件
*/
2022-08-22 16:29:27 +08:00
private array $loaded_files = [];
2022-08-19 23:10:43 +08:00
/**
* @var array 配置文件路径
*/
2022-08-22 16:29:27 +08:00
private array $config_paths;
2022-08-19 23:10:43 +08:00
/**
* @var string 当前环境
*/
2022-08-22 16:29:27 +08:00
private string $environment;
2022-08-19 23:10:43 +08:00
/**
* @var Config 内部配置容器
*/
2022-08-22 16:29:27 +08:00
private Config $holder;
2022-08-19 23:10:43 +08:00
/**
* 构造配置实例
*
2022-08-20 18:40:54 +08:00
* @param array $config_paths 配置文件路径
* @param string $environment 环境
2022-08-19 23:10:43 +08:00
*
* @throws ConfigException 配置文件加载出错
*/
public function __construct(array $config_paths, string $environment = 'development')
{
$this->config_paths = $config_paths;
$this->environment = $environment;
$this->holder = new Config([]);
$this->loadFiles();
}
/**
2022-08-20 15:58:46 +08:00
* 加载配置文件
*
* @throws ConfigException
2022-08-19 23:10:43 +08:00
*/
2022-08-20 15:58:46 +08:00
public function loadFiles(): void
2022-08-19 23:10:43 +08:00
{
2022-08-20 15:58:46 +08:00
$stages = [
'global' => [],
'environment' => [],
'patch' => [],
];
// 遍历所有需加载的文件,并按加载类型进行分组
foreach ($this->config_paths as $config_path) {
$files = scandir($config_path);
foreach ($files as $file) {
2022-08-20 18:28:22 +08:00
[, $ext, $load_type,] = $this->getFileMeta($file);
2022-08-20 17:43:06 +08:00
// 略过不支持的文件
2022-08-20 18:28:22 +08:00
if (!in_array($ext, self::ALLOWED_FILE_EXTENSIONS, true)) {
2022-08-20 15:58:46 +08:00
continue;
}
2022-08-20 17:43:06 +08:00
2022-08-20 15:58:46 +08:00
$file_path = $config_path . '/' . $file;
if (is_dir($file_path)) {
// TODO: 支持子目录(待定)
continue;
}
2022-08-20 17:43:06 +08:00
// 略过不应加载的文件
if (!$this->shouldLoadFile($file)) {
continue;
}
// 略过加载顺序未知的文件
2022-08-20 15:58:46 +08:00
if (!in_array($load_type, self::LOAD_ORDER, true)) {
continue;
}
2022-08-20 17:43:06 +08:00
// 将文件加入到对应的加载阶段
2022-08-20 15:58:46 +08:00
$stages[$load_type][] = $file_path;
}
}
// 按照加载顺序加载配置文件
foreach (self::LOAD_ORDER as $load_type) {
foreach ($stages[$load_type] as $file_path) {
2022-08-21 16:13:16 +08:00
logger()->info('Loading config file: ' . $file_path);
2022-08-20 17:43:06 +08:00
$this->loadConfigFromPath($file_path);
2022-08-20 15:58:46 +08:00
}
}
}
2022-08-20 18:40:54 +08:00
/**
* 合并传入的配置数组至指定的配置项
*
* @param string $key 目标配置项,必须为数组
* @param array $config 要合并的配置数组
*/
public function merge(string $key, array $config): void
{
$original = $this->get($key, []);
$this->set($key, array_merge($original, $config));
}
/**
* 获取配置项
*
* @param string $key 配置项名称,可使用.访问数组
* @param mixed $default 默认值
*
* @return null|array|mixed
*/
public function get(string $key, $default = null)
{
return $this->holder->get($key, $default);
}
/**
* 设置配置项
* 仅在本次运行期间生效,不会保存到配置文件中哦
*
* @param string $key 配置项名称,可使用.访问数组
* @param mixed $value 要写入的值,传入 null 会进行删除
*/
public function set(string $key, $value): void
{
$this->holder->set($key, $value);
}
/**
* 获取内部配置容器
*/
public function getHolder(): Config
{
return $this->holder;
}
/**
* 重载配置文件
* 运行期间新增的配置文件不会被加载哟~
*
* @throws ConfigException
*/
public function reload(): void
{
$this->holder = new Config([]);
$this->loadFiles();
}
2022-08-20 15:58:46 +08:00
/**
2022-08-20 18:28:22 +08:00
* 获取文件元信息
2022-08-19 23:10:43 +08:00
*
2022-08-20 18:28:22 +08:00
* @param string $name 文件名
2022-08-19 23:10:43 +08:00
*
2022-08-20 18:28:22 +08:00
* @return array 文件元信息,数组元素按次序为:配置组名/扩展名/加载类型/环境类型
2022-08-19 23:10:43 +08:00
*/
2022-08-20 18:28:22 +08:00
private function getFileMeta(string $name): array
2022-08-19 23:10:43 +08:00
{
2022-08-20 18:28:22 +08:00
$basename = pathinfo($name, PATHINFO_BASENAME);
$parts = explode('.', $basename);
$ext = array_pop($parts);
$load_type = $this->getFileLoadType(implode('.', $parts));
if ($load_type === 'global') {
$env = null;
} else {
$env = array_pop($parts);
}
$group = implode('.', $parts);
return [$group, $ext, $load_type, $env];
2022-08-20 15:58:46 +08:00
}
/**
* 获取文件加载类型
*
2022-08-21 16:13:16 +08:00
* @param string $name 文件名,不带扩展名
2022-08-20 15:58:46 +08:00
*
* @return string 可能为global, environment, patch
*/
private function getFileLoadType(string $name): string
{
2022-08-21 16:13:16 +08:00
// TODO: 对于多段名称的处理,如 test.patch.development
2022-08-20 15:58:46 +08:00
// 传入此处的 name 参数有三种可能的格式:
// 1. 纯文件名:如 test此时加载类型为 global
// 2. 文件名.环境:如 test.development此时加载类型为 environment
// 3. 文件名.patch如 test.patch此时加载类型为 patch
// 至于其他的格式,则为未定义行为
if (strpos($name, '.') === false) {
return 'global';
}
$name_and_env = explode('.', $name);
if (count($name_and_env) !== 2) {
return 'undefined';
}
if ($name_and_env[1] === 'patch') {
return 'patch';
2022-08-19 23:10:43 +08:00
}
2022-08-20 15:58:46 +08:00
return 'environment';
2022-08-19 23:10:43 +08:00
}
/**
2022-08-20 15:58:46 +08:00
* 判断是否应该加载配置文件
2022-08-19 23:10:43 +08:00
*
2022-08-20 18:28:22 +08:00
* @param string $path 文件名,包含扩展名
2022-08-19 23:10:43 +08:00
*/
2022-08-20 18:28:22 +08:00
private function shouldLoadFile(string $path): bool
2022-08-19 23:10:43 +08:00
{
2022-08-20 18:28:22 +08:00
$name = pathinfo($path, PATHINFO_FILENAME);
2022-08-20 15:58:46 +08:00
// 传入此处的 name 参数有两种可能的格式:
// 1. 纯文件名:如 test
// 2. 文件名.环境:如 test.development
// 对于第一种格式,在任何情况下均应该加载
// 对于第二种格式,只有当环境与当前环境相同时才加载
// 至于其他的格式,则为未定义行为
if (strpos($name, '.') === false) {
return true;
}
$name_and_env = explode('.', $name);
if (count($name_and_env) !== 2) {
return false;
}
return $name_and_env[1] === $this->environment;
2022-08-19 23:10:43 +08:00
}
/**
* 从传入的路径加载配置文件
*
* @param string $path 配置文件路径
*
* @throws ConfigException 传入的配置文件不支持
*/
2022-08-20 15:58:46 +08:00
private function loadConfigFromPath(string $path): void
2022-08-19 23:10:43 +08:00
{
2022-08-20 15:58:46 +08:00
if (in_array($path, $this->loaded_files, true)) {
2022-08-19 23:10:43 +08:00
return;
}
$this->loaded_files[] = $path;
// 判断文件格式是否支持
2022-08-20 18:28:22 +08:00
[$group, $ext, $load_type, $env] = $this->getFileMeta($path);
2022-08-20 15:58:46 +08:00
if (!in_array($ext, self::ALLOWED_FILE_EXTENSIONS, true)) {
2022-08-22 16:29:27 +08:00
throw ConfigException::unsupportedFileType($path);
2022-08-19 23:10:43 +08:00
}
// 读取并解析配置
$content = file_get_contents($path);
$config = [];
switch ($ext) {
case 'php':
$config = require $path;
break;
case 'json':
2022-08-22 16:29:27 +08:00
try {
$config = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw ConfigException::loadConfigFailed($path, $e->getMessage());
}
2022-08-19 23:10:43 +08:00
break;
case 'yaml':
case 'yml':
// TODO: 实现yaml解析
break;
case 'toml':
// TODO: 实现toml解析
break;
default:
2022-08-22 16:29:27 +08:00
throw ConfigException::unsupportedFileType($path);
2022-08-19 23:10:43 +08:00
}
// 加入配置
2022-08-20 18:28:22 +08:00
$this->merge($group, $config);
}
2022-08-19 23:10:43 +08:00
}