From 4896cf0ad3a882fc6e1154bbcce1a8f180eb8ecc Mon Sep 17 00:00:00 2001 From: sunxyw Date: Fri, 19 Aug 2022 23:10:43 +0800 Subject: [PATCH 01/18] simply refactor ZMConfig --- src/ZM/Config/RefactoredConfig.php | 207 +++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 src/ZM/Config/RefactoredConfig.php diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php new file mode 100644 index 00000000..719842ab --- /dev/null +++ b/src/ZM/Config/RefactoredConfig.php @@ -0,0 +1,207 @@ +config_paths = $config_paths; + $this->environment = $environment; + $this->holder = new Config([]); + $this->loadFiles(); + } + + /** + * 获取内部配置容器 + */ + public function getHolder(): Config + { + return $this->holder; + } + + /** + * 获取配置项 + * + * @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) + { + $this->holder->set($key, $value); + } + + /** + * 合并传入的配置数组至指定的配置项 + * + * @param string $key 目标配置项,必须为数组 + * @param array $config 要合并的配置数组 + */ + public function merge(string $key, array $config) + { + $original = $this->get($key, []); + $this->set($key, array_merge($original, $config)); + } + + /** + * 加载配置文件 + * + * @throws ConfigException + */ + public function loadFiles() + { + foreach ($this->config_paths as $config_path) { + $files = scandir($config_path); + foreach ($files as $file) { + if (!in_array(pathinfo($file, PATHINFO_EXTENSION), self::ALLOWED_FILE_EXTENSIONS)) { + continue; + } + $file_path = $config_path . '/' . $file; + if (is_dir($file_path)) { + // TODO: 支持子目录(待定) + continue; + } + $this->loadConfigFromPath($file_path); + } + } + } + + /** + * 重载配置文件 + * 运行期间新增的配置文件不会被加载哟~ + * + * @throws ConfigException + */ + public function reload() + { + $this->holder = new Config([]); + $this->loadFiles(); + } + + /** + * 从传入的路径加载配置文件 + * + * @param string $path 配置文件路径 + * + * @throws ConfigException 传入的配置文件不支持 + */ + private function loadConfigFromPath(string $path) + { + if (in_array($path, $this->loaded_files)) { + return; + } + $this->loaded_files[] = $path; + + // 判断文件格式是否支持 + $info = pathinfo($path); + $name = $info['filename']; + $ext = $info['extension']; + if (!in_array($ext, self::ALLOWED_FILE_EXTENSIONS)) { + throw new ConfigException('E00079', "不支持的配置文件格式:{$ext}"); + } + + // 判断是否应该加载 + if (!$this->shouldLoadFile($name)) { + return; + } + + // 读取并解析配置 + $content = file_get_contents($path); + $config = []; + switch ($ext) { + case 'php': + $config = require $path; + break; + case 'json': + $config = json_decode($content, true); + break; + case 'yaml': + case 'yml': + // TODO: 实现yaml解析 + break; + case 'toml': + // TODO: 实现toml解析 + break; + default: + throw new ConfigException('E00079', "不支持的配置文件格式:{$ext}"); + } + + // 加入配置 + $this->merge($name, $config); + } + + /** + * 判断是否应该加载配置文件 + * + * @param string $name 文件名 + */ + private function shouldLoadFile(string $name): bool + { + // 传入此处的 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; + } +} From aa8fdf557e47efbfefd7abeb8115d7da6593ca8c Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sat, 20 Aug 2022 15:58:46 +0800 Subject: [PATCH 02/18] add load order to config --- src/ZM/Config/RefactoredConfig.php | 182 ++++++++++++++++++----------- 1 file changed, 116 insertions(+), 66 deletions(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index 719842ab..42859390 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -14,6 +14,11 @@ class RefactoredConfig */ public const ALLOWED_FILE_EXTENSIONS = ['php', 'yaml', 'yml', 'json', 'toml']; + /** + * @var array 配置文件加载顺序,后覆盖前 + */ + public const LOAD_ORDER = ['global', 'environment', 'patch']; + /** * @var array 已加载的配置文件 */ @@ -51,11 +56,61 @@ class RefactoredConfig } /** - * 获取内部配置容器 + * 加载配置文件 + * + * @throws ConfigException */ - public function getHolder(): Config + public function loadFiles(): void { - return $this->holder; + $stages = [ + 'global' => [], + 'environment' => [], + 'patch' => [], + ]; + + // 遍历所有需加载的文件,并按加载类型进行分组 + foreach ($this->config_paths as $config_path) { + $files = scandir($config_path); + foreach ($files as $file) { + if (!in_array(pathinfo($file, PATHINFO_EXTENSION), self::ALLOWED_FILE_EXTENSIONS, true)) { + continue; + } + $file_path = $config_path . '/' . $file; + if (is_dir($file_path)) { + // TODO: 支持子目录(待定) + continue; + } + + $load_type = $this->getFileLoadType($file); + if (!in_array($load_type, self::LOAD_ORDER, true)) { + continue; + } + + $stages[$load_type][] = $file_path; + } + } + + // 按照加载顺序加载配置文件 + foreach (self::LOAD_ORDER as $load_type) { + foreach ($stages[$load_type] as $file_path) { + $file = pathinfo($file_path, PATHINFO_FILENAME); + if ($this->shouldLoadFile($file)) { + $this->loadConfigFromPath($file); + } + } + } + } + + /** + * 合并传入的配置数组至指定的配置项 + * + * @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)); } /** @@ -78,44 +133,17 @@ class RefactoredConfig * @param string $key 配置项名称,可使用.访问数组 * @param mixed $value 要写入的值,传入 null 会进行删除 */ - public function set(string $key, $value) + public function set(string $key, $value): void { $this->holder->set($key, $value); } /** - * 合并传入的配置数组至指定的配置项 - * - * @param string $key 目标配置项,必须为数组 - * @param array $config 要合并的配置数组 + * 获取内部配置容器 */ - public function merge(string $key, array $config) + public function getHolder(): Config { - $original = $this->get($key, []); - $this->set($key, array_merge($original, $config)); - } - - /** - * 加载配置文件 - * - * @throws ConfigException - */ - public function loadFiles() - { - foreach ($this->config_paths as $config_path) { - $files = scandir($config_path); - foreach ($files as $file) { - if (!in_array(pathinfo($file, PATHINFO_EXTENSION), self::ALLOWED_FILE_EXTENSIONS)) { - continue; - } - $file_path = $config_path . '/' . $file; - if (is_dir($file_path)) { - // TODO: 支持子目录(待定) - continue; - } - $this->loadConfigFromPath($file_path); - } - } + return $this->holder; } /** @@ -124,12 +152,62 @@ class RefactoredConfig * * @throws ConfigException */ - public function reload() + public function reload(): void { $this->holder = new Config([]); $this->loadFiles(); } + /** + * 获取文件加载类型 + * + * @param string $name 文件名 + * + * @return string 可能为:global, environment, patch + */ + private function getFileLoadType(string $name): string + { + // 传入此处的 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'; + } + return 'environment'; + } + + /** + * 判断是否应该加载配置文件 + * + * @param string $name 文件名 + */ + private function shouldLoadFile(string $name): bool + { + // 传入此处的 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; + } + /** * 从传入的路径加载配置文件 * @@ -137,9 +215,9 @@ class RefactoredConfig * * @throws ConfigException 传入的配置文件不支持 */ - private function loadConfigFromPath(string $path) + private function loadConfigFromPath(string $path): void { - if (in_array($path, $this->loaded_files)) { + if (in_array($path, $this->loaded_files, true)) { return; } $this->loaded_files[] = $path; @@ -148,15 +226,10 @@ class RefactoredConfig $info = pathinfo($path); $name = $info['filename']; $ext = $info['extension']; - if (!in_array($ext, self::ALLOWED_FILE_EXTENSIONS)) { + if (!in_array($ext, self::ALLOWED_FILE_EXTENSIONS, true)) { throw new ConfigException('E00079', "不支持的配置文件格式:{$ext}"); } - // 判断是否应该加载 - if (!$this->shouldLoadFile($name)) { - return; - } - // 读取并解析配置 $content = file_get_contents($path); $config = []; @@ -181,27 +254,4 @@ class RefactoredConfig // 加入配置 $this->merge($name, $config); } - - /** - * 判断是否应该加载配置文件 - * - * @param string $name 文件名 - */ - private function shouldLoadFile(string $name): bool - { - // 传入此处的 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; - } } From f819922d8abd012a2972e543c3d4ec84e40a0427 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sat, 20 Aug 2022 17:43:06 +0800 Subject: [PATCH 03/18] add basic usable config --- src/ZM/Config/RefactoredConfig.php | 16 +++- tests/ZM/Config/RefactoredConfigTest.php | 101 +++++++++++++++++++++++ 2 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 tests/ZM/Config/RefactoredConfigTest.php diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index 42859390..6268e7af 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -72,20 +72,31 @@ class RefactoredConfig foreach ($this->config_paths as $config_path) { $files = scandir($config_path); foreach ($files as $file) { + // 略过不支持的文件 if (!in_array(pathinfo($file, PATHINFO_EXTENSION), self::ALLOWED_FILE_EXTENSIONS, true)) { continue; } + $file_path = $config_path . '/' . $file; if (is_dir($file_path)) { // TODO: 支持子目录(待定) continue; } + $file = pathinfo($file, PATHINFO_FILENAME); + + // 略过不应加载的文件 + if (!$this->shouldLoadFile($file)) { + continue; + } + + // 略过加载顺序未知的文件 $load_type = $this->getFileLoadType($file); if (!in_array($load_type, self::LOAD_ORDER, true)) { continue; } + // 将文件加入到对应的加载阶段 $stages[$load_type][] = $file_path; } } @@ -93,10 +104,7 @@ class RefactoredConfig // 按照加载顺序加载配置文件 foreach (self::LOAD_ORDER as $load_type) { foreach ($stages[$load_type] as $file_path) { - $file = pathinfo($file_path, PATHINFO_FILENAME); - if ($this->shouldLoadFile($file)) { - $this->loadConfigFromPath($file); - } + $this->loadConfigFromPath($file_path); } } } diff --git a/tests/ZM/Config/RefactoredConfigTest.php b/tests/ZM/Config/RefactoredConfigTest.php new file mode 100644 index 00000000..ae1bffd7 --- /dev/null +++ b/tests/ZM/Config/RefactoredConfigTest.php @@ -0,0 +1,101 @@ + 'bar', + 'bar' => 'baz', + 'baz' => 'bat', + 'null' => null, + 'boolean' => true, + 'associate' => [ + 'x' => 'xxx', + 'y' => 'yyy', + ], + 'array' => [ + 'aaa', + 'zzz', + ], + 'x' => [ + 'z' => 'zoo', + ], + 'a.b' => 'c', + 'a' => [ + 'b.c' => 'd', + ], + 'from' => 'global', + ]; + + // 下方测试需要临时写入的文件 + file_put_contents($mock_dir . '/test.php', ' "environment", "env" => "dev"];'); + file_put_contents($mock_dir . '/test.production.php', + ' "environment", "env" => "prod"];'); + file_put_contents($mock_dir . '/test.invalid.php', ' "invalid"];'); + + $config = new RefactoredConfig([ + __DIR__ . '/config_mock', + ], 'development'); + self::$config = $config; + } + + public static function tearDownAfterClass(): void + { + foreach (scandir(__DIR__ . '/config_mock') as $file) { + if ($file !== '.' && $file !== '..') { + unlink(__DIR__ . '/config_mock/' . $file); + } + } + rmdir(__DIR__ . '/config_mock'); + } + + public function testGetValueWhenKeyContainsDot(): void + { + $this->markTestSkipped('should it be supported?'); + $this->assertEquals('c', self::$config->get('test.a.b')); + $this->assertEquals('d', self::$config->get('test.a.b.c')); + } + + public function testGetBooleanValue(): void + { + $this->assertTrue(self::$config->get('test.boolean')); + } + + public function testGetValue(): void + { + $this->assertSame('bar', self::$config->get('test.foo')); + } + + public function testGetWithDefault(): void + { + $this->assertSame('default', self::$config->get('not_exist', 'default')); + } + + public function testSetValue(): void + { + self::$config->set('key', 'value'); + $this->assertSame('value', self::$config->get('key')); + } + + public function testSetArrayValue(): void + { + self::$config->set('array', ['a', 'b']); + $this->assertSame(['a', 'b'], self::$config->get('array')); + $this->assertSame('a', self::$config->get('array.0')); + } +} From 90282b858e9d89863863c2fe0c666a3d464cad65 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sat, 20 Aug 2022 18:28:22 +0800 Subject: [PATCH 04/18] fix inappropriate config group name --- src/ZM/Config/RefactoredConfig.php | 143 +++++++++++++---------- tests/ZM/Config/RefactoredConfigTest.php | 10 +- 2 files changed, 89 insertions(+), 64 deletions(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index 6268e7af..d830b012 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -42,8 +42,8 @@ class RefactoredConfig /** * 构造配置实例 * - * @param array $config_paths 配置文件路径 - * @param string $environment 环境 + * @param array $config_paths 配置文件路径 + * @param string $environment 环境 * * @throws ConfigException 配置文件加载出错 */ @@ -72,8 +72,9 @@ class RefactoredConfig foreach ($this->config_paths as $config_path) { $files = scandir($config_path); foreach ($files as $file) { + [, $ext, $load_type,] = $this->getFileMeta($file); // 略过不支持的文件 - if (!in_array(pathinfo($file, PATHINFO_EXTENSION), self::ALLOWED_FILE_EXTENSIONS, true)) { + if (!in_array($ext, self::ALLOWED_FILE_EXTENSIONS, true)) { continue; } @@ -83,15 +84,12 @@ class RefactoredConfig continue; } - $file = pathinfo($file, PATHINFO_FILENAME); - // 略过不应加载的文件 if (!$this->shouldLoadFile($file)) { continue; } // 略过加载顺序未知的文件 - $load_type = $this->getFileLoadType($file); if (!in_array($load_type, self::LOAD_ORDER, true)) { continue; } @@ -110,60 +108,25 @@ class RefactoredConfig } /** - * 合并传入的配置数组至指定的配置项 + * 获取文件元信息 * - * @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 $name 文件名 * - * @param string $key 配置项名称,可使用.访问数组 - * @param mixed $default 默认值 - * - * @return null|array|mixed + * @return array 文件元信息,数组元素按次序为:配置组名/扩展名/加载类型/环境类型 */ - public function get(string $key, $default = null) + private function getFileMeta(string $name): array { - 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(); + $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]; } /** @@ -196,10 +159,11 @@ class RefactoredConfig /** * 判断是否应该加载配置文件 * - * @param string $name 文件名 + * @param string $path 文件名,包含扩展名 */ - private function shouldLoadFile(string $name): bool + private function shouldLoadFile(string $path): bool { + $name = pathinfo($path, PATHINFO_FILENAME); // 传入此处的 name 参数有两种可能的格式: // 1. 纯文件名:如 test // 2. 文件名.环境:如 test.development @@ -231,9 +195,7 @@ class RefactoredConfig $this->loaded_files[] = $path; // 判断文件格式是否支持 - $info = pathinfo($path); - $name = $info['filename']; - $ext = $info['extension']; + [$group, $ext, $load_type, $env] = $this->getFileMeta($path); if (!in_array($ext, self::ALLOWED_FILE_EXTENSIONS, true)) { throw new ConfigException('E00079', "不支持的配置文件格式:{$ext}"); } @@ -260,6 +222,63 @@ class RefactoredConfig } // 加入配置 - $this->merge($name, $config); + $this->merge($group, $config); + } + + /** + * 合并传入的配置数组至指定的配置项 + * + * @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(); } } diff --git a/tests/ZM/Config/RefactoredConfigTest.php b/tests/ZM/Config/RefactoredConfigTest.php index ae1bffd7..6f395a90 100644 --- a/tests/ZM/Config/RefactoredConfigTest.php +++ b/tests/ZM/Config/RefactoredConfigTest.php @@ -43,9 +43,9 @@ class RefactoredConfigTest extends TestCase // 下方测试需要临时写入的文件 file_put_contents($mock_dir . '/test.php', ' "environment", "env" => "dev"];'); + ' "environment", "env" => "development"];'); file_put_contents($mock_dir . '/test.production.php', - ' "environment", "env" => "prod"];'); + ' "environment", "env" => "production"];'); file_put_contents($mock_dir . '/test.invalid.php', ' "invalid"];'); $config = new RefactoredConfig([ @@ -98,4 +98,10 @@ class RefactoredConfigTest extends TestCase $this->assertSame(['a', 'b'], self::$config->get('array')); $this->assertSame('a', self::$config->get('array.0')); } + + public function testGetEnvironmentSpecifiedValue(): void + { + $this->assertSame('environment', self::$config->get('test.from')); + $this->assertSame('development', self::$config->get('test.env')); + } } From 1a1c65afce19c8a6d6e044c7d8fb226d82d8b5eb Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sat, 20 Aug 2022 18:40:54 +0800 Subject: [PATCH 05/18] fix code styles --- src/ZM/Config/RefactoredConfig.php | 118 +++++++++++------------ tests/ZM/Config/RefactoredConfigTest.php | 19 +++- 2 files changed, 73 insertions(+), 64 deletions(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index d830b012..f8cced12 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -42,8 +42,8 @@ class RefactoredConfig /** * 构造配置实例 * - * @param array $config_paths 配置文件路径 - * @param string $environment 环境 + * @param array $config_paths 配置文件路径 + * @param string $environment 环境 * * @throws ConfigException 配置文件加载出错 */ @@ -107,6 +107,63 @@ class RefactoredConfig } } + /** + * 合并传入的配置数组至指定的配置项 + * + * @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(); + } + /** * 获取文件元信息 * @@ -224,61 +281,4 @@ class RefactoredConfig // 加入配置 $this->merge($group, $config); } - - /** - * 合并传入的配置数组至指定的配置项 - * - * @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(); - } } diff --git a/tests/ZM/Config/RefactoredConfigTest.php b/tests/ZM/Config/RefactoredConfigTest.php index 6f395a90..b195bcc0 100644 --- a/tests/ZM/Config/RefactoredConfigTest.php +++ b/tests/ZM/Config/RefactoredConfigTest.php @@ -1,10 +1,15 @@ "environment", "env" => "development"];'); - file_put_contents($mock_dir . '/test.production.php', - ' "environment", "env" => "production"];'); + file_put_contents( + $mock_dir . '/test.development.php', + ' "environment", "env" => "development"];' + ); + file_put_contents( + $mock_dir . '/test.production.php', + ' "environment", "env" => "production"];' + ); file_put_contents($mock_dir . '/test.invalid.php', ' "invalid"];'); $config = new RefactoredConfig([ From 07b2f175f312db72d8feea77852efb3101c44e3e Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sun, 21 Aug 2022 14:19:53 +0800 Subject: [PATCH 06/18] add more test cases --- tests/ZM/Config/RefactoredConfigTest.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/ZM/Config/RefactoredConfigTest.php b/tests/ZM/Config/RefactoredConfigTest.php index b195bcc0..812facb4 100644 --- a/tests/ZM/Config/RefactoredConfigTest.php +++ b/tests/ZM/Config/RefactoredConfigTest.php @@ -85,9 +85,23 @@ class RefactoredConfigTest extends TestCase $this->assertTrue(self::$config->get('test.boolean')); } - public function testGetValue(): void + /** + * @dataProvider providerTestGetValue + */ + public function testGetValue(string $key, $expected): void { - $this->assertSame('bar', self::$config->get('test.foo')); + $this->assertSame($expected, self::$config->get($key)); + } + + public function providerTestGetValue(): array + { + return [ + 'null' => ['test.null', null], + 'boolean' => ['test.boolean', true], + 'associate' => ['test.associate', ['x' => 'xxx', 'y' => 'yyy']], + 'array' => ['test.array', ['aaa', 'zzz']], + 'dot access' => ['test.x.z', 'zoo'], + ]; } public function testGetWithDefault(): void From 74af1516aca909568707350bf09698737fdc3f54 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Sun, 21 Aug 2022 16:13:16 +0800 Subject: [PATCH 07/18] add get file load type test --- src/ZM/Config/RefactoredConfig.php | 4 +++- src/ZM/Utils/ReflectionUtil.php | 17 +++++++++++++++++ tests/ZM/Config/RefactoredConfigTest.php | 23 +++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index f8cced12..71e279a5 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -102,6 +102,7 @@ class RefactoredConfig // 按照加载顺序加载配置文件 foreach (self::LOAD_ORDER as $load_type) { foreach ($stages[$load_type] as $file_path) { + logger()->info('Loading config file: ' . $file_path); $this->loadConfigFromPath($file_path); } } @@ -189,12 +190,13 @@ class RefactoredConfig /** * 获取文件加载类型 * - * @param string $name 文件名 + * @param string $name 文件名,不带扩展名 * * @return string 可能为:global, environment, patch */ private function getFileLoadType(string $name): string { + // TODO: 对于多段名称的处理,如 test.patch.development // 传入此处的 name 参数有三种可能的格式: // 1. 纯文件名:如 test,此时加载类型为 global // 2. 文件名.环境:如 test.development,此时加载类型为 environment diff --git a/src/ZM/Utils/ReflectionUtil.php b/src/ZM/Utils/ReflectionUtil.php index 34a288a7..b3632e3f 100644 --- a/src/ZM/Utils/ReflectionUtil.php +++ b/src/ZM/Utils/ReflectionUtil.php @@ -120,4 +120,21 @@ class ReflectionUtil ? new ReflectionMethod($callback[0], $callback[1]) : new ReflectionFunction($callback); } + + /** + * 获取传入的类方法,并确保其可访问 + * + * 请不要滥用此方法!!! + * + * @param string $class 类名 + * @param string $method 方法名 + * @throws ReflectionException + */ + public static function getMethod(string $class, string $method): ReflectionMethod + { + $class = new \ReflectionClass($class); + $method = $class->getMethod($method); + $method->setAccessible(true); + return $method; + } } diff --git a/tests/ZM/Config/RefactoredConfigTest.php b/tests/ZM/Config/RefactoredConfigTest.php index 812facb4..053550a8 100644 --- a/tests/ZM/Config/RefactoredConfigTest.php +++ b/tests/ZM/Config/RefactoredConfigTest.php @@ -6,6 +6,7 @@ namespace Tests\ZM\Config; use PHPUnit\Framework\TestCase; use ZM\Config\RefactoredConfig; +use ZM\Utils\ReflectionUtil; /** * @internal @@ -87,6 +88,7 @@ class RefactoredConfigTest extends TestCase /** * @dataProvider providerTestGetValue + * @param mixed $expected */ public function testGetValue(string $key, $expected): void { @@ -127,4 +129,25 @@ class RefactoredConfigTest extends TestCase $this->assertSame('environment', self::$config->get('test.from')); $this->assertSame('development', self::$config->get('test.env')); } + + /** + * @dataProvider providerTestGetFileLoadType + */ + public function testGetFileLoadType(string $name, string $type): void + { + $method = ReflectionUtil::getMethod(RefactoredConfig::class, 'getFileLoadType'); + $actual = $method->invokeArgs(self::$config, [$name]); + $this->assertSame($type, $actual); + } + + public function providerTestGetFileLoadType(): array + { + return [ + 'global' => ['test', 'global'], + 'environment' => ['test.development', 'environment'], + 'undefined' => ['test.dev.inv', 'undefined'], + 'patch' => ['test.patch', 'patch'], + // 'complex' => ['test.patch.development', 'patch'], + ]; + } } From 32534c870dfd7e5328f6b4c48bf58247548fa14e Mon Sep 17 00:00:00 2001 From: sunxyw Date: Mon, 22 Aug 2022 16:29:27 +0800 Subject: [PATCH 08/18] add clearer exception --- src/ZM/Config/RefactoredConfig.php | 18 +++++++++++------- src/ZM/Exception/ConfigException.php | 14 ++++++++++++++ tests/ZM/Config/RefactoredConfigTest.php | 2 +- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index 71e279a5..8974b7ec 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -22,22 +22,22 @@ class RefactoredConfig /** * @var array 已加载的配置文件 */ - private $loaded_files = []; + private array $loaded_files = []; /** * @var array 配置文件路径 */ - private $config_paths; + private array $config_paths; /** * @var string 当前环境 */ - private $environment; + private string $environment; /** * @var Config 内部配置容器 */ - private $holder; + private Config $holder; /** * 构造配置实例 @@ -256,7 +256,7 @@ class RefactoredConfig // 判断文件格式是否支持 [$group, $ext, $load_type, $env] = $this->getFileMeta($path); if (!in_array($ext, self::ALLOWED_FILE_EXTENSIONS, true)) { - throw new ConfigException('E00079', "不支持的配置文件格式:{$ext}"); + throw ConfigException::unsupportedFileType($path); } // 读取并解析配置 @@ -267,7 +267,11 @@ class RefactoredConfig $config = require $path; break; case 'json': - $config = json_decode($content, true); + try { + $config = json_decode($content, true, 512, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw ConfigException::loadConfigFailed($path, $e->getMessage()); + } break; case 'yaml': case 'yml': @@ -277,7 +281,7 @@ class RefactoredConfig // TODO: 实现toml解析 break; default: - throw new ConfigException('E00079', "不支持的配置文件格式:{$ext}"); + throw ConfigException::unsupportedFileType($path); } // 加入配置 diff --git a/src/ZM/Exception/ConfigException.php b/src/ZM/Exception/ConfigException.php index d61d8f5e..369f2d93 100644 --- a/src/ZM/Exception/ConfigException.php +++ b/src/ZM/Exception/ConfigException.php @@ -8,8 +8,22 @@ use Throwable; class ConfigException extends ZMException { + public const UNSUPPORTED_FILE_TYPE = 'E00079'; + + public const LOAD_CONFIG_FAILED = 'E00080'; + public function __construct($err_code, $message = '', $code = 0, Throwable $previous = null) { parent::__construct(zm_internal_errcode($err_code) . $message, $code, $previous); } + + public static function unsupportedFileType(string $file_path): ConfigException + { + return new self(self::UNSUPPORTED_FILE_TYPE, "不支持的配置文件类型:{$file_path}"); + } + + public static function loadConfigFailed(string $file_path, string $message): ConfigException + { + return new self(self::LOAD_CONFIG_FAILED, "加载配置文件失败:{$file_path},{$message}"); + } } diff --git a/tests/ZM/Config/RefactoredConfigTest.php b/tests/ZM/Config/RefactoredConfigTest.php index 053550a8..37c00ed4 100644 --- a/tests/ZM/Config/RefactoredConfigTest.php +++ b/tests/ZM/Config/RefactoredConfigTest.php @@ -13,7 +13,7 @@ use ZM\Utils\ReflectionUtil; */ class RefactoredConfigTest extends TestCase { - private static $config; + private static RefactoredConfig $config; public static function setUpBeforeClass(): void { From 488acc3e694ba79b66a7cfabd6ed4e20f763ed90 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Mon, 22 Aug 2022 16:30:45 +0800 Subject: [PATCH 09/18] fix incorrect onebot namespace --- src/ZM/Config/RefactoredConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index 8974b7ec..9b94ccc3 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace ZM\Config; -use Onebot\V12\Config\Config; +use OneBot\V12\Config\Config; use ZM\Exception\ConfigException; class RefactoredConfig From b09d12aad5eca2e747eb42d889b736c09b5903d8 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Mon, 22 Aug 2022 17:02:50 +0800 Subject: [PATCH 10/18] add config patch support --- src/ZM/Config/RefactoredConfig.php | 25 ++++++++++++------------ tests/ZM/Config/RefactoredConfigTest.php | 22 ++++++++++++++------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index 9b94ccc3..24e2f3f1 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -102,7 +102,7 @@ class RefactoredConfig // 按照加载顺序加载配置文件 foreach (self::LOAD_ORDER as $load_type) { foreach ($stages[$load_type] as $file_path) { - logger()->info('Loading config file: ' . $file_path); + logger()->info("加载配置文件:{$file_path}"); $this->loadConfigFromPath($file_path); } } @@ -196,7 +196,6 @@ class RefactoredConfig */ private function getFileLoadType(string $name): string { - // TODO: 对于多段名称的处理,如 test.patch.development // 传入此处的 name 参数有三种可能的格式: // 1. 纯文件名:如 test,此时加载类型为 global // 2. 文件名.环境:如 test.development,此时加载类型为 environment @@ -223,20 +222,20 @@ class RefactoredConfig private function shouldLoadFile(string $path): bool { $name = pathinfo($path, PATHINFO_FILENAME); - // 传入此处的 name 参数有两种可能的格式: - // 1. 纯文件名:如 test - // 2. 文件名.环境:如 test.development - // 对于第一种格式,在任何情况下均应该加载 - // 对于第二种格式,只有当环境与当前环境相同时才加载 - // 至于其他的格式,则为未定义行为 - if (strpos($name, '.') === false) { + // 对于 `global` 和 `patch`,任何情况下均应加载 + // 对于 `environment`,只有当环境与当前环境相同时才加载 + // 对于其他情况,则不加载 + $type = $this->getFileLoadType($name); + if ($type === 'global' || $type === 'patch') { return true; } - $name_and_env = explode('.', $name); - if (count($name_and_env) !== 2) { - return false; + if ($type === 'environment') { + $name_and_env = explode('.', $name); + if ($name_and_env[1] === $this->environment) { + return true; + } } - return $name_and_env[1] === $this->environment; + return false; } /** diff --git a/tests/ZM/Config/RefactoredConfigTest.php b/tests/ZM/Config/RefactoredConfigTest.php index 37c00ed4..1fcaab06 100644 --- a/tests/ZM/Config/RefactoredConfigTest.php +++ b/tests/ZM/Config/RefactoredConfigTest.php @@ -43,20 +43,23 @@ class RefactoredConfigTest extends TestCase 'a' => [ 'b.c' => 'd', ], - 'from' => 'global', + 'global' => 'yes', ]; // 下方测试需要临时写入的文件 file_put_contents($mock_dir . '/test.php', ' "environment", "env" => "development"];' + ' "yes", "env" => "development"];' ); file_put_contents( $mock_dir . '/test.production.php', - ' "environment", "env" => "production"];' + ' "yes", "env" => "production"];' + ); + file_put_contents( + $mock_dir . '/test.patch.php', + ' "yes"];' ); - file_put_contents($mock_dir . '/test.invalid.php', ' "invalid"];'); $config = new RefactoredConfig([ __DIR__ . '/config_mock', @@ -126,10 +129,15 @@ class RefactoredConfigTest extends TestCase public function testGetEnvironmentSpecifiedValue(): void { - $this->assertSame('environment', self::$config->get('test.from')); + $this->assertSame('yes', self::$config->get('test.environment')); $this->assertSame('development', self::$config->get('test.env')); } + public function testGetPatchSpecifiedValue(): void + { + $this->assertSame('yes', self::$config->get('test.patch')); + } + /** * @dataProvider providerTestGetFileLoadType */ @@ -145,9 +153,9 @@ class RefactoredConfigTest extends TestCase return [ 'global' => ['test', 'global'], 'environment' => ['test.development', 'environment'], - 'undefined' => ['test.dev.inv', 'undefined'], 'patch' => ['test.patch', 'patch'], - // 'complex' => ['test.patch.development', 'patch'], + // complex case are not supported yet + 'invalid' => ['test.patch.development', 'undefined'], ]; } } From 5d8a1deb35e876f76f4a3e9bb5625975322e9959 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Mon, 22 Aug 2022 17:15:10 +0800 Subject: [PATCH 11/18] add yaml and toml config support --- src/ZM/Config/RefactoredConfig.php | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index 24e2f3f1..139e0c77 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -274,10 +274,26 @@ class RefactoredConfig break; case 'yaml': case 'yml': - // TODO: 实现yaml解析 + $yaml_parser_class = 'Symfony\Component\Yaml\Yaml'; + if (!class_exists($yaml_parser_class)) { + throw ConfigException::loadConfigFailed($path, 'YAML 解析器未安装'); + } + try { + $config = $yaml_parser_class::parse($content); + } catch (\RuntimeException $e) { + throw ConfigException::loadConfigFailed($path, $e->getMessage()); + } break; case 'toml': - // TODO: 实现toml解析 + $toml_parser_class = 'Yosymfony\Toml\Toml'; + if (!class_exists($toml_parser_class)) { + throw ConfigException::loadConfigFailed($path, 'TOML 解析器未安装'); + } + try { + $config = $toml_parser_class::parse($content); + } catch (\RuntimeException $e) { + throw ConfigException::loadConfigFailed($path, $e->getMessage()); + } break; default: throw ConfigException::unsupportedFileType($path); From afa6411004897e6eedc78846409837d52ba7d911 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Tue, 23 Aug 2022 15:37:31 +0800 Subject: [PATCH 12/18] add array replace feature --- src/ZM/Config/RefactoredConfig.php | 4 +++- tests/ZM/Config/RefactoredConfigTest.php | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index 139e0c77..dbf8c4b3 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -111,13 +111,15 @@ class RefactoredConfig /** * 合并传入的配置数组至指定的配置项 * + * 请注意内部实现是 array_replace_recursive,而不是 array_merge + * * @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)); + $this->set($key, array_replace_recursive($original, $config)); } /** diff --git a/tests/ZM/Config/RefactoredConfigTest.php b/tests/ZM/Config/RefactoredConfigTest.php index 1fcaab06..2e1ee9c5 100644 --- a/tests/ZM/Config/RefactoredConfigTest.php +++ b/tests/ZM/Config/RefactoredConfigTest.php @@ -44,6 +44,9 @@ class RefactoredConfigTest extends TestCase 'b.c' => 'd', ], 'global' => 'yes', + 'another array' => [ + 'foo', 'bar', + ], ]; // 下方测试需要临时写入的文件 @@ -58,7 +61,7 @@ class RefactoredConfigTest extends TestCase ); file_put_contents( $mock_dir . '/test.patch.php', - ' "yes"];' + ' "yes", "another array" => ["far", "baz"]];' ); $config = new RefactoredConfig([ @@ -158,4 +161,11 @@ class RefactoredConfigTest extends TestCase 'invalid' => ['test.patch.development', 'undefined'], ]; } + + public function testArrayReplaceInsteadOfMerge(): void + { + // using of space inside config key is not an officially supported feature, + // it may be removed in the future, please avoid using it in your project. + $this->assertSame(['far', 'baz'], self::$config->get('test.another array')); + } } From 29f5c3c758329ad5ffa72c8fe84cfdf74a2938ba Mon Sep 17 00:00:00 2001 From: sunxyw Date: Tue, 23 Aug 2022 15:59:55 +0800 Subject: [PATCH 13/18] change confusing name global to default --- src/ZM/Config/RefactoredConfig.php | 16 ++++++++-------- tests/ZM/Config/RefactoredConfigTest.php | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index dbf8c4b3..6ed07a61 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -17,7 +17,7 @@ class RefactoredConfig /** * @var array 配置文件加载顺序,后覆盖前 */ - public const LOAD_ORDER = ['global', 'environment', 'patch']; + public const LOAD_ORDER = ['default', 'environment', 'patch']; /** * @var array 已加载的配置文件 @@ -63,7 +63,7 @@ class RefactoredConfig public function loadFiles(): void { $stages = [ - 'global' => [], + 'default' => [], 'environment' => [], 'patch' => [], ]; @@ -180,7 +180,7 @@ class RefactoredConfig $parts = explode('.', $basename); $ext = array_pop($parts); $load_type = $this->getFileLoadType(implode('.', $parts)); - if ($load_type === 'global') { + if ($load_type === 'default') { $env = null; } else { $env = array_pop($parts); @@ -194,17 +194,17 @@ class RefactoredConfig * * @param string $name 文件名,不带扩展名 * - * @return string 可能为:global, environment, patch + * @return string 可能为:default, environment, patch */ private function getFileLoadType(string $name): string { // 传入此处的 name 参数有三种可能的格式: - // 1. 纯文件名:如 test,此时加载类型为 global + // 1. 纯文件名:如 test,此时加载类型为 default // 2. 文件名.环境:如 test.development,此时加载类型为 environment // 3. 文件名.patch:如 test.patch,此时加载类型为 patch // 至于其他的格式,则为未定义行为 if (strpos($name, '.') === false) { - return 'global'; + return 'default'; } $name_and_env = explode('.', $name); if (count($name_and_env) !== 2) { @@ -224,11 +224,11 @@ class RefactoredConfig private function shouldLoadFile(string $path): bool { $name = pathinfo($path, PATHINFO_FILENAME); - // 对于 `global` 和 `patch`,任何情况下均应加载 + // 对于 `default` 和 `patch`,任何情况下均应加载 // 对于 `environment`,只有当环境与当前环境相同时才加载 // 对于其他情况,则不加载 $type = $this->getFileLoadType($name); - if ($type === 'global' || $type === 'patch') { + if ($type === 'default' || $type === 'patch') { return true; } if ($type === 'environment') { diff --git a/tests/ZM/Config/RefactoredConfigTest.php b/tests/ZM/Config/RefactoredConfigTest.php index 2e1ee9c5..41541530 100644 --- a/tests/ZM/Config/RefactoredConfigTest.php +++ b/tests/ZM/Config/RefactoredConfigTest.php @@ -43,7 +43,7 @@ class RefactoredConfigTest extends TestCase 'a' => [ 'b.c' => 'd', ], - 'global' => 'yes', + 'default' => 'yes', 'another array' => [ 'foo', 'bar', ], @@ -154,7 +154,7 @@ class RefactoredConfigTest extends TestCase public function providerTestGetFileLoadType(): array { return [ - 'global' => ['test', 'global'], + 'default' => ['test', 'default'], 'environment' => ['test.development', 'environment'], 'patch' => ['test.patch', 'patch'], // complex case are not supported yet From 6fc875aa7222c87134d2878c87f799be3a9c5cae Mon Sep 17 00:00:00 2001 From: sunxyw Date: Tue, 23 Aug 2022 16:00:55 +0800 Subject: [PATCH 14/18] fix windows compatibility --- src/ZM/Config/RefactoredConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/RefactoredConfig.php index 6ed07a61..2a5ab46a 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/RefactoredConfig.php @@ -78,7 +78,7 @@ class RefactoredConfig continue; } - $file_path = $config_path . '/' . $file; + $file_path = zm_dir($config_path . '/' . $file); if (is_dir($file_path)) { // TODO: 支持子目录(待定) continue; From 6cbde80a1c7596ef741752e69349fb34353a349d Mon Sep 17 00:00:00 2001 From: sunxyw Date: Tue, 23 Aug 2022 17:36:45 +0800 Subject: [PATCH 15/18] delete outdated config classes --- src/ZM/Config/ConfigMetadata.php | 18 -- src/ZM/Config/ZMConfig.php | 296 ------------------------------- 2 files changed, 314 deletions(-) delete mode 100644 src/ZM/Config/ConfigMetadata.php delete mode 100644 src/ZM/Config/ZMConfig.php diff --git a/src/ZM/Config/ConfigMetadata.php b/src/ZM/Config/ConfigMetadata.php deleted file mode 100644 index 040263ca..00000000 --- a/src/ZM/Config/ConfigMetadata.php +++ /dev/null @@ -1,18 +0,0 @@ -debug('配置文件' . $name . ' ' . $additional_key . '没读取过,正在从文件加载 ...'); - self::$config[$head_name] = self::loadConfig($head_name); - } - // global.remote_terminal - logger()->debug('根据切分来寻找子配置: ' . $name); - $obj = self::$config[$head_name]; - foreach ($separated as $key) { - if (isset($obj[$key])) { - $obj = $obj[$key]; - } else { - return null; - } - } - return $obj; - } - - public static function trace(string $name) - { - // TODO: 调试配置文件搜寻路径 - } - - public static function reload() - { - self::$config = []; - self::$config_meta_list = []; - } - - /** - * 智能patch,将patch数组内的数据合并更新到data中 - * - * @param array|mixed $data 原数据 - * @param array|mixed $patch 要patch的数据 - * @return array|mixed - */ - public static function smartPatch($data, $patch) - { - /* patch 样例: - [patch] - runtime: - annotation_reader_ignore: ["牛逼"] - custom: "非常酷的patch模式" - - [base] - runtime: - annotation_reader_ignore: [] - reload_delay_time: 800 - - [result] - runtime: - annotation_reader_ignore: ["牛逼"] - reload_delay_time: 800 - custom: "非常酷的patch模式" - */ - if (is_array($data) && is_array($patch)) { // 两者必须是数组才行 - if (is_assoc_array($patch) && is_assoc_array($data)) { // 两者必须都是kv数组才能递归merge,如果是顺序数组,则直接覆盖 - foreach ($patch as $k => $v) { - if (!isset($data[$k])) { // 如果项目不在基类存在,则直接写入 - $data[$k] = $v; - } else { // 如果base存在的话,则递归patch覆盖 - $data[$k] = self::smartPatch($data[$k], $v); - } - } - return $data; - } - } - return $patch; - } - - /** - * 加载配置文件 - * - * @throws ConfigException - * @return array|int|string - */ - private static function loadConfig(string $name) - { - // 首先获取此名称的所有配置文件的路径 - self::parseList($name); - - $env1_patch0 = null; - $env1_patch1 = null; - $env0_patch0 = null; - $env0_patch1 = null; - foreach (self::$config_meta_list[$name] as $v) { - /** @var ConfigMetadata $v */ - if ($v->is_env && !$v->is_patch) { - $env1_patch0 = $v->data; - } elseif ($v->is_env && $v->is_patch) { - $env1_patch1 = $v->data; - } elseif (!$v->is_env && !$v->is_patch) { - $env0_patch0 = $v->data; - } else { - $env0_patch1 = $v->data; - } - } - // 优先级:无env无patch < 无env有patch < 有env无patch < 有env有patch - // 但是无patch的版本必须有一个,否则会报错 - if ($env1_patch0 === null && $env0_patch0 === null) { - throw new ConfigException('E00078', '未找到配置文件 ' . $name . ' !'); - } - $data = $env1_patch0 ?? $env0_patch0; - if (is_array($patch = $env1_patch1 ?? $env0_patch1) && is_assoc_array($patch)) { - $data = self::smartPatch($data, $patch); - } - - return $data; - } - - /** - * 通过名称将所有该名称的配置文件路径和信息读取到列表中 - * - * @throws ConfigException - */ - private static function parseList(string $name): void - { - $list = []; - $files = FileSystem::scanDirFiles(self::$path, true, true); - foreach ($files as $file) { - logger()->debug('正在从目录' . self::$path . '读取配置文件 ' . $file); - $info = pathinfo($file); - $info['extension'] = $info['extension'] ?? ''; - - // 排除子文件夹名字带点的文件 - if ($info['dirname'] !== '.' && strpos($info['dirname'], '.') !== false) { - continue; - } - - // 判断文件名是否为配置文件 - if (!in_array($info['extension'], self::SUPPORTED_EXTENSIONS)) { - continue; - } - - $ext = $info['extension']; - $dot_separated = explode('.', $info['filename']); - - // 将配置文件加进来 - $obj = new ConfigMetadata(); - if ($dot_separated[0] === $name) { // 如果文件名与配置文件名一致 - // 首先检测该文件是否为补丁版本儿 - if (str_ends_with($info['filename'], '.patch')) { - $obj->is_patch = true; - $info['filename'] = substr($info['filename'], 0, -6); - } else { - $obj->is_patch = false; - } - // 其次检测该文件是不是带有环境参数的版本儿 - if (str_ends_with($info['filename'], '.' . self::$env)) { - $obj->is_env = true; - $info['filename'] = substr($info['filename'], 0, -(strlen(self::$env) + 1)); - } else { - $obj->is_env = false; - } - if (mb_strpos($info['filename'], '.') !== false) { - logger()->warning('文件名 ' . $info['filename'] . ' 不合法(含有"."),请检查文件名是否合法。'); - continue; - } - $obj->path = zm_dir(self::$path . '/' . $info['dirname'] . '/' . $info['basename']); - $obj->extension = $ext; - $obj->data = self::readConfigFromFile(zm_dir(self::$path . '/' . $info['dirname'] . '/' . $info['basename']), $info['extension']); - $list[] = $obj; - } - } - // 如果是源码模式,config目录和default目录相同,所以不需要继续采摘default目录下的文件 - if (realpath(self::$path) !== realpath(self::DEFAULT_PATH)) { - $files = FileSystem::scanDirFiles(self::DEFAULT_PATH, true, true); - foreach ($files as $file) { - $info = pathinfo($file); - $info['extension'] = $info['extension'] ?? ''; - // 判断文件名是否为配置文件 - if (!in_array($info['extension'], self::SUPPORTED_EXTENSIONS)) { - continue; - } - if ($info['filename'] === $name) { // 如果文件名与配置文件名一致,就创建一个配置文件的元数据对象 - $obj = new ConfigMetadata(); - $obj->is_patch = false; - $obj->is_env = false; - $obj->path = realpath(self::DEFAULT_PATH . '/' . $info['dirname'] . '/' . $info['basename']); - $obj->extension = $info['extension']; - $obj->data = self::readConfigFromFile(zm_dir(self::DEFAULT_PATH . '/' . $info['dirname'] . '/' . $info['basename']), $info['extension']); - $list[] = $obj; - } - } - } - self::$config_meta_list[$name] = $list; - } - - /** - * 根据不同的扩展类型读取配置文件数组 - * - * @param mixed|string $filename 文件名 - * @param mixed|string $ext_name 扩展名 - * @throws ConfigException - */ - private static function readConfigFromFile($filename, $ext_name): array - { - logger()->debug('正加载配置文件 ' . $filename); - switch ($ext_name) { - case 'php': - $r = require $filename; - if (is_array($r)) { - return $r; - } - throw new ConfigException('E00079', 'php配置文件include失败,请检查终端warning错误'); - case 'json': - default: - $r = json_decode(file_get_contents($filename), true); - if (is_array($r)) { - return $r; - } - throw new ConfigException('E00079', 'json反序列化失败,请检查文件内容'); - } - } -} From 18892a14c21b1cf2c266b7f89acf44d2b3c80c26 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Tue, 23 Aug 2022 17:51:20 +0800 Subject: [PATCH 16/18] ready to replace legacy config --- src/Globals/global_functions.php | 29 +++- .../{RefactoredConfig.php => ZMConfig.php} | 2 +- .../Container/ContainerServicesProvider.php | 8 +- ...actoredConfigTest.php => ZMConfigTest.php} | 10 +- tests_old/ZM/Config/ZMConfigTest.php | 164 ------------------ 5 files changed, 39 insertions(+), 174 deletions(-) rename src/ZM/Config/{RefactoredConfig.php => ZMConfig.php} (99%) rename tests/ZM/Config/{RefactoredConfigTest.php => ZMConfigTest.php} (95%) delete mode 100644 tests_old/ZM/Config/ZMConfigTest.php diff --git a/src/Globals/global_functions.php b/src/Globals/global_functions.php index 15c76222..982ddf7b 100644 --- a/src/Globals/global_functions.php +++ b/src/Globals/global_functions.php @@ -7,6 +7,7 @@ use OneBot\Driver\Coroutine\CoroutineInterface; use OneBot\Driver\Process\ExecutionResult; use OneBot\V12\Object\MessageSegment; use Psr\Log\LoggerInterface; +use ZM\Config\ZMConfig; use ZM\Container\Container; use ZM\Container\ContainerInterface; use ZM\Context\Context; @@ -141,8 +142,8 @@ function container(): ContainerInterface /** * 解析类实例(使用容器) * - * @template T - * @param class-string $abstract + * @template T + * @param class-string $abstract * @return Closure|mixed|T * @noinspection PhpDocMissingThrowsInspection */ @@ -156,7 +157,7 @@ function resolve(string $abstract, array $parameters = []) * 获取容器实例 * * @template T - * @param null|class-string $abstract + * @param null|class-string $abstract * @return Closure|ContainerInterface|mixed|T */ function app(string $abstract = null, array $parameters = []) @@ -187,3 +188,25 @@ function mysql_builder(string $name = '') { return (new MySQLWrapper($name))->createQueryBuilder(); } + +/** + * 获取 / 设置配置项 + * + * 传入键名和(或)默认值,获取配置项 + * 传入数组,设置配置项 + * 不传参数,返回配置容器 + * + * @param null|string|array $key 键名 + * @param mixed $default 默认值 + * @return mixed|ZMConfig + */ +function config($key = null, $default = null) +{ + if (is_null($key)) { + return resolve('config'); + } + if (is_array($key)) { + return resolve('config')->set(...$key); + } + return resolve('config')->get($key, $default); +} diff --git a/src/ZM/Config/RefactoredConfig.php b/src/ZM/Config/ZMConfig.php similarity index 99% rename from src/ZM/Config/RefactoredConfig.php rename to src/ZM/Config/ZMConfig.php index 2a5ab46a..73780924 100644 --- a/src/ZM/Config/RefactoredConfig.php +++ b/src/ZM/Config/ZMConfig.php @@ -7,7 +7,7 @@ namespace ZM\Config; use OneBot\V12\Config\Config; use ZM\Exception\ConfigException; -class RefactoredConfig +class ZMConfig { /** * @var array 支持的文件扩展名 diff --git a/src/ZM/Container/ContainerServicesProvider.php b/src/ZM/Container/ContainerServicesProvider.php index 26a79e85..7a80df07 100644 --- a/src/ZM/Container/ContainerServicesProvider.php +++ b/src/ZM/Container/ContainerServicesProvider.php @@ -29,7 +29,7 @@ class ContainerServicesProvider * connection: open, close, message * ``` * - * @param string $scope 作用域 + * @param string $scope 作用域 * @throws ConfigException */ public function registerServices(string $scope, ...$params): void @@ -81,6 +81,12 @@ class ContainerServicesProvider // 注册logger $container->instance(LoggerInterface::class, logger()); + + // 注册config + $container->instance(ZMConfig::class, new ZMConfig([ + SOURCE_ROOT_DIR . '/config', + ])); + $container->alias(ZMConfig::class, 'config'); } /** diff --git a/tests/ZM/Config/RefactoredConfigTest.php b/tests/ZM/Config/ZMConfigTest.php similarity index 95% rename from tests/ZM/Config/RefactoredConfigTest.php rename to tests/ZM/Config/ZMConfigTest.php index 41541530..a2ca06fa 100644 --- a/tests/ZM/Config/RefactoredConfigTest.php +++ b/tests/ZM/Config/ZMConfigTest.php @@ -5,15 +5,15 @@ declare(strict_types=1); namespace Tests\ZM\Config; use PHPUnit\Framework\TestCase; -use ZM\Config\RefactoredConfig; +use ZM\Config\ZMConfig; use ZM\Utils\ReflectionUtil; /** * @internal */ -class RefactoredConfigTest extends TestCase +class ZMConfigTest extends TestCase { - private static RefactoredConfig $config; + private static ZMConfig $config; public static function setUpBeforeClass(): void { @@ -64,7 +64,7 @@ class RefactoredConfigTest extends TestCase ' "yes", "another array" => ["far", "baz"]];' ); - $config = new RefactoredConfig([ + $config = new ZMConfig([ __DIR__ . '/config_mock', ], 'development'); self::$config = $config; @@ -146,7 +146,7 @@ class RefactoredConfigTest extends TestCase */ public function testGetFileLoadType(string $name, string $type): void { - $method = ReflectionUtil::getMethod(RefactoredConfig::class, 'getFileLoadType'); + $method = ReflectionUtil::getMethod(ZMConfig::class, 'getFileLoadType'); $actual = $method->invokeArgs(self::$config, [$name]); $this->assertSame($type, $actual); } diff --git a/tests_old/ZM/Config/ZMConfigTest.php b/tests_old/ZM/Config/ZMConfigTest.php deleted file mode 100644 index 477c286c..00000000 --- a/tests_old/ZM/Config/ZMConfigTest.php +++ /dev/null @@ -1,164 +0,0 @@ - 30055];'); - file_put_contents($mock_dir . '/php_exception.php', ' 30055];'); - file_put_contents($mock_dir . '/global.invalid.development.php', ' 30055];'); - file_put_contents($mock_dir . '/fake.development.json', '{"multi":{"level":"test"}}'); - file_put_contents($mock_dir . '/no_main_only_patch.patch.json', '{"multi":{"level":"test"}}'); - } - - public static function tearDownAfterClass(): void - { - ZMConfig::reload(); - ZMConfig::restoreDirectory(); - foreach (DataProvider::scanDirFiles(__DIR__ . '/config_mock', true, false) as $file) { - unlink($file); - } - rmdir(__DIR__ . '/config_mock'); - } - - /** - * @throws ConfigException - */ - public function testReload() - { - $this->markTestIncomplete('logger level change in need'); - $this->expectOutputRegex('/没读取过,正在从文件加载/'); - $this->assertEquals('0.0.0.0', ZMConfig::get('global.host')); - ZMConfig::reload(); - Console::setLevel(4); - $this->assertEquals('0.0.0.0', ZMConfig::get('global.host')); - Console::setLevel(0); - } - - public function testSetAndRestoreDirectory() - { - $origin = ZMConfig::getDirectory(); - ZMConfig::setDirectory('.'); - $this->assertEquals('.', ZMConfig::getDirectory()); - ZMConfig::restoreDirectory(); - $this->assertEquals($origin, ZMConfig::getDirectory()); - } - - public function testSetAndGetEnv() - { - $this->expectException(ConfigException::class); - ZMConfig::setEnv('production'); - $this->assertEquals('production', ZMConfig::getEnv()); - ZMConfig::setEnv(); - ZMConfig::setEnv('reee'); - } - - /** - * @dataProvider providerTestGet - * @param mixed $expected - * @throws ConfigException - */ - public function testGet(array $data_params, $expected) - { - $this->assertEquals($expected, ZMConfig::get(...$data_params)); - } - - public function providerTestGet(): array - { - return [ - 'get port' => [['global.port'], 30055], - 'get port key 2' => [['global', 'port'], 30055], - 'get invalid key' => [['global', 'invalid'], null], - 'get another environment' => [['fake.multi.level'], 'test'], - ]; - } - - public function testGetPhpException() - { - $this->expectException(ConfigException::class); - ZMConfig::get('php_exception'); - } - - public function testGetJsonException() - { - $this->expectException(ConfigException::class); - ZMConfig::get('json_exception'); - } - - public function testOnlyPatchException() - { - $this->expectException(ConfigException::class); - ZMConfig::get('no_main_only_patch.test'); - } - - public function testSmartPatch() - { - $array = [ - 'key-1-1' => 'value-1-1', - 'key-1-2' => [ - 'key-2-1' => [ - 'key-3-1' => [ - 'value-3-1', - 'value-3-2', - ], - ], - ], - 'key-1-3' => [ - 'key-4-1' => 'value-4-1', - ], - ]; - $patch = [ - 'key-1-2' => [ - 'key-2-1' => [ - 'key-3-1' => [ - 'value-3-3', - ], - ], - ], - 'key-1-3' => [ - 'key-4-2' => [ - 'key-5-1' => 'value-5-1', - ], - ], - ]; - $expected = [ - 'key-1-1' => 'value-1-1', - 'key-1-2' => [ - 'key-2-1' => [ - 'key-3-1' => [ - 'value-3-3', - ], - ], - ], - 'key-1-3' => [ - 'key-4-1' => 'value-4-1', - 'key-4-2' => [ - 'key-5-1' => 'value-5-1', - ], - ], - ]; - $this->assertEquals($expected, ZMConfig::smartPatch($array, $patch)); - } -} From eb7e700e7c4a79b975912e7e9ef34254c6a04ee9 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Tue, 23 Aug 2022 18:02:00 +0800 Subject: [PATCH 17/18] replace legacy config --- src/Globals/global_functions.php | 8 +-- src/ZM/Annotation/AnnotationParser.php | 3 +- src/ZM/Command/CheckConfigCommand.php | 3 +- .../Generate/SystemdGenerateCommand.php | 3 +- src/ZM/Config/ZMConfig.php | 49 ++++++++++++++++- .../Container/ContainerServicesProvider.php | 4 +- src/ZM/Event/Listener/MasterEventListener.php | 3 +- src/ZM/Event/Listener/WorkerEventListener.php | 3 +- src/ZM/Framework.php | 53 +++++++++---------- src/ZM/InstantApplication.php | 3 +- src/ZM/Store/MySQL/MySQLDriver.php | 3 +- src/ZM/Utils/HttpUtil.php | 15 +++--- 12 files changed, 94 insertions(+), 56 deletions(-) diff --git a/src/Globals/global_functions.php b/src/Globals/global_functions.php index 982ddf7b..3f4df067 100644 --- a/src/Globals/global_functions.php +++ b/src/Globals/global_functions.php @@ -143,7 +143,7 @@ function container(): ContainerInterface * 解析类实例(使用容器) * * @template T - * @param class-string $abstract + * @param class-string $abstract * @return Closure|mixed|T * @noinspection PhpDocMissingThrowsInspection */ @@ -157,7 +157,7 @@ function resolve(string $abstract, array $parameters = []) * 获取容器实例 * * @template T - * @param null|class-string $abstract + * @param null|class-string $abstract * @return Closure|ContainerInterface|mixed|T */ function app(string $abstract = null, array $parameters = []) @@ -196,8 +196,8 @@ function mysql_builder(string $name = '') * 传入数组,设置配置项 * 不传参数,返回配置容器 * - * @param null|string|array $key 键名 - * @param mixed $default 默认值 + * @param null|array|string $key 键名 + * @param mixed $default 默认值 * @return mixed|ZMConfig */ function config($key = null, $default = null) diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php index 70f6f285..c897baf5 100644 --- a/src/ZM/Annotation/AnnotationParser.php +++ b/src/ZM/Annotation/AnnotationParser.php @@ -15,7 +15,6 @@ use ZM\Annotation\Http\Route; use ZM\Annotation\Interfaces\ErgodicAnnotation; use ZM\Annotation\Interfaces\Level; use ZM\Annotation\Middleware\Middleware; -use ZM\Config\ZMConfig; use ZM\Exception\ConfigException; use ZM\Store\FileSystem; use ZM\Utils\HttpUtil; @@ -92,7 +91,7 @@ class AnnotationParser $all_class = FileSystem::getClassesPsr4($path[0], $path[1]); // 读取配置文件中配置的忽略解析的注解名,防止误解析一些别的地方需要的注解,比如@mixin - $conf = ZMConfig::get('global.runtime.annotation_reader_ignore'); + $conf = config('global.runtime.annotation_reader_ignore'); // 有两种方式,第一种是通过名称,第二种是通过命名空间 if (isset($conf['name']) && is_array($conf['name'])) { foreach ($conf['name'] as $v) { diff --git a/src/ZM/Command/CheckConfigCommand.php b/src/ZM/Command/CheckConfigCommand.php index ef49127b..eaec01e2 100644 --- a/src/ZM/Command/CheckConfigCommand.php +++ b/src/ZM/Command/CheckConfigCommand.php @@ -7,7 +7,6 @@ namespace ZM\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use ZM\Config\ZMConfig; class CheckConfigCommand extends Command { @@ -57,7 +56,7 @@ class CheckConfigCommand extends Command { $local_file = include_once WORKING_DIR . '/config/' . $local; if ($local_file === true) { - $local_file = ZMConfig::get('global'); + $local_file = config('global'); } foreach ($remote as $k => $v) { if (!isset($local_file[$k])) { diff --git a/src/ZM/Command/Generate/SystemdGenerateCommand.php b/src/ZM/Command/Generate/SystemdGenerateCommand.php index 6de2711a..851f9ca1 100644 --- a/src/ZM/Command/Generate/SystemdGenerateCommand.php +++ b/src/ZM/Command/Generate/SystemdGenerateCommand.php @@ -7,7 +7,6 @@ namespace ZM\Command\Generate; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use ZM\Config\ZMConfig; class SystemdGenerateCommand extends Command { @@ -21,7 +20,7 @@ class SystemdGenerateCommand extends Command protected function execute(InputInterface $input, OutputInterface $output): int { - ZMConfig::setDirectory(SOURCE_ROOT_DIR . '/config'); + config()->addConfigPath(SOURCE_ROOT_DIR . '/config'); $path = $this->generate(); $output->writeln('成功生成 systemd 文件,位置:' . $path . ''); $output->writeln('有关如何使用 systemd 配置文件,请访问 `https://github.com/zhamao-robot/zhamao-framework/issues/36`'); diff --git a/src/ZM/Config/ZMConfig.php b/src/ZM/Config/ZMConfig.php index 73780924..3b759ffc 100644 --- a/src/ZM/Config/ZMConfig.php +++ b/src/ZM/Config/ZMConfig.php @@ -7,7 +7,7 @@ namespace ZM\Config; use OneBot\V12\Config\Config; use ZM\Exception\ConfigException; -class ZMConfig +class ZMConfig implements \ArrayAccess { /** * @var array 支持的文件扩展名 @@ -55,6 +55,33 @@ class ZMConfig $this->loadFiles(); } + /** + * 添加配置文件路径 + * + * @param string $path 路径 + */ + public function addConfigPath(string $path): void + { + if (!in_array($path, $this->config_paths, true)) { + $this->config_paths[] = $path; + } + } + + /** + * 设置当前环境 + * + * 变更环境后,将会自动调用 `reload` 方法重载配置 + * + * @param string $environment 目标环境 + */ + public function setEnvironment(string $environment): void + { + if ($this->environment !== $environment) { + $this->environment = $environment; + $this->reload(); + } + } + /** * 加载配置文件 * @@ -167,6 +194,26 @@ class ZMConfig $this->loadFiles(); } + public function offsetExists($offset): bool + { + return $this->get($offset) !== null; + } + + public function offsetGet($offset) + { + return $this->get($offset); + } + + public function offsetSet($offset, $value): void + { + $this->set($offset, $value); + } + + public function offsetUnset($offset): void + { + $this->set($offset, null); + } + /** * 获取文件元信息 * diff --git a/src/ZM/Container/ContainerServicesProvider.php b/src/ZM/Container/ContainerServicesProvider.php index 7a80df07..1f2c5ad3 100644 --- a/src/ZM/Container/ContainerServicesProvider.php +++ b/src/ZM/Container/ContainerServicesProvider.php @@ -29,7 +29,7 @@ class ContainerServicesProvider * connection: open, close, message * ``` * - * @param string $scope 作用域 + * @param string $scope 作用域 * @throws ConfigException */ public function registerServices(string $scope, ...$params): void @@ -72,7 +72,7 @@ class ContainerServicesProvider $container->instance('path.working', WORKING_DIR); $container->instance('path.source', SOURCE_ROOT_DIR); $container->alias('path.source', 'path.base'); - $container->instance('path.data', ZMConfig::get('global.data_dir')); + $container->instance('path.data', config('global.data_dir')); $container->instance('path.framework', FRAMEWORK_ROOT_DIR); // 注册worker和驱动 diff --git a/src/ZM/Event/Listener/MasterEventListener.php b/src/ZM/Event/Listener/MasterEventListener.php index 6ecd585a..8e6be230 100644 --- a/src/ZM/Event/Listener/MasterEventListener.php +++ b/src/ZM/Event/Listener/MasterEventListener.php @@ -7,7 +7,6 @@ namespace ZM\Event\Listener; use OneBot\Driver\Workerman\Worker; use OneBot\Util\Singleton; use Swoole\Server; -use ZM\Config\ZMConfig; use ZM\Exception\ZMKnownException; use ZM\Framework; use ZM\Process\ProcessStateManager; @@ -27,7 +26,7 @@ class MasterEventListener SignalListener::getInstance()->signalMaster(); } ProcessStateManager::saveProcessState(ONEBOT_PROCESS_MASTER, $server->master_pid, [ - 'stdout' => ZMConfig::get('global.swoole_options.swoole_set.log_file'), + 'stdout' => config('global.swoole_options.swoole_set.log_file'), 'daemon' => (bool) Framework::getInstance()->getArgv()['daemon'], ]); }); diff --git a/src/ZM/Event/Listener/WorkerEventListener.php b/src/ZM/Event/Listener/WorkerEventListener.php index c275648d..dfd09c91 100644 --- a/src/ZM/Event/Listener/WorkerEventListener.php +++ b/src/ZM/Event/Listener/WorkerEventListener.php @@ -11,7 +11,6 @@ use ZM\Annotation\AnnotationHandler; use ZM\Annotation\AnnotationMap; use ZM\Annotation\AnnotationParser; use ZM\Annotation\Framework\Init; -use ZM\Config\ZMConfig; use ZM\Container\ContainerServicesProvider; use ZM\Exception\ConfigException; use ZM\Exception\ZMKnownException; @@ -159,7 +158,7 @@ class WorkerEventListener } // 读取 MySQL 配置文件 - $conf = ZMConfig::get('global.mysql'); + $conf = config('global.mysql'); if (is_array($conf) && !is_assoc_array($conf)) { // 如果有多个数据库连接,则遍历 foreach ($conf as $conn_conf) { diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 33b0ff08..7fde6191 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -19,7 +19,6 @@ use OneBot\Driver\Workerman\WorkermanDriver; use OneBot\Util\Singleton; use Phar; use ZM\Command\Server\ServerStartCommand; -use ZM\Config\ZMConfig; use ZM\Event\EventProvider; use ZM\Event\Listener\HttpEventListener; use ZM\Event\Listener\ManagerEventListener; @@ -189,8 +188,8 @@ class Framework } foreach ($find_dir as $v) { if (is_dir($v)) { - ZMConfig::setDirectory($v); - ZMConfig::setEnv($this->argv['env'] = $this->argv['env'] ?? 'development'); + config()->addConfigPath($v); + config()->setEnvironment($this->argv['env'] = ($this->argv['env'] ?? 'development')); $config_done = true; break; } @@ -214,7 +213,7 @@ class Framework $ob_event_provider = EventProvider::getInstance(); // 初始化时区,默认为上海时区 - date_default_timezone_set(ZMConfig::get('global.runtime.timezone')); + date_default_timezone_set(config('global.runtime.timezone')); // 注册全局错误处理器 set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) { @@ -251,20 +250,20 @@ class Framework */ public function initDriver() { - switch ($driver = ZMConfig::get('global.driver')) { + switch ($driver = config('global.driver')) { case 'swoole': if (DIRECTORY_SEPARATOR === '\\') { logger()->emergency('Windows does not support swoole driver!'); exit(1); } - ZMConfig::$config['global']['swoole_options']['driver_init_policy'] = DriverInitPolicy::MULTI_PROCESS_INIT_IN_MASTER; - $this->driver = new SwooleDriver(ZMConfig::get('global.swoole_options')); - $this->driver->initDriverProtocols(ZMConfig::get('global.servers')); + config(['global.swoole_options.driver_init_policy' => DriverInitPolicy::MULTI_PROCESS_INIT_IN_MASTER]); + $this->driver = new SwooleDriver(config('global.swoole_options')); + $this->driver->initDriverProtocols(config('global.servers')); break; case 'workerman': - ZMConfig::$config['global']['workerman_options']['driver_init_policy'] = DriverInitPolicy::MULTI_PROCESS_INIT_IN_MASTER; - $this->driver = new WorkermanDriver(ZMConfig::get('global.workerman_options')); - $this->driver->initDriverProtocols(ZMConfig::get('global.servers')); + config(['global.workerman_options.driver_init_policy' => DriverInitPolicy::MULTI_PROCESS_INIT_IN_MASTER]); + $this->driver = new WorkermanDriver(config('global.workerman_options')); + $this->driver->initDriverProtocols(config('global.servers')); break; default: logger()->error(zm_internal_errcode('E00081') . '未知的驱动类型 ' . $driver . ' !'); @@ -327,9 +326,9 @@ class Framework // 打印环境信息 $properties['environment'] = $this->argv['env']; // 打印驱动 - $properties['driver'] = ZMConfig::get('global.driver'); + $properties['driver'] = config('global.driver'); // 打印logger显示等级 - $properties['log_level'] = $this->argv['log-level'] ?? ZMConfig::get('global', 'log_level') ?? 'info'; + $properties['log_level'] = $this->argv['log-level'] ?? config('global', 'log_level') ?? 'info'; // 打印框架版本 $properties['version'] = self::VERSION . (LOAD_MODE === 0 ? (' (build ' . ZM_VERSION_ID . ')') : ''); // 打印 PHP 版本 @@ -342,8 +341,8 @@ class Framework if ($this->driver->getName() === 'swoole') { $properties['process_mode'] = 'MST1'; ProcessStateManager::$process_mode['master'] = 1; - if (ZMConfig::get('global.swoole_options.swoole_server_mode') === SWOOLE_BASE) { - $worker_num = ZMConfig::get('global.swoole_options.swoole_set.worker_num'); + if (config('global.swoole_options.swoole_server_mode') === SWOOLE_BASE) { + $worker_num = config('global.swoole_options.swoole_set.worker_num'); if ($worker_num === null || $worker_num === 1) { $properties['process_mode'] .= 'MAN0#0'; ProcessStateManager::$process_mode['manager'] = 0; @@ -353,12 +352,12 @@ class Framework ProcessStateManager::$process_mode['manager'] = 0; ProcessStateManager::$process_mode['worker'] = swoole_cpu_num(); } else { - $properties['process_mode'] .= 'MAN0#' . ($worker = ZMConfig::get('global.swoole_options.swoole_set.worker_num') ?? swoole_cpu_num()); + $properties['process_mode'] .= 'MAN0#' . ($worker = config('global.swoole_options.swoole_set.worker_num') ?? swoole_cpu_num()); ProcessStateManager::$process_mode['manager'] = 0; ProcessStateManager::$process_mode['worker'] = $worker; } } else { - $worker = ZMConfig::get('global.swoole_options.swoole_set.worker_num') === 0 ? swoole_cpu_num() : ZMConfig::get('global.swoole_options.swoole_set.worker_num') ?? swoole_cpu_num(); + $worker = config('global.swoole_options.swoole_set.worker_num') === 0 ? swoole_cpu_num() : config('global.swoole_options.swoole_set.worker_num') ?? swoole_cpu_num(); $properties['process_mode'] .= 'MAN1#' . $worker; ProcessStateManager::$process_mode['manager'] = 1; ProcessStateManager::$process_mode['worker'] = $worker; @@ -366,7 +365,7 @@ class Framework } elseif ($this->driver->getName() === 'workerman') { $properties['process_mode'] = 'MST1'; ProcessStateManager::$process_mode['master'] = 1; - $worker_num = ZMConfig::get('global.workerman_options.workerman_worker_num'); + $worker_num = config('global.workerman_options.workerman_worker_num'); if (DIRECTORY_SEPARATOR === '\\') { $properties['process_mode'] .= '#0'; ProcessStateManager::$process_mode['manager'] = 0; @@ -379,17 +378,17 @@ class Framework } } // 打印监听端口 - foreach (ZMConfig::get('global.servers') as $k => $v) { + foreach (config('global.servers') as $k => $v) { $properties['listen_' . $k] = $v['type'] . '://' . $v['host'] . ':' . $v['port']; } // 打印 MySQL 连接信息 - if ((ZMConfig::get('global.mysql_config.host') ?? '') !== '') { - $conf = ZMConfig::get('global', 'mysql_config'); + if ((config('global.mysql_config.host') ?? '') !== '') { + $conf = config('global', 'mysql_config'); $properties['mysql'] = $conf['dbname'] . '@' . $conf['host'] . ':' . $conf['port']; } // 打印 Redis 连接信息 - if ((ZMConfig::get('global', 'redis_config')['host'] ?? '') !== '') { - $conf = ZMConfig::get('global', 'redis_config'); + if ((config('global', 'redis_config')['host'] ?? '') !== '') { + $conf = config('global', 'redis_config'); $properties['redis_pool'] = $conf['host'] . ':' . $conf['port']; } @@ -480,14 +479,14 @@ class Framework } switch ($x) { case 'driver': // 动态设置驱动类型 - ZMConfig::$config['global']['driver'] = $y; + config()['global']['driver'] = $y; break; case 'worker-num': // 动态设置 Worker 数量 - ZMConfig::$config['global']['swoole_options']['swoole_set']['worker_num'] = intval($y); - ZMConfig::$config['global']['workerman_options']['workerman_worker_num'] = intval($y); + config()['global']['swoole_options']['swoole_set']['worker_num'] = intval($y); + config()['global']['workerman_options']['workerman_worker_num'] = intval($y); break; case 'daemon': // 启动为守护进程 - ZMConfig::$config['global']['swoole_options']['swoole_set']['daemonize'] = 1; + config()['global']['swoole_options']['swoole_set']['daemonize'] = 1; Worker::$daemonize = true; break; } diff --git a/src/ZM/InstantApplication.php b/src/ZM/InstantApplication.php index 6076aee1..74c4e703 100644 --- a/src/ZM/InstantApplication.php +++ b/src/ZM/InstantApplication.php @@ -6,7 +6,6 @@ namespace ZM; use Exception; use ZM\Command\Server\ServerStartCommand; -use ZM\Config\ZMConfig; use ZM\Exception\InitException; use ZM\Plugin\InstantPlugin; @@ -40,7 +39,7 @@ class InstantApplication extends InstantPlugin public function withArgs(array $args): InstantApplication { - $this->args = ZMConfig::smartPatch($this->args, $args); + $this->args = array_replace_recursive($this->args, $args); return $this; } diff --git a/src/ZM/Store/MySQL/MySQLDriver.php b/src/ZM/Store/MySQL/MySQLDriver.php index 2205248e..68815d0b 100644 --- a/src/ZM/Store/MySQL/MySQLDriver.php +++ b/src/ZM/Store/MySQL/MySQLDriver.php @@ -7,7 +7,6 @@ namespace ZM\Store\MySQL; use Doctrine\DBAL\Driver as DoctrineDriver; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Schema\MySqlSchemaManager; -use ZM\Config\ZMConfig; class MySQLDriver implements DoctrineDriver { @@ -34,7 +33,7 @@ class MySQLDriver implements DoctrineDriver public function getDatabase($conn) { - $conf = ZMConfig::get('global.mysql'); + $conf = config('global.mysql'); if ($conn instanceof MySQLConnection) { foreach ($conf as $v) { diff --git a/src/ZM/Utils/HttpUtil.php b/src/ZM/Utils/HttpUtil.php index d10d0a64..df651c88 100644 --- a/src/ZM/Utils/HttpUtil.php +++ b/src/ZM/Utils/HttpUtil.php @@ -13,7 +13,6 @@ use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\RouteCollection; -use ZM\Config\ZMConfig; use ZM\Exception\ConfigException; use ZM\Store\FileSystem; @@ -80,13 +79,13 @@ class HttpUtil public static function handleStaticPage(string $uri, array $settings = []): ResponseInterface { // 确定根目录 - $base_dir = $settings['document_root'] ?? ZMConfig::get('global.file_server.document_root'); + $base_dir = $settings['document_root'] ?? config('global.file_server.document_root'); // 将相对路径转换为绝对路径 if (FileSystem::isRelativePath($base_dir)) { $base_dir = SOURCE_ROOT_DIR . '/' . $base_dir; } // 支持默认缺省搜索的文件名(如index.html) - $base_index = $settings['document_index'] ?? ZMConfig::get('global.file_server.document_index'); + $base_index = $settings['document_index'] ?? config('global.file_server.document_index'); if (is_string($base_index)) { $base_index = [$base_index]; } @@ -110,7 +109,7 @@ class HttpUtil logger()->info('[200] ' . $uri); $exp = strtolower(pathinfo($path . $vp)['extension'] ?? 'unknown'); return HttpFactory::getInstance()->createResponse() - ->withAddedHeader('Content-Type', ZMConfig::get('file_header')[$exp] ?? 'application/octet-stream') + ->withAddedHeader('Content-Type', config('file_header')[$exp] ?? 'application/octet-stream') ->withBody(HttpFactory::getInstance()->createStream(file_get_contents($path . '/' . $vp))); } } @@ -119,7 +118,7 @@ class HttpUtil logger()->info('[200] ' . $uri); $exp = strtolower(pathinfo($path)['extension'] ?? 'unknown'); return HttpFactory::getInstance()->createResponse() - ->withAddedHeader('Content-Type', ZMConfig::get('file_header')[$exp] ?? 'application/octet-stream') + ->withAddedHeader('Content-Type', config('file_header')[$exp] ?? 'application/octet-stream') ->withBody(HttpFactory::getInstance()->createStream(file_get_contents($path))); } } @@ -136,14 +135,14 @@ class HttpUtil public static function handleHttpCodePage(int $code): ResponseInterface { // 获取有没有规定 code page - $code_page = ZMConfig::get('global.file_server.document_code_page')[$code] ?? null; - if ($code_page !== null && !file_exists((ZMConfig::get('global.file_server.document_root') ?? '/not/exist/') . '/' . $code_page)) { + $code_page = config('global.file_server.document_code_page')[$code] ?? null; + if ($code_page !== null && !file_exists((config('global.file_server.document_root') ?? '/not/exist/') . '/' . $code_page)) { $code_page = null; } if ($code_page === null) { return HttpFactory::getInstance()->createResponse($code); } - return HttpFactory::getInstance()->createResponse($code, null, [], file_get_contents(ZMConfig::get('global.file_server.document_root') . '/' . $code_page)); + return HttpFactory::getInstance()->createResponse($code, null, [], file_get_contents(config('global.file_server.document_root') . '/' . $code_page)); } /** From 975d0e3540e92701539cf209d66cf6ee408e6453 Mon Sep 17 00:00:00 2001 From: sunxyw Date: Tue, 23 Aug 2022 18:18:20 +0800 Subject: [PATCH 18/18] allow multiple set config --- src/Globals/global_functions.php | 14 +++++----- src/ZM/Config/ZMConfig.php | 27 ++++++++++++++----- .../Container/ContainerServicesProvider.php | 13 +-------- src/ZM/Framework.php | 2 +- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/Globals/global_functions.php b/src/Globals/global_functions.php index 3f4df067..862741d1 100644 --- a/src/Globals/global_functions.php +++ b/src/Globals/global_functions.php @@ -196,17 +196,19 @@ function mysql_builder(string $name = '') * 传入数组,设置配置项 * 不传参数,返回配置容器 * - * @param null|array|string $key 键名 - * @param mixed $default 默认值 - * @return mixed|ZMConfig + * @param null|array|string $key 键名 + * @param mixed $default 默认值 + * @return mixed|void|ZMConfig */ function config($key = null, $default = null) { + $config = ZMConfig::getInstance(); if (is_null($key)) { - return resolve('config'); + return $config; } if (is_array($key)) { - return resolve('config')->set(...$key); + $config->set($key); + return; } - return resolve('config')->get($key, $default); + return $config->get($key, $default); } diff --git a/src/ZM/Config/ZMConfig.php b/src/ZM/Config/ZMConfig.php index 3b759ffc..faf0b1c5 100644 --- a/src/ZM/Config/ZMConfig.php +++ b/src/ZM/Config/ZMConfig.php @@ -4,11 +4,14 @@ declare(strict_types=1); namespace ZM\Config; +use OneBot\Util\Singleton; use OneBot\V12\Config\Config; use ZM\Exception\ConfigException; class ZMConfig implements \ArrayAccess { + use Singleton; + /** * @var array 支持的文件扩展名 */ @@ -19,6 +22,11 @@ class ZMConfig implements \ArrayAccess */ public const LOAD_ORDER = ['default', 'environment', 'patch']; + /** + * @var string 默认配置文件路径 + */ + public const DEFAULT_CONFIG_PATH = SOURCE_ROOT_DIR . '/config'; + /** * @var array 已加载的配置文件 */ @@ -47,9 +55,9 @@ class ZMConfig implements \ArrayAccess * * @throws ConfigException 配置文件加载出错 */ - public function __construct(array $config_paths, string $environment = 'development') + public function __construct(array $config_paths = [], string $environment = 'development') { - $this->config_paths = $config_paths; + $this->config_paths = $config_paths ?: [self::DEFAULT_CONFIG_PATH]; $this->environment = $environment; $this->holder = new Config([]); $this->loadFiles(); @@ -166,12 +174,18 @@ class ZMConfig implements \ArrayAccess * 设置配置项 * 仅在本次运行期间生效,不会保存到配置文件中哦 * - * @param string $key 配置项名称,可使用.访问数组 - * @param mixed $value 要写入的值,传入 null 会进行删除 + * 如果传入的是数组,则会将键名作为配置项名称,并将值作为配置项的值 + * 顺带一提,数组支持批量设置 + * + * @param array|string $key 配置项名称,可使用.访问数组 + * @param mixed $value 要写入的值,传入 null 会进行删除 */ - public function set(string $key, $value): void + public function set($key, $value = null): void { - $this->holder->set($key, $value); + $keys = is_array($key) ? $key : [$key => $value]; + foreach ($keys as $i_key => $i_val) { + $this->holder->set($i_key, $i_val); + } } /** @@ -199,6 +213,7 @@ class ZMConfig implements \ArrayAccess return $this->get($offset) !== null; } + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->get($offset); diff --git a/src/ZM/Container/ContainerServicesProvider.php b/src/ZM/Container/ContainerServicesProvider.php index 1f2c5ad3..94d65d65 100644 --- a/src/ZM/Container/ContainerServicesProvider.php +++ b/src/ZM/Container/ContainerServicesProvider.php @@ -10,10 +10,8 @@ use OneBot\Driver\Event\Http\HttpRequestEvent; use OneBot\Driver\Process\ProcessManager; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; -use ZM\Config\ZMConfig; use ZM\Context\Context; use ZM\Context\ContextInterface; -use ZM\Exception\ConfigException; use ZM\Framework; class ContainerServicesProvider @@ -29,8 +27,7 @@ class ContainerServicesProvider * connection: open, close, message * ``` * - * @param string $scope 作用域 - * @throws ConfigException + * @param string $scope 作用域 */ public function registerServices(string $scope, ...$params): void { @@ -63,8 +60,6 @@ class ContainerServicesProvider /** * 注册全局服务 - * - * @throws ConfigException */ private function registerGlobalServices(ContainerInterface $container): void { @@ -81,12 +76,6 @@ class ContainerServicesProvider // 注册logger $container->instance(LoggerInterface::class, logger()); - - // 注册config - $container->instance(ZMConfig::class, new ZMConfig([ - SOURCE_ROOT_DIR . '/config', - ])); - $container->alias(ZMConfig::class, 'config'); } /** diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 7fde6191..d4d5ef67 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -328,7 +328,7 @@ class Framework // 打印驱动 $properties['driver'] = config('global.driver'); // 打印logger显示等级 - $properties['log_level'] = $this->argv['log-level'] ?? config('global', 'log_level') ?? 'info'; + $properties['log_level'] = $this->argv['log-level'] ?? config('global.log_level') ?? 'info'; // 打印框架版本 $properties['version'] = self::VERSION . (LOAD_MODE === 0 ? (' (build ' . ZM_VERSION_ID . ')') : ''); // 打印 PHP 版本