From 36d6be6b7f1fa3fccb60470f48706948a9b68e2d Mon Sep 17 00:00:00 2001 From: ChinaMoli Date: Thu, 14 Aug 2025 17:43:33 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8F=91=E7=8E=B0=E5=A4=9A=E5=BA=94=E7=94=A8=E7=9A=84=20route?= =?UTF-8?q?=20=E7=9B=AE=E5=BD=95=E5=B9=B6=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/think/console/command/optimize/Route.php | 60 +++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/src/think/console/command/optimize/Route.php b/src/think/console/command/optimize/Route.php index 563659096d..6ea4546f81 100644 --- a/src/think/console/command/optimize/Route.php +++ b/src/think/console/command/optimize/Route.php @@ -10,12 +10,15 @@ // +---------------------------------------------------------------------- namespace think\console\command\optimize; +use Composer\InstalledVersions; use DirectoryIterator; +use InvalidArgumentException; use think\console\Command; use think\console\Input; use think\console\input\Argument; use think\console\Output; use think\event\RouteLoaded; +use Throwable; class Route extends Command { @@ -28,18 +31,22 @@ protected function configure() protected function execute(Input $input, Output $output) { - $dir = $input->getArgument('dir') ?: ''; + $dirs = ((array) $input->getArgument('dir')) ?: $this->getDefaultDirs(); - $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : ''); - if (!is_dir($path)) { + foreach ($dirs as $dir) { + $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : ''); try { - mkdir($path, 0755, true); - } catch (\Exception $e) { - // 创建失败 + $cache = $this->buildRouteCache($dir); + if (! is_dir($path)) { + mkdir($path, 0755, true); + } + file_put_contents($path . 'route.php', $cache); + } catch (Throwable $e) { + $output->warning($e->getMessage()); } } - file_put_contents($path . 'route.php', $this->buildRouteCache($dir)); - $output->writeln('Succeed!'); + + $output->info('Succeed!'); } protected function scanRoute($path, $root, $autoGroup) @@ -53,7 +60,7 @@ protected function scanRoute($path, $root, $autoGroup) if ($fileinfo->getType() == 'file' && $fileinfo->getExtension() == 'php') { $groupName = str_replace('\\', '/', substr_replace($fileinfo->getPath(), '', 0, strlen($root))); if ($groupName) { - $this->app->route->group($groupName, function() use ($fileinfo) { + $this->app->route->group($groupName, function () use ($fileinfo) { include $fileinfo->getRealPath(); }); } else { @@ -73,6 +80,9 @@ protected function buildRouteCache(?string $dir = null): string // 路由检测 $autoGroup = $this->app->route->config('route_auto_group'); $path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR; + if (! is_dir($path)) { + throw new InvalidArgumentException("{$path} directory does not exist"); + } $this->scanRoute($path, $path, $autoGroup); @@ -83,4 +93,36 @@ protected function buildRouteCache(?string $dir = null): string return ' + */ + private function getDefaultDirs(): array + { + // 判断是否使用多应用模式 + // 如果使用了则扫描 app 目录 + // 否则返回 null,让其扫描根目录的 route 目录 + return InstalledVersions::isInstalled('topthink/think-multi-app') + ? $this->discoveryMultiAppDirs() + : [null]; + } + + /** + * 发现多应用程序目录 + * @return string[] + */ + private function discoveryMultiAppDirs(): array + { + $dirs = []; + foreach (new DirectoryIterator($this->app->getAppPath()) as $item) { + if (! $item->isDir() || $item->isDot()) { + continue; + } + $routePath = $item->getRealPath() . DIRECTORY_SEPARATOR . 'route' . DIRECTORY_SEPARATOR; + if (is_dir($routePath)) { + $dirs[] = $item->getFilename(); + } + } + return $dirs; + } } From f5ebb733e7fa0fdfa9f6091f03647471296d9cda Mon Sep 17 00:00:00 2001 From: ChinaMoli Date: Thu, 14 Aug 2025 18:04:32 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20optimize=20=E5=91=BD?= =?UTF-8?q?=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/think/Console.php | 3 +- src/think/console/command/CommandCallable.php | 32 +++++++++++++++ .../console/command/optimize/Optimize.php | 40 +++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/think/console/command/CommandCallable.php create mode 100644 src/think/console/command/optimize/Optimize.php diff --git a/src/think/Console.php b/src/think/Console.php index c69880b486..2e77c89e14 100644 --- a/src/think/Console.php +++ b/src/think/Console.php @@ -28,6 +28,7 @@ use think\console\command\make\Subscribe; use think\console\command\make\Validate; use think\console\command\optimize\Config; +use think\console\command\optimize\Optimize; use think\console\command\optimize\Route; use think\console\command\optimize\Schema; use think\console\command\RouteList; @@ -70,6 +71,7 @@ class Console 'make:listener' => Listener::class, 'make:service' => Service::class, 'make:subscribe' => Subscribe::class, + 'optimize' => Optimize::class, 'optimize:config' => Config::class, 'optimize:route' => Route::class, 'optimize:schema' => Schema::class, @@ -785,5 +787,4 @@ private function extractAllNamespaces(string $name): array return $namespaces; } - } diff --git a/src/think/console/command/CommandCallable.php b/src/think/console/command/CommandCallable.php new file mode 100644 index 0000000000..9587a51af4 --- /dev/null +++ b/src/think/console/command/CommandCallable.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; + +/** + * @mixin Command + */ +trait CommandCallable +{ + /** + * @param class-string $class + */ + private function callCommand(string $class): Command + { + return tap(app($class, newInstance: true), function ($command) { + $command->setApp($this->app); + $command->run(new Input([]), clone $this->output); + }); + } +} diff --git a/src/think/console/command/optimize/Optimize.php b/src/think/console/command/optimize/Optimize.php new file mode 100644 index 0000000000..d8e06e9b86 --- /dev/null +++ b/src/think/console/command/optimize/Optimize.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\command\CommandCallable; +use think\console\Input; +use think\console\Output; + +class Optimize extends Command +{ + use CommandCallable; + + protected function configure() + { + $this->setName('optimize') + ->setDescription('Build cache.'); + } + + protected function execute(Input $input, Output $output) + { + $commands = [ + Config::class, + Route::class, + Schema::class, + ]; + foreach ($commands as $class) { + $command = $this->callCommand($class); + $this->output->info($command->getName() . ' run succeed!'); + }; + } +} From 24894fc8e5f0226f523222cf7f746653fd4cfbc8 Mon Sep 17 00:00:00 2001 From: ChinaMoli Date: Thu, 14 Aug 2025 18:48:40 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8F=91=E7=8E=B0=E5=A4=9A=E5=BA=94=E7=94=A8=E7=9A=84=20config?= =?UTF-8?q?=20=E7=9B=AE=E5=BD=95=E5=B9=B6=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/think/console/command/optimize/Config.php | 71 ++++++++++++------- .../console/command/optimize/Discoverable.php | 44 ++++++++++++ src/think/console/command/optimize/Route.php | 26 ++----- 3 files changed, 93 insertions(+), 48 deletions(-) create mode 100644 src/think/console/command/optimize/Discoverable.php diff --git a/src/think/console/command/optimize/Config.php b/src/think/console/command/optimize/Config.php index bee164f62b..5d7e1c46ed 100644 --- a/src/think/console/command/optimize/Config.php +++ b/src/think/console/command/optimize/Config.php @@ -10,13 +10,17 @@ // +---------------------------------------------------------------------- namespace think\console\command\optimize; +use InvalidArgumentException; use think\console\Command; use think\console\Input; use think\console\input\Argument; use think\console\Output; +use Throwable; class Config extends Command { + use Discoverable; + protected function configure() { $this->setName('optimize:config') @@ -26,39 +30,54 @@ protected function configure() protected function execute(Input $input, Output $output) { - // 加载配置文件 - $dir = $input->getArgument('dir') ?: ''; - $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : ''); - if (!is_dir($path)) { + $dirs = ((array) $input->getArgument('dir')) ?: $this->getDefaultDirs(); + + foreach ($dirs as $dir) { + $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : ''); try { - mkdir($path, 0755, true); - } catch (\Exception $e) { - // 创建失败 + $cache = $this->buildCache($dir); + if (! is_dir($path)) { + mkdir($path, 0755, true); + } + file_put_contents($path . 'config.php', $cache); + } catch (Throwable $e) { + $output->warning($e->getMessage()); } } - $file = $path . 'config.php'; - $config = $this->loadConfig($dir); - $content = 'writeln("Succeed!"); - } else { - $output->writeln("config build fail"); - } + + $output->info('Succeed!'); } - public function loadConfig($dir = '') + private function buildCache(?string $dir = null): string { - $configPath = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'config' . DIRECTORY_SEPARATOR; - $files = []; - - if (is_dir($configPath)) { - $files = glob($configPath . '*' . $this->app->getConfigExt()); + $path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'config' . DIRECTORY_SEPARATOR; + if (! is_dir($path)) { + throw new InvalidArgumentException("{$path} directory does not exist"); } - foreach ($files as $file) { - $this->app->config->load($file, pathinfo($file, PATHINFO_FILENAME)); + // 使用 clone 防止多应用配置污染 + $config = clone $this->app->config; + if (is_dir($path)) { + $files = glob($path . '*' . $this->app->getConfigExt()); + foreach ($files as $file) { + $config->load($file, pathinfo($file, PATHINFO_FILENAME)); + } } - return $this->app->config->get(); - } -} \ No newline at end of file + return 'get(), true) . ';'; + } + + /** + * 获取默认目录名 + * @return array + */ + private function getDefaultDirs(): array + { + // 包含全局应用配置目录 + $dirs = [null]; + if ($this->isInstalledMultiApp()) { + $dirs = array_merge($dirs, $this->discoveryMultiAppDirs('config')); + } + return $dirs; + } +} diff --git a/src/think/console/command/optimize/Discoverable.php b/src/think/console/command/optimize/Discoverable.php new file mode 100644 index 0000000000..ad014215de --- /dev/null +++ b/src/think/console/command/optimize/Discoverable.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use Composer\InstalledVersions; +use DirectoryIterator; + +trait Discoverable +{ + /** + * 判断是否安装 topthink/think-multi-app + */ + private function isInstalledMultiApp(): bool + { + return InstalledVersions::isInstalled('topthink/think-multi-app'); + } + + /** + * 发现多应用程序目录 + * @return string[] + */ + private function discoveryMultiAppDirs(string $directoryName): array + { + $dirs = []; + foreach (new DirectoryIterator($this->app->getAppPath()) as $item) { + if (! $item->isDir() || $item->isDot()) { + continue; + } + $path = $item->getRealPath() . DIRECTORY_SEPARATOR . $directoryName . DIRECTORY_SEPARATOR; + if (is_dir($path)) { + $dirs[] = $item->getFilename(); + } + } + return $dirs; + } +} diff --git a/src/think/console/command/optimize/Route.php b/src/think/console/command/optimize/Route.php index 6ea4546f81..8d192a5122 100644 --- a/src/think/console/command/optimize/Route.php +++ b/src/think/console/command/optimize/Route.php @@ -10,7 +10,6 @@ // +---------------------------------------------------------------------- namespace think\console\command\optimize; -use Composer\InstalledVersions; use DirectoryIterator; use InvalidArgumentException; use think\console\Command; @@ -22,6 +21,8 @@ class Route extends Command { + use Discoverable; + protected function configure() { $this->setName('optimize:route') @@ -102,27 +103,8 @@ private function getDefaultDirs(): array // 判断是否使用多应用模式 // 如果使用了则扫描 app 目录 // 否则返回 null,让其扫描根目录的 route 目录 - return InstalledVersions::isInstalled('topthink/think-multi-app') - ? $this->discoveryMultiAppDirs() + return $this->isInstalledMultiApp() + ? $this->discoveryMultiAppDirs('route') : [null]; } - - /** - * 发现多应用程序目录 - * @return string[] - */ - private function discoveryMultiAppDirs(): array - { - $dirs = []; - foreach (new DirectoryIterator($this->app->getAppPath()) as $item) { - if (! $item->isDir() || $item->isDot()) { - continue; - } - $routePath = $item->getRealPath() . DIRECTORY_SEPARATOR . 'route' . DIRECTORY_SEPARATOR; - if (is_dir($routePath)) { - $dirs[] = $item->getFilename(); - } - } - return $dirs; - } } From 92fe7ec3c7d6cf3d22d252dd22abaeaf130b096b Mon Sep 17 00:00:00 2001 From: ChinaMoli Date: Fri, 15 Aug 2025 09:50:22 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8F=91=E7=8E=B0=E5=A4=9A=E5=BA=94=E7=94=A8=E7=9A=84=20model?= =?UTF-8?q?=20=E7=9B=AE=E5=BD=95=E5=B9=B6=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/think/console/command/optimize/Schema.php | 160 ++++++++++++------ 1 file changed, 106 insertions(+), 54 deletions(-) diff --git a/src/think/console/command/optimize/Schema.php b/src/think/console/command/optimize/Schema.php index 3ee3f42fed..52baa0af7b 100644 --- a/src/think/console/command/optimize/Schema.php +++ b/src/think/console/command/optimize/Schema.php @@ -11,15 +11,23 @@ namespace think\console\command\optimize; use Exception; +use InvalidArgumentException; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use ReflectionClass; +use SplFileInfo; use think\console\Command; use think\console\Input; use think\console\input\Argument; use think\console\input\Option; use think\console\Output; use think\db\PDOConnection; +use Throwable; class Schema extends Command { + use Discoverable; + protected function configure() { $this->setName('optimize:schema') @@ -31,79 +39,123 @@ protected function configure() protected function execute(Input $input, Output $output) { - $dir = $input->getArgument('dir') ?: ''; - - if ($input->hasOption('table')) { - $connection = $this->app->db->connect($input->getOption('connection')); - if (!$connection instanceof PDOConnection) { - $output->error("only PDO connection support schema cache!"); - return; - } - $table = $input->getOption('table'); - if (!str_contains($table, '.')) { - $dbName = $connection->getConfig('database'); + try { + if ($table = $input->hasOption('table')) { + $this->cacheTable($table, $input->getOption('connection')); } else { - [$dbName, $table] = explode('.', $table); + $dirs = ((array) $input->getArgument('dir')) ?: $this->getDefaultDirs(); + foreach ($dirs as $dir) { + $this->cacheModel($dir); + } } + } catch (Throwable $e) { + return $output->error($e->getMessage()); + } - if ($table == '*') { - $table = $connection->getTables($dbName); - } + $output->info('Succeed!'); + } - $this->buildDataBaseSchema($connection, (array) $table, $dbName); - } else { - if ($dir) { - $appPath = $this->app->getBasePath() . $dir . DIRECTORY_SEPARATOR; - $namespace = 'app\\' . $dir; - } else { - $appPath = $this->app->getBasePath(); - $namespace = 'app'; + protected function buildModelSchema(string $class): void + { + $reflect = new ReflectionClass($class); + if ($reflect->isAbstract() || ! $reflect->isSubclassOf('\think\Model')) { + return; + } + try { + /** @var \think\Model $model */ + $model = new $class; + $connection = $model->db()->getConnection(); + if ($connection instanceof PDOConnection) { + $table = $model->getTable(); + //预读字段信息 + $connection->getSchemaInfo($table, true); } + } catch (Exception $e) { + } + } - $path = $appPath . 'model'; - $list = is_dir($path) ? scandir($path) : []; + protected function buildDataBaseSchema(PDOConnection $connection, array $tables, string $dbName): void + { + foreach ($tables as $table) { + //预读字段信息 + $connection->getSchemaInfo("{$dbName}.{$table}", true); + } + } - foreach ($list as $file) { - if (str_starts_with($file, '.')) { - continue; - } - $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); - - if (!class_exists($class)) { - continue; - } + /** + * 缓存表 + */ + private function cacheTable(string $table, ?string $connectionName = null): void + { + $connection = $this->app->db->connect($connectionName); + if (! $connection instanceof PDOConnection) { + throw new InvalidArgumentException('only PDO connection support schema cache!'); + } - $this->buildModelSchema($class); - } + if (str_contains($table, '.')) { + [$dbName, $table] = explode('.', $table); + } else { + $dbName = $connection->getConfig('database'); } - $output->writeln('Succeed!'); + if ($table == '*') { + $table = $connection->getTables($dbName); + } + + $this->buildDataBaseSchema($connection, (array) $table, $dbName); } - protected function buildModelSchema(string $class): void + /** + * 缓存模型 + */ + private function cacheModel(string $dir = null): void { - $reflect = new \ReflectionClass($class); - if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { - try { - /** @var \think\Model $model */ - $model = new $class; - $connection = $model->db()->getConnection(); - if ($connection instanceof PDOConnection) { - $table = $model->getTable(); - //预读字段信息 - $connection->getSchemaInfo($table, true); - } - } catch (Exception $e) { + if ($dir) { + $modelDir = $this->app->getAppPath() . $dir . DIRECTORY_SEPARATOR . 'model' . DIRECTORY_SEPARATOR; + $namespace = 'app\\' . $dir; + } else { + $modelDir = $this->app->getAppPath() . 'model' . DIRECTORY_SEPARATOR; + $namespace = 'app'; + } + if (! is_dir($modelDir)) { + throw new InvalidArgumentException("{$modelDir} directory does not exist"); + } + + /** @var SplFileInfo[] $iterator */ + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($modelDir, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::SELF_FIRST + ); + + foreach ($iterator as $fileInfo) { + $relativePath = substr($fileInfo->getRealPath(), strlen($modelDir)); + if (! str_ends_with($relativePath, '.php')) { + continue; + } + // 去除 .php + $relativePath = substr($relativePath, 0, -4); + + $class = '\\' . $namespace . '\\model\\' . str_replace('/', '\\', $relativePath); + if (! class_exists($class)) { + continue; } + + $this->buildModelSchema($class); } } - protected function buildDataBaseSchema(PDOConnection $connection, array $tables, string $dbName): void + /** + * 获取默认目录名 + * @return array + */ + private function getDefaultDirs(): array { - foreach ($tables as $table) { - //预读字段信息 - $connection->getSchemaInfo("{$dbName}.{$table}", true); + // 包含默认的模型目录 + $dirs = [null]; + if ($this->isInstalledMultiApp()) { + $dirs = array_merge($dirs, $this->discoveryMultiAppDirs('model')); } + return $dirs; } } From 0650be598a092946f42c16fd86cc076870db0bae Mon Sep 17 00:00:00 2001 From: ChinaMoli Date: Fri, 15 Aug 2025 10:08:49 +0800 Subject: [PATCH 5/5] CS Fix --- src/think/console/command/optimize/Optimize.php | 2 +- src/think/console/command/optimize/Route.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/think/console/command/optimize/Optimize.php b/src/think/console/command/optimize/Optimize.php index d8e06e9b86..d7a8517966 100644 --- a/src/think/console/command/optimize/Optimize.php +++ b/src/think/console/command/optimize/Optimize.php @@ -35,6 +35,6 @@ protected function execute(Input $input, Output $output) foreach ($commands as $class) { $command = $this->callCommand($class); $this->output->info($command->getName() . ' run succeed!'); - }; + } } } diff --git a/src/think/console/command/optimize/Route.php b/src/think/console/command/optimize/Route.php index 8d192a5122..073f608131 100644 --- a/src/think/console/command/optimize/Route.php +++ b/src/think/console/command/optimize/Route.php @@ -61,7 +61,7 @@ protected function scanRoute($path, $root, $autoGroup) if ($fileinfo->getType() == 'file' && $fileinfo->getExtension() == 'php') { $groupName = str_replace('\\', '/', substr_replace($fileinfo->getPath(), '', 0, strlen($root))); if ($groupName) { - $this->app->route->group($groupName, function () use ($fileinfo) { + $this->app->route->group($groupName, function () use ($fileinfo) { include $fileinfo->getRealPath(); }); } else {