diff --git a/bin/phpunit-zm b/bin/phpunit-zm index ae1717a2..0b07a84a 100755 --- a/bin/phpunit-zm +++ b/bin/phpunit-zm @@ -57,14 +57,13 @@ $options['driver'] = 'workerman'; $options['worker-num'] = 1; $options['private-mode'] = true; -// TODO: optimize this, maybe abstract the application (framework) -$bootstrappers = new ReflectionProperty(\ZM\ConsoleApplication::class, 'bootstrappers'); -foreach ($bootstrappers->getDefaultValue() as $bootstrapper) { - resolve($bootstrapper)->bootstrap($options); -} - try { - (new Framework($options))->init()->start(); + $framework = new Framework(); + $framework->runtime_preferences = $framework->runtime_preferences + ->withConfigDir(dirname(__DIR__) . '/config') + ->withEnvironment('development'); + $framework->bootstrap(); + $framework->init()->start(); exit($_swoole_atomic->get()); } catch (Throwable $e) { echo $e->getMessage() . PHP_EOL; diff --git a/config/container.php b/config/container.php index 9e7cc42c..684f31b0 100644 --- a/config/container.php +++ b/config/container.php @@ -7,8 +7,8 @@ use OneBot\Driver\Process\ProcessManager; use Psr\Log\LoggerInterface; use ZM\Config\Environment; use ZM\Config\EnvironmentInterface; -use ZM\Config\ZMConfig; use ZM\Framework; +use ZM\HasRuntimeInfo; /* * 这里是容器的配置文件,你可以在这里配置容器的绑定和其他一些参数。 @@ -28,6 +28,8 @@ return [ Driver::class => fn () => Framework::getInstance()->getDriver(), LoggerInterface::class => fn () => logger(), EnvironmentInterface::class => Environment::class, + + HasRuntimeInfo::class => Framework::class, ], // 容器的缓存配置,默认情况下,只有在生产环境下才会启用缓存 @@ -37,7 +39,7 @@ return [ // 详细介绍请参阅:https://php-di.org/doc/performances.html#caching 'cache' => [ // 是否启用缓存,支持 bool、callable - 'enable' => fn () => ZMConfig::getInstance()->getEnvironment() === 'production', + 'enable' => fn () => Framework::getInstance()->runtime_preferences->environment('production'), 'namespace' => 'zm', ], ]; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7f81ee24..1ceb3339 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -28,6 +28,6 @@ - + diff --git a/src/Module/Example/Hello123.php b/src/Module/Example/Hello123.php index 135d0b9e..94fd6381 100644 --- a/src/Module/Example/Hello123.php +++ b/src/Module/Example/Hello123.php @@ -5,15 +5,11 @@ declare(strict_types=1); namespace Module\Example; use Choir\Http\ServerRequest; -use Choir\WebSocket\FrameInterface; -use OneBot\Driver\Coroutine\Adaptive; use OneBot\Driver\Event\WebSocket\WebSocketMessageEvent; use ZM\Annotation\Framework\BindEvent; -use ZM\Annotation\Framework\Cron; use ZM\Annotation\Http\Route; use ZM\Annotation\Middleware\Middleware; use ZM\Annotation\OneBot\BotCommand; -use ZM\Annotation\OneBot\BotEvent; use ZM\Annotation\OneBot\CommandArgument; use ZM\Annotation\OneBot\CommandHelp; use ZM\Context\BotContext; @@ -21,22 +17,22 @@ use ZM\Middleware\TimerMiddleware; class Hello123 { + #[BindEvent(WebSocketMessageEvent::class, level: 5000)] + public function onMessage(WebSocketMessageEvent $event) + { + $Data = json_decode($event->getFrame()->getData(), true); + } + #[Route('/route', request_method: ['GET'])] #[Route('/route/{id}', request_method: ['GET'])] #[Middleware(TimerMiddleware::class)] - public function route(array $params, ServerRequest $request, \HttpRequestEvent $event) + public function route(array $params, ServerRequest $request, \HttpRequestEvent $event, BotContext $ctx) { // 目前因内部实现限制,路由方法的参数必须按照这个顺序定义,可以省略,但是不能乱序 // 如果希望获取其他依赖,可以在现有参数后面继续添加 return 'Hello Zhamao!This is the first 3.0 page!' . ($params['id'] ?? ''); } - #[BotEvent()] - public function onOBEvent(WebSocketMessageEvent $messageEvent, \OneBotEvent $event): void - { - logger()->info("收到了 {$event->getType()}.{$event->getDetailType()} 事件"); - } - #[BotCommand('echo', 'echo')] #[CommandArgument('text', '要回复的内容', required: true)] #[CommandHelp('复读机', '只需要发送 echo+内容 即可自动复读', 'echo 你好 会回复 你好')] @@ -44,25 +40,4 @@ class Hello123 { $context->reply($event->getMessage()); } - - #[BindEvent(WebSocketMessageEvent::class)] - public function onWSMessage(FrameInterface $frame, WebSocketMessageEvent $event): void - { - logger()->info('收到了 WebSocket 消息'); - } - - #[Cron('* * * * *', no_overlap: true)] - public function logTime(): void - { - $time = date('Y-m-d H:i:s'); - logger()->info('我看到时间了,让我写下来'); - Adaptive::sleep(5); - logger()->info('写好啦,时间是' . $time); - } - - #[Cron('* * * * *')] - public function logTime2(): void - { - logger()->info('我不需要等,但也不给你看时间'); - } } diff --git a/src/ZM/Bootstrap/Bootstrapper.php b/src/ZM/Bootstrap/Bootstrapper.php new file mode 100644 index 00000000..05f2290d --- /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' => [$preferences->getConfigDir()], + ], + ]); } private function loadEnvVariables(EnvironmentInterface $env): void diff --git a/src/ZM/Bootstrap/LoadGlobalDefines.php b/src/ZM/Bootstrap/LoadGlobalDefines.php index 356a9c24..e9cf67c5 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\Config\RuntimePreferences; + +class LoadGlobalDefines implements Bootstrapper { - public function bootstrap(array $config): void + public function bootstrap(RuntimePreferences $preferences): 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..79501be4 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\Config\RuntimePreferences; use ZM\Plugin\PluginManager; -class LoadPlugins +class LoadPlugins implements Bootstrapper { - public function bootstrap(array $config): void + public function bootstrap(RuntimePreferences $preferences): 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..8bf53c71 100644 --- a/src/ZM/Bootstrap/RegisterEventProvider.php +++ b/src/ZM/Bootstrap/RegisterEventProvider.php @@ -4,11 +4,12 @@ declare(strict_types=1); namespace ZM\Bootstrap; +use ZM\Config\RuntimePreferences; use ZM\Event\EventProvider; -class RegisterEventProvider +class RegisterEventProvider implements Bootstrapper { - public function bootstrap(array $config): void + public function bootstrap(RuntimePreferences $preferences): 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..2bbf799a 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\Config\RuntimePreferences; use ZM\Logger\ConsoleLogger; -class RegisterLogger +class RegisterLogger implements Bootstrapper { - public function bootstrap(array $config): void + public function bootstrap(RuntimePreferences $preferences): 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($preferences->getLogLevel()); ob_logger_register($logger); } } diff --git a/src/ZM/Bootstrap/SetInternalTimezone.php b/src/ZM/Bootstrap/SetInternalTimezone.php index 5b327ca7..aa3a7f96 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\Config\RuntimePreferences; + +class SetInternalTimezone implements Bootstrapper { - public function bootstrap(array $config): void + public function bootstrap(RuntimePreferences $preferences): void { date_default_timezone_set(config('global.runtime.timezone', 'UTC')); } diff --git a/src/ZM/Command/Command.php b/src/ZM/Command/Command.php index 77f77191..066f2333 100644 --- a/src/ZM/Command/Command.php +++ b/src/ZM/Command/Command.php @@ -33,11 +33,6 @@ abstract class Command extends \Symfony\Component\Console\Command\Command implem $this->input = $input; $this->output = $output; if ($this->shouldExecute()) { - if (property_exists($this, 'bootstrappers')) { - foreach ($this->bootstrappers as $bootstrapper) { - (new $bootstrapper())->bootstrap($this->input->getOptions()); - } - } try { return $this->handle(); } catch (\Throwable $e) { diff --git a/src/ZM/Command/Server/ServerStartCommand.php b/src/ZM/Command/Server/ServerStartCommand.php index 1ff05740..4980678d 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,8 +58,23 @@ 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(); + Framework::getInstance()->init($input->getOptions())->start(); return 0; } } diff --git a/src/ZM/Config/RuntimePreferences.php b/src/ZM/Config/RuntimePreferences.php new file mode 100644 index 00000000..458f2377 --- /dev/null +++ b/src/ZM/Config/RuntimePreferences.php @@ -0,0 +1,83 @@ +environment; + } + + return in_array($this->environment, $environments, true); + } + + public function withEnvironment(string $environment): self + { + $copy = clone $this; + $copy->environment = $environment; + return $copy; + } + + public function isDebugMode(): bool + { + return $this->debug_mode; + } + + public function enableDebugMode(bool $debug_mode): self + { + $copy = clone $this; + $copy->debug_mode = $debug_mode; + return $copy; + } + + public function getLogLevel(): string + { + return $this->isDebugMode() ? 'debug' : $this->log_level; + } + + public function withLogLevel(string $log_level): self + { + $copy = clone $this; + $copy->log_level = $log_level; + return $copy; + } + + public function getConfigDir(): string + { + return $this->config_dir; + } + + public function withConfigDir(string $config_dir): self + { + $copy = clone $this; + $copy->config_dir = $config_dir; + return $copy; + } + + public function runningInInteractiveTerminal(): bool + { + if (function_exists('posix_isatty')) { + return posix_isatty(STDIN) && posix_isatty(STDOUT); + } + + // fallback to stream_isatty() if posix_isatty() is not available (e.g. on Windows) + return function_exists('stream_isatty') && stream_isatty(STDIN) && stream_isatty(STDOUT); + } + + public function runningUnitTests(): bool + { + return defined('PHPUNIT_RUNNING') && constant('PHPUNIT_RUNNING'); + } +} diff --git a/src/ZM/Config/ZMConfig.php b/src/ZM/Config/ZMConfig.php index 812ec861..38e44cc5 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\Framework; 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 (Framework::getInstance()->runtime_preferences->environment($name_and_env[1])) { return true; } } diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 4423bd87..f595b541 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,68 @@ 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 Framework $kernel */ + $kernel = Framework::getInstance(); + $kernel->runtime_preferences = $kernel->runtime_preferences + ->withConfigDir($input->getOption('config-dir')) + ->withEnvironment($input->getOption('env')) + ->enableDebugMode($input->getOption('debug')) + ->withLogLevel($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 +106,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 +129,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..ba4f7527 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\Framework; class ContainerHolder { /** @var Container[] */ private static array $container = []; + private static array $config = []; + public static function getEventContainer(): Container { $cid = Adaptive::getCoroutine()?->getCid() ?? -1; @@ -31,16 +34,18 @@ class ContainerHolder private static function buildContainer(): Container { + self::loadConfig(); + $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 +54,18 @@ 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 + { + if (self::$config) { + return; + } + self::$config = require Framework::getInstance()->runtime_preferences->getConfigDir() . '/container.php'; + } } diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 712dc866..8df6bf46 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -20,8 +20,9 @@ use OneBot\Driver\Swoole\SwooleDriver; use OneBot\Driver\Workerman\Worker; use OneBot\Driver\Workerman\WorkermanDriver; use OneBot\Util\Singleton; +use ZM\Bootstrap\Bootstrapper; use ZM\Command\Server\ServerStartCommand; -use ZM\Config\ZMConfig; +use ZM\Config\RuntimePreferences; use ZM\Container\ContainerBindingListener; use ZM\Event\Listener\HttpEventListener; use ZM\Event\Listener\ManagerEventListener; @@ -38,6 +39,8 @@ use ZM\Utils\EasterEgg; /** * 框架入口类 * @since 3.0 + * + * @method static Framework getInstance() */ class Framework { @@ -49,6 +52,11 @@ class Framework /** @var string 版本名称 */ public const VERSION = '3.1.1'; + /** + * @var RuntimePreferences 运行时偏好(环境信息&参数) + */ + public RuntimePreferences $runtime_preferences; + /** @var array 传入的参数 */ protected array $argv; @@ -70,11 +78,9 @@ class Framework /** * 框架初始化文件 - * - * @param array $argv 传入的参数(见 ServerStartCommand) * @throws \Exception */ - public function __construct(array $argv = []) + public function __construct() { // 单例化整个Framework类 if (self::$instance !== null) { @@ -82,17 +88,22 @@ class Framework } self::$instance = $this; - // 初始化必需的args参数,如果没有传入的话,使用默认值 - $this->argv = empty($argv) ? ServerStartCommand::exportOptionArray() : $argv; + $this->runtime_preferences = new RuntimePreferences(); } /** * 初始化框架 * + * @param array $argv 传入的参数(见 ServerStartCommand) + * * @throws \Exception */ - public function init(): Framework + public function init(array $argv = []): Framework { + // TODO: discard argv + // 初始化必需的args参数,如果没有传入的话,使用默认值 + $this->argv = empty($argv) ? ServerStartCommand::exportOptionArray() : $argv; + // 初始化 @OnSetup 事件 $this->initSetupAnnotations(); @@ -251,6 +262,14 @@ class Framework } } + public function bootstrap(): void + { + foreach ($this->bootstrappers as $bootstrapper) { + /* @var Bootstrapper $bootstrapper */ + (new $bootstrapper())->bootstrap($this->runtime_preferences); + } + } + /** * 打印属性表格 */ @@ -260,7 +279,7 @@ class Framework // 打印工作目录 $properties['working_dir'] = WORKING_DIR; // 打印环境信息 - $properties['environment'] = ZMConfig::getInstance()->getEnvironment(); + $properties['environment'] = $this->runtime_preferences->environment(); // 打印驱动 $properties['driver'] = config('global.driver'); // 打印logger显示等级 diff --git a/src/ZM/ZMApplication.php b/src/ZM/ZMApplication.php index a1ed6bba..42e51f52 100644 --- a/src/ZM/ZMApplication.php +++ b/src/ZM/ZMApplication.php @@ -53,6 +53,6 @@ class ZMApplication extends ZMPlugin $meta = new PluginMeta(name: 'native', plugin_type: ZM_PLUGIN_TYPE_NATIVE); $meta->bindEntity($this); PluginManager::addPlugin($meta); - (new Framework($this->args))->init()->start(); + Framework::getInstance()->init($this->args)->start(); } } diff --git a/tests/ZM/Config/ZMConfigTest.php b/tests/ZM/Config/ZMConfigTest.php index fe32a653..9d46859e 100644 --- a/tests/ZM/Config/ZMConfigTest.php +++ b/tests/ZM/Config/ZMConfigTest.php @@ -58,7 +58,7 @@ class ZMConfigTest extends TestCase try { $init_conf = require SOURCE_ROOT_DIR . '/config/config.php'; $init_conf['source']['paths'] = [$this->vfs->url()]; - $config = new ZMConfig('development', $init_conf); + $config = new ZMConfig($init_conf); } catch (ConfigException $e) { $this->fail($e->getMessage()); }