From bc76febadba76d9ec39c4ecbbffee88bf612105a Mon Sep 17 00:00:00 2001 From: sunxyw Date: Fri, 24 Feb 2023 16:49:24 +0800 Subject: [PATCH] refactor framework kernel --- src/ZM/Bootstrap/Bootstrapper.php | 12 ++ src/ZM/Bootstrap/HandleExceptions.php | 5 +- src/ZM/Bootstrap/LoadConfiguration.php | 67 ++-------- src/ZM/Bootstrap/LoadGlobalDefines.php | 6 +- src/ZM/Bootstrap/LoadPlugins.php | 5 +- src/ZM/Bootstrap/RegisterEventProvider.php | 5 +- src/ZM/Bootstrap/RegisterLogger.php | 9 +- src/ZM/Bootstrap/SetInternalTimezone.php | 6 +- src/ZM/Command/Server/ServerStartCommand.php | 19 ++- src/ZM/Config/ZMConfig.php | 47 +------ src/ZM/ConsoleApplication.php | 106 ++++++++++------ src/ZM/Container/ContainerHolder.php | 14 ++- src/ZM/Framework.php | 3 +- src/ZM/Kernel.php | 124 +++++++++++++++++++ 14 files changed, 266 insertions(+), 162 deletions(-) create mode 100644 src/ZM/Bootstrap/Bootstrapper.php create mode 100644 src/ZM/Kernel.php diff --git a/src/ZM/Bootstrap/Bootstrapper.php b/src/ZM/Bootstrap/Bootstrapper.php new file mode 100644 index 00000000..a258d2dd --- /dev/null +++ b/src/ZM/Bootstrap/Bootstrapper.php @@ -0,0 +1,12 @@ +loadEnvVariables($env); - $config_i = config(); - $config_i->addConfigPath($this->getConfigDir($config)); - $config_i->setEnvironment($this->getConfigEnvironment($config)); - $this->parseArgvToConfig($config, $config_i); - } - - private function getConfigDir(array $config): string - { - $config_dir = $config['config-dir'] ?? null; - // 默认配置文件目录 - $find_dir = [ - WORKING_DIR . '/config', - SOURCE_ROOT_DIR . '/config', - ]; - // 如果启动参数指定了配置文件目录,则优先使用 - if ($config_dir !== null) { - array_unshift($find_dir, $config_dir); - } - - // 遍历目录,找到第一个存在的目录 - foreach ($find_dir as $dir) { - if (is_dir($dir)) { - return $dir; - } - } - - // 如果没有找到目录,则抛出异常 - throw new \RuntimeException('No config directory found'); - } - - private function getConfigEnvironment(array $config): string - { - return $config['env'] ?? 'development'; - } - - private function parseArgvToConfig(array $argv, ZMConfig $config): void - { - foreach ($argv as $x => $y) { - // 当值为 true/false 时,表示该参数为可选参数。当值为 null 时,表示该参数必定会有一个值,如果是 null,说明没指定 - if ($y === false || is_null($y)) { - continue; - } - switch ($x) { - case 'driver': // 动态设置驱动类型 - $config->set('global.driver', $y); - break; - case 'worker-num': // 动态设置 Worker 数量 - $config->set('global.swoole_options.swoole_set.worker_num', (int) $y); - $config->set('global.workerman_options.workerman_worker_num', (int) $y); - break; - case 'daemon': // 启动为守护进程 - $config->set('global.swoole_options.swoole_set.daemonize', 1); - Worker::$daemonize = true; - break; - } - } + new ZMConfig([ + 'source' => [ + 'paths' => [$kernel->getConfigDir()], + ], + ]); } private function loadEnvVariables(EnvironmentInterface $env): void diff --git a/src/ZM/Bootstrap/LoadGlobalDefines.php b/src/ZM/Bootstrap/LoadGlobalDefines.php index 356a9c24..e06f671b 100644 --- a/src/ZM/Bootstrap/LoadGlobalDefines.php +++ b/src/ZM/Bootstrap/LoadGlobalDefines.php @@ -4,9 +4,11 @@ declare(strict_types=1); namespace ZM\Bootstrap; -class LoadGlobalDefines +use ZM\Kernel; + +class LoadGlobalDefines implements Bootstrapper { - public function bootstrap(array $config): void + public function bootstrap(Kernel $kernel): void { require FRAMEWORK_ROOT_DIR . '/src/Globals/global_defines_framework.php'; } diff --git a/src/ZM/Bootstrap/LoadPlugins.php b/src/ZM/Bootstrap/LoadPlugins.php index abe2b2be..d531d4eb 100644 --- a/src/ZM/Bootstrap/LoadPlugins.php +++ b/src/ZM/Bootstrap/LoadPlugins.php @@ -4,11 +4,12 @@ declare(strict_types=1); namespace ZM\Bootstrap; +use ZM\Kernel; use ZM\Plugin\PluginManager; -class LoadPlugins +class LoadPlugins implements Bootstrapper { - public function bootstrap(array $config): void + public function bootstrap(Kernel $kernel): void { // 先遍历下插件目录下是否有这个插件,没有这个插件则不能打包 $plugin_dir = config('global.plugin.load_dir', SOURCE_ROOT_DIR . '/plugins'); diff --git a/src/ZM/Bootstrap/RegisterEventProvider.php b/src/ZM/Bootstrap/RegisterEventProvider.php index 005006fa..9976499a 100644 --- a/src/ZM/Bootstrap/RegisterEventProvider.php +++ b/src/ZM/Bootstrap/RegisterEventProvider.php @@ -5,10 +5,11 @@ declare(strict_types=1); namespace ZM\Bootstrap; use ZM\Event\EventProvider; +use ZM\Kernel; -class RegisterEventProvider +class RegisterEventProvider implements Bootstrapper { - public function bootstrap(array $config): void + public function bootstrap(Kernel $kernel): void { global $ob_event_provider; $ob_event_provider = EventProvider::getInstance(); diff --git a/src/ZM/Bootstrap/RegisterLogger.php b/src/ZM/Bootstrap/RegisterLogger.php index 19a79d18..6d57fad2 100644 --- a/src/ZM/Bootstrap/RegisterLogger.php +++ b/src/ZM/Bootstrap/RegisterLogger.php @@ -4,18 +4,17 @@ declare(strict_types=1); namespace ZM\Bootstrap; +use ZM\Kernel; use ZM\Logger\ConsoleLogger; -class RegisterLogger +class RegisterLogger implements Bootstrapper { - public function bootstrap(array $config): void + public function bootstrap(Kernel $kernel): void { // 初始化 Logger if (!ob_logger_registered()) { - $debug = $config['verbose'] ?? false; - $debug = $debug ? 'debug' : null; // 如果没有注册过 Logger,那么就初始化一个,在启动框架前注册的话,就不会初始化了,可替换为其他 Logger - $logger = new ConsoleLogger($config['log-level'] ?? $debug ?? 'info'); + $logger = new ConsoleLogger($kernel->getLogLevel()); ob_logger_register($logger); } } diff --git a/src/ZM/Bootstrap/SetInternalTimezone.php b/src/ZM/Bootstrap/SetInternalTimezone.php index 5b327ca7..f03d1a20 100644 --- a/src/ZM/Bootstrap/SetInternalTimezone.php +++ b/src/ZM/Bootstrap/SetInternalTimezone.php @@ -4,9 +4,11 @@ declare(strict_types=1); namespace ZM\Bootstrap; -class SetInternalTimezone +use ZM\Kernel; + +class SetInternalTimezone implements Bootstrapper { - public function bootstrap(array $config): void + public function bootstrap(Kernel $kernel): void { date_default_timezone_set(config('global.runtime.timezone', 'UTC')); } diff --git a/src/ZM/Command/Server/ServerStartCommand.php b/src/ZM/Command/Server/ServerStartCommand.php index 1ff05740..1e830448 100644 --- a/src/ZM/Command/Server/ServerStartCommand.php +++ b/src/ZM/Command/Server/ServerStartCommand.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ZM\Command\Server; use OneBot\Driver\Process\ProcessManager; +use OneBot\Driver\Workerman\Worker; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -26,13 +27,10 @@ class ServerStartCommand extends ServerCommand protected function configure() { $this->setDefinition([ - new InputOption('config-dir', null, InputOption::VALUE_REQUIRED, '指定其他配置文件目录'), new InputOption('driver', null, InputOption::VALUE_REQUIRED, '指定驱动类型'), - new InputOption('log-level', null, InputOption::VALUE_REQUIRED, '调整消息等级到debug (log-level=4)'), new InputOption('daemon', null, null, '以守护进程的方式运行框架'), new InputOption('worker-num', null, InputOption::VALUE_REQUIRED, '启动框架时运行的 Worker 进程数量'), new InputOption('watch', null, null, '监听 plugins/ 目录下各个插件的文件变化并热更新(还不能用)'), - new InputOption('env', null, InputOption::VALUE_REQUIRED, '设置环境类型 (production, development, staging)'), new InputOption('disable-safe-exit', null, null, '关闭安全退出(关闭后按CtrlC时直接杀死进程)'), new InputOption('no-state-check', null, null, '关闭启动前框架运行状态检查'), new InputOption('private-mode', null, null, '启动时隐藏MOTD和敏感信息'), @@ -60,6 +58,21 @@ class ServerStartCommand extends ServerCommand } } } + + if ($input->getOption('driver')) { + config(['global.driver' => $input->getOption('driver')]); + } + + if ($input->getOption('worker-num')) { + config(['global.swoole_options.swoole_set.worker_num' => (int) $input->getOption('worker-num')]); + config(['global.workerman_options.workerman_worker_num' => (int) $input->getOption('worker-num')]); + } + + if ($input->getOption('daemon')) { + config(['global.swoole_options.swoole_set.daemonize' => 1]); + Worker::$daemonize = true; + } + // 框架启动的入口 (new Framework($input->getOptions()))->init()->start(); return 0; diff --git a/src/ZM/Config/ZMConfig.php b/src/ZM/Config/ZMConfig.php index 812ec861..1c6d8fdc 100644 --- a/src/ZM/Config/ZMConfig.php +++ b/src/ZM/Config/ZMConfig.php @@ -8,6 +8,7 @@ use OneBot\Config\Config; use OneBot\Config\Loader\LoaderInterface; use OneBot\Util\Singleton; use ZM\Exception\ConfigException; +use ZM\Kernel; class ZMConfig { @@ -42,11 +43,6 @@ class ZMConfig */ private array $config_paths; - /** - * @var string 当前环境 - */ - private string $environment; - /** * @var Config 内部配置容器 */ @@ -66,18 +62,15 @@ class ZMConfig /** * 构造配置实例 * - * @param string $environment 环境 - * * @throws ConfigException 配置文件加载出错 */ - public function __construct(string $environment = 'uninitiated', array $init_config = null) + public function __construct(array $init_config = null) { - $conf = $init_config ?: $this->loadInitConfig(); + // 合并初始化配置,构造传入优先 + $conf = array_merge_recursive($this->loadInitConfig(), $init_config ?? []); $this->file_extensions = $conf['source']['extensions']; $this->config_paths = $conf['source']['paths']; - $this->environment = self::$environment_alias[$environment] ?? $environment; - // 初始化配置容器 $this->holder = new Config( new ($conf['repository'][0])(...$conf['repository'][1]), @@ -93,9 +86,7 @@ class ZMConfig $this->tracer = null; } - if ($environment !== 'uninitiated') { - $this->loadFiles(); - } + $this->loadFiles(); } /** @@ -207,32 +198,6 @@ class ZMConfig } } - /** - * 获取当前环境 - * - * @return string 当前环境 - */ - public function getEnvironment(): string - { - return $this->environment; - } - - /** - * 设置当前环境 - * - * 变更环境后,将会自动调用 `reload` 方法重载配置 - * - * @param string $environment 目标环境 - */ - public function setEnvironment(string $environment): void - { - $target = self::$environment_alias[$environment] ?? $environment; - if ($this->environment !== $target) { - $this->environment = $target; - $this->reload(); - } - } - /** * 重载配置文件 * 运行期间新增的配置文件不会被加载哟~ @@ -337,7 +302,7 @@ class ZMConfig } if ($type === 'environment') { $name_and_env = explode('.', $name); - if ($name_and_env[1] === $this->environment) { + if (Kernel::getInstance()->environment($name_and_env[1])) { return true; } } diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 4423bd87..42839559 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -7,9 +7,11 @@ namespace ZM; use Symfony\Component\Console\Application; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\CommandLoader\FactoryCommandLoader; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use ZM\Command\Server\ServerStartCommand; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\EventDispatcher\EventDispatcher; use ZM\Exception\SingletonViolationException; use ZM\Store\FileSystem; @@ -20,15 +22,6 @@ use ZM\Store\FileSystem; */ final class ConsoleApplication extends Application { - protected array $bootstrappers = [ - Bootstrap\LoadConfiguration::class, // 加载配置文件 - Bootstrap\LoadGlobalDefines::class, // 加载框架级别的全局常量声明 - Bootstrap\RegisterLogger::class, // 加载 Logger - Bootstrap\HandleExceptions::class, // 注册异常处理器 - Bootstrap\RegisterEventProvider::class, // 绑定框架的 EventProvider 到 libob 的 Driver 上 - Bootstrap\SetInternalTimezone::class, // 设置时区 - ]; - private static ?ConsoleApplication $obj = null; public function __construct(string $name = 'zhamao-framework') @@ -37,6 +30,67 @@ final class ConsoleApplication extends Application throw new SingletonViolationException(self::class); } + // 初始化 Composer 变量 + if (file_exists(WORKING_DIR . '/runtime/composer.phar')) { + echo '* Using native composer' . PHP_EOL; + putenv('COMPOSER_EXECUTABLE=' . WORKING_DIR . '/runtime/composer.phar'); + } + + $this->registerCommandLoader(); + + // 执行父级初始化 + parent::__construct($name, ZM_VERSION); + + $this->registerGlobalOptions(); + + // 设置命令事件分发器 + $dispatcher = new EventDispatcher(); + $this->setDispatcher($dispatcher); + + // 注册命令执行前监听器 + $dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) { + $input = $event->getInput(); + + // 初始化内核 + /** @var Kernel $kernel */ + $kernel = Kernel::getInstance(); + $kernel->setConfigDir($input->getOption('config-dir')); + $kernel->setEnvironment($input->getOption('env')); + $kernel->setDebugMode($input->getOption('debug')); + $kernel->setLogLevel($input->getOption('log-level')); + $kernel->bootstrap(); + }); + + // 注册命令执行错误监听器 + $dispatcher->addListener(ConsoleEvents::ERROR, function (ConsoleErrorEvent $event) { + $e = $event->getError(); + // 输出错误信息 + echo zm_internal_errcode('E00005') . "{$e->getMessage()} at {$e->getFile()}({$e->getLine()})" . PHP_EOL; + exit(1); + }); + + // 设置单例,阻止后续实例化 + self::$obj = $this; + } + + /** + * 注册全局选项,应用到所有命令 + */ + public function registerGlobalOptions(): void + { + $this->getDefinition()->addOptions([ + new InputOption('debug', 'd', InputOption::VALUE_NONE, '启用调试模式'), + new InputOption('env', 'e', InputOption::VALUE_REQUIRED, '指定运行环境', 'development'), + new InputOption('config-dir', 'c', InputOption::VALUE_REQUIRED, '指定配置文件目录', SOURCE_ROOT_DIR . '/config'), + new InputOption('log-level', 'l', InputOption::VALUE_REQUIRED, '指定日志等级', 'info'), + ]); + } + + /** + * 注册命令加载器 + */ + private function registerCommandLoader(): void + { // 初始化命令 $command_classes = []; // 先加载框架内置命令 @@ -51,11 +105,7 @@ final class ConsoleApplication extends Application FileSystem::getClassesPsr4(SOURCE_ROOT_DIR . '/src/Command', 'Command') ); } - // 初始化 Composer 变量 - if (file_exists(WORKING_DIR . '/runtime/composer.phar')) { - echo '* Using native composer' . PHP_EOL; - putenv('COMPOSER_EXECUTABLE=' . WORKING_DIR . '/runtime/composer.phar'); - } + // TODO: 加载插件命令,可以考虑自定义 CommandLoader $commands = []; foreach ($command_classes as $command_class) { try { @@ -78,27 +128,5 @@ final class ConsoleApplication extends Application // 命令工厂,用于延迟加载命令 $command_loader = new FactoryCommandLoader($commands); $this->setCommandLoader($command_loader); - - self::$obj = $this; // 用于标记已经初始化完成 - parent::__construct($name, ZM_VERSION); - } - - /** - * {@inheritdoc} - */ - public function run(InputInterface $input = null, OutputInterface $output = null): int - { - // 注册 bootstrap - $options = $input?->getOptions() ?? ServerStartCommand::exportOptionArray(); - foreach ($this->bootstrappers as $bootstrapper) { - resolve($bootstrapper)->bootstrap($options); - } - - try { - return parent::run($input, $output); - } catch (\Throwable $e) { - echo zm_internal_errcode('E00005') . "{$e->getMessage()} at {$e->getFile()}({$e->getLine()})" . PHP_EOL; - exit(1); - } } } diff --git a/src/ZM/Container/ContainerHolder.php b/src/ZM/Container/ContainerHolder.php index 56fe1264..5cc2f8d7 100644 --- a/src/ZM/Container/ContainerHolder.php +++ b/src/ZM/Container/ContainerHolder.php @@ -8,12 +8,15 @@ use DI; use DI\Container; use DI\ContainerBuilder; use OneBot\Driver\Coroutine\Adaptive; +use ZM\Kernel; class ContainerHolder { /** @var Container[] */ private static array $container = []; + private static array $config = []; + public static function getEventContainer(): Container { $cid = Adaptive::getCoroutine()?->getCid() ?? -1; @@ -34,13 +37,13 @@ class ContainerHolder $builder = new ContainerBuilder(); $builder->addDefinitions( new AliasDefinitionSource(), - new DI\Definition\Source\DefinitionArray(config('container.definitions', [])), + new DI\Definition\Source\DefinitionArray(self::$config['definitions'] ?? []), ); $builder->useAutowiring(true); $builder->useAttributes(true); // 容器缓存 - $enable_cache = config('container.cache.enable', false); + $enable_cache = self::$config['cache']['enable'] ?? false; if (is_callable($enable_cache)) { $enable_cache = $enable_cache(); } @@ -49,10 +52,15 @@ class ContainerHolder if (!extension_loaded('apcu')) { logger()->warning('APCu 扩展未加载,容器缓存将不可用'); } else { - $builder->enableDefinitionCache(config('container.cache.namespace', '')); + $builder->enableDefinitionCache(self::$config['cache']['namespace'] ?? ''); } } return $builder->build(); } + + private static function loadConfig(): void + { + self::$config = require Kernel::getInstance()->getConfigDir() . '/container.php'; + } } diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 712dc866..40fb6381 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -21,7 +21,6 @@ use OneBot\Driver\Workerman\Worker; use OneBot\Driver\Workerman\WorkermanDriver; use OneBot\Util\Singleton; use ZM\Command\Server\ServerStartCommand; -use ZM\Config\ZMConfig; use ZM\Container\ContainerBindingListener; use ZM\Event\Listener\HttpEventListener; use ZM\Event\Listener\ManagerEventListener; @@ -260,7 +259,7 @@ class Framework // 打印工作目录 $properties['working_dir'] = WORKING_DIR; // 打印环境信息 - $properties['environment'] = ZMConfig::getInstance()->getEnvironment(); + $properties['environment'] = Kernel::getInstance()->environment(); // 打印驱动 $properties['driver'] = config('global.driver'); // 打印logger显示等级 diff --git a/src/ZM/Kernel.php b/src/ZM/Kernel.php new file mode 100644 index 00000000..8dfa9a71 --- /dev/null +++ b/src/ZM/Kernel.php @@ -0,0 +1,124 @@ +registerBootstrappers([ + Bootstrap\LoadConfiguration::class, // 加载配置文件 + Bootstrap\LoadGlobalDefines::class, // 加载框架级别的全局常量声明 + Bootstrap\RegisterLogger::class, // 加载 Logger + Bootstrap\HandleExceptions::class, // 注册异常处理器 + Bootstrap\RegisterEventProvider::class, // 绑定框架的 EventProvider 到 libob 的 Driver 上 + Bootstrap\SetInternalTimezone::class, // 设置时区 + ]); + } + + /** + * 获取版本号 + */ + public function version(): string + { + return ZM_VERSION; + } + + /** + * 获取配置文件目录 + */ + public function getConfigDir(): string + { + return $this->config_dir; + } + + /** + * 设置配置文件目录 + */ + public function setConfigDir(string $config_dir): void + { + $this->config_dir = $config_dir; + } + + /** + * 获取或检查运行环境 + * + * @param array|string ...$environments + */ + public function environment(...$environments): string|bool + { + if (empty($environments)) { + return $this->environment; + } + + return in_array($this->environment, $environments, true); + } + + /** + * 设置运行环境 + */ + public function setEnvironment(string $environment): void + { + $this->environment = $environment; + } + + public function isDebugMode(): bool + { + return $this->debug_mode; + } + + public function setDebugMode(bool $debug_mode): void + { + $this->debug_mode = $debug_mode; + } + + public function getLogLevel(): string + { + return $this->isDebugMode() ? 'debug' : $this->log_level; + } + + public function setLogLevel(string $log_level): void + { + $this->log_level = $log_level; + } + + public function runningInConsole(): bool + { + return PHP_SAPI === 'cli'; + } + + public function runningUnitTests(): bool + { + return $this->environment('testing'); + } + + public function registerBootstrappers(array $bootstrappers): void + { + $this->bootstrappers = array_merge($this->bootstrappers, $bootstrappers); + } + + public function bootstrap(): void + { + foreach ($this->bootstrappers as $bootstrapper) { + /* @var Bootstrapper $bootstrapper */ + (new $bootstrapper())->bootstrap($this); + } + } +}