同步更新

This commit is contained in:
2026-01-13 17:27:32 +08:00
parent 45785dd33f
commit 5d6e2fc5e0
38 changed files with 1211 additions and 305 deletions

View File

@@ -0,0 +1,32 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
/**
* @mixin Command
*/
trait CommandCallable
{
/**
* @param class-string<Command> $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);
});
}
}

View File

@@ -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 @@ class Config extends Command
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 = '<?php ' . PHP_EOL . 'return ' . var_export($config, true) . ';';
if (file_put_contents($file, $content)) {
$output->writeln("<info>Succeed!</info>");
} else {
$output->writeln("<error>config build fail</error>");
}
$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();
}
return '<?php ' . PHP_EOL . 'return ' . var_export($config->get(), true) . ';';
}
/**
* 获取默认目录名
* @return array<int, ?string>
*/
private function getDefaultDirs(): array
{
// 包含全局应用配置目录
$dirs = [null];
if ($this->isInstalledMultiApp()) {
$dirs = array_merge($dirs, $this->discoveryMultiAppDirs('config'));
}
return $dirs;
}
}

View File

@@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
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;
}
}

View File

@@ -0,0 +1,40 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
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!');
}
}
}

View File

@@ -11,14 +11,18 @@
namespace think\console\command\optimize;
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
{
use Discoverable;
protected function configure()
{
$this->setName('optimize:route')
@@ -28,18 +32,22 @@ class Route extends Command
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('<info>Succeed!</info>');
$output->info('Succeed!');
}
protected function scanRoute($path, $root, $autoGroup)
@@ -53,7 +61,7 @@ class Route extends Command
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 +81,9 @@ class Route extends Command
// 路由检测
$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 +94,17 @@ class Route extends Command
return '<?php ' . PHP_EOL . 'return ' . var_export($rules, true) . ';';
}
/**
* 获取默认目录名
* @return array<int, ?string>
*/
private function getDefaultDirs(): array
{
// 判断是否使用多应用模式
// 如果使用了则扫描 app 目录
// 否则返回 null让其扫描根目录的 route 目录
return $this->isInstalledMultiApp()
? $this->discoveryMultiAppDirs('route')
: [null];
}
}

View File

@@ -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,71 +39,38 @@ class Schema extends Command
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);
}
if ($table == '*') {
$table = $connection->getTables($dbName);
}
$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';
}
$path = $appPath . 'model';
$list = is_dir($path) ? scandir($path) : [];
foreach ($list as $file) {
if (str_starts_with($file, '.')) {
continue;
$dirs = ((array) $input->getArgument('dir')) ?: $this->getDefaultDirs();
foreach ($dirs as $dir) {
$this->cacheModel($dir);
}
$class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME);
if (!class_exists($class)) {
continue;
}
$this->buildModelSchema($class);
}
} catch (Throwable $e) {
return $output->error($e->getMessage());
}
$output->writeln('<info>Succeed!</info>');
$output->info('Succeed!');
}
protected function buildModelSchema(string $class): 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) {
$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) {
}
}
@@ -106,4 +81,81 @@ class Schema extends Command
$connection->getSchemaInfo("{$dbName}.{$table}", true);
}
}
/**
* 缓存表
*/
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!');
}
if (str_contains($table, '.')) {
[$dbName, $table] = explode('.', $table);
} else {
$dbName = $connection->getConfig('database');
}
if ($table == '*') {
$table = $connection->getTables($dbName);
}
$this->buildDataBaseSchema($connection, (array) $table, $dbName);
}
/**
* 缓存模型
*/
private function cacheModel(?string $dir = null): void
{
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);
}
}
/**
* 获取默认目录名
* @return array<int, ?string>
*/
private function getDefaultDirs(): array
{
// 包含默认的模型目录
$dirs = [null];
if ($this->isInstalledMultiApp()) {
$dirs = array_merge($dirs, $this->discoveryMultiAppDirs('model'));
}
return $dirs;
}
}