From 5d6e2fc5e0be4f20a345e4f00cba1a122543d064 Mon Sep 17 00:00:00 2001 From: "X14XA\\shengli" Date: Tue, 13 Jan 2026 17:27:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .php-cs-fixer.dist.php | 48 ++++ composer.json | 9 +- src/helper.php | 6 +- src/think/App.php | 11 +- src/think/Console.php | 3 +- src/think/Cookie.php | 2 +- src/think/Event.php | 5 +- src/think/Request.php | 99 +------ src/think/Response.php | 6 +- src/think/cache/Driver.php | 6 +- src/think/cache/TagSet.php | 6 +- src/think/cache/driver/Memcached.php | 6 +- src/think/cache/driver/Redis.php | 6 +- src/think/cache/driver/Wincache.php | 6 +- src/think/console/command/CommandCallable.php | 32 +++ src/think/console/command/optimize/Config.php | 69 +++-- .../console/command/optimize/Discoverable.php | 44 ++++ .../console/command/optimize/Optimize.php | 40 +++ src/think/console/command/optimize/Route.php | 42 ++- src/think/console/command/optimize/Schema.php | 162 ++++++++---- src/think/console/output/Descriptor.php | 2 +- src/think/console/output/driver/Console.php | 8 +- src/think/contract/ModelRelationInterface.php | 20 +- src/think/exception/ErrorException.php | 12 +- src/think/exception/Handle.php | 2 +- src/think/facade/App.php | 2 +- src/think/facade/Request.php | 7 +- src/think/initializer/Error.php | 8 +- src/think/log/driver/File.php | 3 + src/think/response/File.php | 2 +- src/think/route/Dispatch.php | 124 +++++---- src/think/route/Rule.php | 22 +- src/think/route/Url.php | 4 +- src/think/session/driver/Cache.php | 2 +- src/think/traits/DomainHandler.php | 243 ++++++++++++++++++ src/think/traits/HttpMethodHandler.php | 213 +++++++++++++++ src/think/traits/UrlHandler.php | 233 +++++++++++++++++ 38 files changed, 1211 insertions(+), 305 deletions(-) create mode 100644 .php-cs-fixer.dist.php create mode 100644 src/think/console/command/CommandCallable.php create mode 100644 src/think/console/command/optimize/Discoverable.php create mode 100644 src/think/console/command/optimize/Optimize.php create mode 100644 src/think/traits/DomainHandler.php create mode 100644 src/think/traits/HttpMethodHandler.php create mode 100644 src/think/traits/UrlHandler.php diff --git a/.gitignore b/.gitignore index 1accf15..085a103 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ Thumbs.db /.buildpath /.project .phpunit.result.cache +.php-cs-fixer.cache \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..7ae3c1b --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,48 @@ +in(__DIR__) +; + +return (new Config()) + ->setRules([ + '@PhpCsFixer' => true, + '@PHP8x0Migration' => true, + 'binary_operator_spaces' => [ + 'default' => 'align_single_space_minimal', + 'operators' => [ + '=>' => 'align_single_space_minimal_by_scope', + ], + ], + 'blank_line_before_statement' => [ + 'statements' => [ + 'continue', + 'declare', + 'return', + 'throw', + 'try', + ], + ], + 'concat_space' => [ + 'spacing' => 'one', + ], + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => false, + 'import_functions' => false, + ], + 'ordered_class_elements' => [ + 'order' => [ + 'use_trait', + 'case', + 'constant', + 'property', + 'construct', + ], + ], + ]) + ->setFinder($finder) +; diff --git a/composer.json b/composer.json index 11cdb10..2fe5398 100644 --- a/composer.json +++ b/composer.json @@ -25,17 +25,18 @@ "ext-ctype": "*", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.0|^2.0", "topthink/think-orm": "^3.0|^4.0", "topthink/think-helper": "^3.1", "fixtopthink/think-container": "^3.0", "topthink/think-validate": "^3.0" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.92", + "guzzlehttp/psr7": "^2.1.0", "mikey179/vfsstream": "^1.6", "mockery/mockery": "^1.2", - "phpunit/phpunit": "^9.5", - "guzzlehttp/psr7": "^2.1.0" + "phpunit/phpunit": "^9.5" }, "autoload": { "files": [], @@ -54,6 +55,6 @@ "sort-packages": true }, "scripts": { - "php-cs-fixer": "php-cs-fixer fix src/ --rules=@PER-CS2.0 --dry-run --diff" + "php-cs-fixer": "php-cs-fixer fix src/ --dry-run --diff" } } diff --git a/src/helper.php b/src/helper.php index 7de5839..dfeb099 100644 --- a/src/helper.php +++ b/src/helper.php @@ -42,9 +42,9 @@ use think\validate\ValidateRuleSet; if (!function_exists('abort')) { /** * 抛出HTTP异常 - * @param integer|Response $code 状态码 或者 Response对象实例 - * @param string $message 错误信息 - * @param array $header 参数 + * @param int|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 */ function abort($code, string $message = '', array $header = []) { diff --git a/src/think/App.php b/src/think/App.php index 3b3a014..7d86543 100644 --- a/src/think/App.php +++ b/src/think/App.php @@ -43,7 +43,7 @@ class App extends Container * 核心框架版本 * @deprecated 已经废弃 请改用version()方法 */ - const VERSION = '8.0.0'; + public const VERSION = '8.0.0'; /** * 应用调试模式 @@ -71,7 +71,7 @@ class App extends Container /** * 应用内存初始占用 - * @var integer + * @var int */ protected $beginMem; @@ -427,7 +427,7 @@ class App extends Container /** * 获取应用初始内存占用 * @access public - * @return integer + * @return int */ public function getBeginMem(): int { @@ -458,9 +458,8 @@ class App extends Container public function initialize() { $this->initialized = true; - - $this->beginTime = microtime(true); - $this->beginMem = memory_get_usage(); + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); // 加载环境变量 if ($this->baseEnvName) { diff --git a/src/think/Console.php b/src/think/Console.php index c69880b..2e77c89 100644 --- a/src/think/Console.php +++ b/src/think/Console.php @@ -28,6 +28,7 @@ use think\console\command\make\Service; 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 @@ class Console return $namespaces; } - } diff --git a/src/think/Cookie.php b/src/think/Cookie.php index 585d812..5c66ed9 100644 --- a/src/think/Cookie.php +++ b/src/think/Cookie.php @@ -135,7 +135,7 @@ class Cookie * @access public * @param string $name cookie名称 * @param string $value cookie值 - * @param mixed $option 可选参数 可能会是 null|integer|string + * @param mixed $option 可选参数 可能会是 null|int|string * @return void */ public function forever(string $name, string $value = '', $option = null): void diff --git a/src/think/Event.php b/src/think/Event.php index 2169e22..1f81c6f 100644 --- a/src/think/Event.php +++ b/src/think/Event.php @@ -191,9 +191,12 @@ class Event if (empty($prefix) && $reflect->hasProperty('eventPrefix')) { $reflectProperty = $reflect->getProperty('eventPrefix'); - if(PHP_VERSION_ID < 80100) { + + if (PHP_VERSION_ID < 80100) { + // 8.0 版本时调用 $reflectProperty->setAccessible(true); } + $prefix = $reflectProperty->getValue($observer); } diff --git a/src/think/Request.php b/src/think/Request.php index 1a1e373..4588a4d 100644 --- a/src/think/Request.php +++ b/src/think/Request.php @@ -17,6 +17,9 @@ use ArrayAccess; use think\facade\Lang; use think\file\UploadedFile; use think\route\Rule; +use think\traits\UrlHandler; +use think\traits\DomainHandler; +use think\traits\HttpMethodHandler; /** * 请求管理类 @@ -24,6 +27,7 @@ use think\route\Rule; */ class Request implements ArrayAccess { + use UrlHandler, DomainHandler, HttpMethodHandler; /** * 兼容PATH_INFO获取 * @var array @@ -36,36 +40,7 @@ class Request implements ArrayAccess */ protected $varPathinfo = 's'; - /** - * 请求类型 - * @var string - */ - protected $varMethod = '_method'; - - /** - * 表单ajax伪装变量 - * @var string - */ - protected $varAjax = '_ajax'; - - /** - * 表单pjax伪装变量 - * @var string - */ - protected $varPjax = '_pjax'; - - /** - * 域名根 - * @var string - */ - protected $rootDomain = ''; - - /** - * 特殊域名根标识 用于识别com.cn org.cn 这种 - * @var array - */ - protected $domainSpecialSuffix = ['com', 'net', 'org', 'edu', 'gov', 'mil', 'co', 'info']; - + /** * HTTPS代理标识 * @var string @@ -84,60 +59,8 @@ class Request implements ArrayAccess */ protected $proxyServerIpHeader = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP']; - /** - * 请求类型 - * @var string - */ - protected $method; - - /** - * 域名(含协议及端口) - * @var string - */ - protected $domain; - - /** - * HOST(含端口) - * @var string - */ - protected $host; - - /** - * 子域名 - * @var string - */ - protected $subDomain; - - /** - * 泛域名 - * @var string - */ - protected $panDomain; - - /** - * 当前URL地址 - * @var string - */ - protected $url; - - /** - * 基础URL - * @var string - */ - protected $baseUrl; - - /** - * 当前执行的文件 - * @var string - */ - protected $baseFile; - - /** - * 访问的ROOT地址 - * @var string - */ - protected $root; - + + /** * pathinfo * @var string @@ -435,7 +358,7 @@ class Request implements ArrayAccess */ public function subDomain(): string { - if (is_null($this->subDomain)) { + if ($this->subDomain === '') { // 获取当前主域名 $rootDomain = $this->rootDomain(); @@ -681,7 +604,7 @@ class Request implements ArrayAccess * 获取当前请求的时间 * @access public * @param bool $float 是否使用浮点类型 - * @return integer|float + * @return int|float */ public function time(bool $float = false) { @@ -1682,7 +1605,7 @@ class Request implements ArrayAccess * @param string $ip IP地址 * @param string $type IP地址类型 (ipv4, ipv6) * - * @return boolean + * @return bool */ public function isValidIP(string $ip, string $type = ''): bool { @@ -2191,7 +2114,7 @@ class Request implements ArrayAccess * 检测中间传递数据的值 * @access public * @param string $name 名称 - * @return boolean + * @return bool */ public function __isset(string $name): bool { diff --git a/src/think/Response.php b/src/think/Response.php index c0387e7..9cd3153 100644 --- a/src/think/Response.php +++ b/src/think/Response.php @@ -38,7 +38,7 @@ abstract class Response /** * 状态码 - * @var integer + * @var int */ protected $code = 200; @@ -277,7 +277,7 @@ abstract class Response /** * 发送HTTP状态 * @access public - * @param integer $code 状态码 + * @param int $code 状态码 * @return $this */ public function code(int $code) @@ -406,7 +406,7 @@ abstract class Response /** * 获取状态码 * @access public - * @return integer + * @return int */ public function getCode(): int { diff --git a/src/think/cache/Driver.php b/src/think/cache/Driver.php index c37850e..248260a 100644 --- a/src/think/cache/Driver.php +++ b/src/think/cache/Driver.php @@ -36,13 +36,13 @@ abstract class Driver implements CacheHandlerInterface /** * 缓存读取次数 - * @var integer + * @var int */ protected $readTimes = 0; /** * 缓存写入次数 - * @var integer + * @var int */ protected $writeTimes = 0; @@ -61,7 +61,7 @@ abstract class Driver implements CacheHandlerInterface /** * 获取有效期 * @access protected - * @param integer|DateInterval|DateTimeInterface $expire 有效期 + * @param int|DateInterval|DateTimeInterface $expire 有效期 * @return int */ protected function getExpireTime(int | DateInterval | DateTimeInterface $expire): int diff --git a/src/think/cache/TagSet.php b/src/think/cache/TagSet.php index 5c42c1b..bd8aee4 100644 --- a/src/think/cache/TagSet.php +++ b/src/think/cache/TagSet.php @@ -33,9 +33,9 @@ class TagSet /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateInterval|DateTimeInterface $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null): bool diff --git a/src/think/cache/driver/Memcached.php b/src/think/cache/driver/Memcached.php index 5d519ae..6901f44 100644 --- a/src/think/cache/driver/Memcached.php +++ b/src/think/cache/driver/Memcached.php @@ -120,9 +120,9 @@ class Memcached extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateInterval|DateTimeInterface $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null): bool diff --git a/src/think/cache/driver/Redis.php b/src/think/cache/driver/Redis.php index fd0abd4..cc3a8d3 100644 --- a/src/think/cache/driver/Redis.php +++ b/src/think/cache/driver/Redis.php @@ -133,9 +133,9 @@ class Redis extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateInterval|DateTimeInterface $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null): bool diff --git a/src/think/cache/driver/Wincache.php b/src/think/cache/driver/Wincache.php index 255e006..4a7ee28 100644 --- a/src/think/cache/driver/Wincache.php +++ b/src/think/cache/driver/Wincache.php @@ -86,9 +86,9 @@ class Wincache extends Driver /** * 写入缓存 * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒) + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateInterval|DateTimeInterface $expire 有效时间(秒) * @return bool */ public function set($name, $value, $expire = null): bool diff --git a/src/think/console/command/CommandCallable.php b/src/think/console/command/CommandCallable.php new file mode 100644 index 0000000..9587a51 --- /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/Config.php b/src/think/console/command/optimize/Config.php index bee164f..2766d9b 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 @@ 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 = '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(); - } + return 'get(), true) . ';'; + } + + /** + * 获取默认目录名 + * @return array + */ + private function getDefaultDirs(): array + { + // 包含全局应用配置目录 + $dirs = [null]; + if ($this->isInstalledMultiApp()) { + $dirs = array_merge($dirs, $this->discoveryMultiAppDirs('config')); + } + return $dirs; + } } \ No newline at end of file diff --git a/src/think/console/command/optimize/Discoverable.php b/src/think/console/command/optimize/Discoverable.php new file mode 100644 index 0000000..ad01421 --- /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/Optimize.php b/src/think/console/command/optimize/Optimize.php new file mode 100644 index 0000000..d7a8517 --- /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!'); + } + } +} diff --git a/src/think/console/command/optimize/Route.php b/src/think/console/command/optimize/Route.php index 5636590..073f608 100644 --- a/src/think/console/command/optimize/Route.php +++ b/src/think/console/command/optimize/Route.php @@ -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('Succeed!'); + + $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 ' + */ + private function getDefaultDirs(): array + { + // 判断是否使用多应用模式 + // 如果使用了则扫描 app 目录 + // 否则返回 null,让其扫描根目录的 route 目录 + return $this->isInstalledMultiApp() + ? $this->discoveryMultiAppDirs('route') + : [null]; + } } diff --git a/src/think/console/command/optimize/Schema.php b/src/think/console/command/optimize/Schema.php index 3ee3f42..3c1f908 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,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('Succeed!'); + $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 + */ + private function getDefaultDirs(): array + { + // 包含默认的模型目录 + $dirs = [null]; + if ($this->isInstalledMultiApp()) { + $dirs = array_merge($dirs, $this->discoveryMultiAppDirs('model')); + } + return $dirs; + } } diff --git a/src/think/console/output/Descriptor.php b/src/think/console/output/Descriptor.php index 2985ddb..0c7ecfe 100644 --- a/src/think/console/output/Descriptor.php +++ b/src/think/console/output/Descriptor.php @@ -212,7 +212,7 @@ class Descriptor */ protected function describeConsole(Console $console, array $options = []) { - $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $describedNamespace = $options['namespace'] ?? null; $description = new ConsoleDescription($console, $describedNamespace); if (isset($options['raw_text']) && $options['raw_text']) { diff --git a/src/think/console/output/driver/Console.php b/src/think/console/output/driver/Console.php index 5c0a9ef..a71ef74 100644 --- a/src/think/console/output/driver/Console.php +++ b/src/think/console/output/driver/Console.php @@ -120,11 +120,11 @@ class Console ]); for ($i = 0, $count = count($trace); $i < $count; ++$i) { - $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; - $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $class = $trace[$i]['class'] ?? ''; + $type = $trace[$i]['type'] ?? ''; $function = $trace[$i]['function']; - $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; - $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + $file = $trace[$i]['file'] ?? 'n/a'; + $line = $trace[$i]['line'] ?? 'n/a'; $this->write(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr); } diff --git a/src/think/contract/ModelRelationInterface.php b/src/think/contract/ModelRelationInterface.php index 66aa204..9cffee3 100644 --- a/src/think/contract/ModelRelationInterface.php +++ b/src/think/contract/ModelRelationInterface.php @@ -55,12 +55,12 @@ interface ModelRelationInterface /** * 关联统计 * @access public - * @param Model $result 模型对象 - * @param Closure $closure 闭包 - * @param string $aggregate 聚合查询方法 - * @param string $field 字段 - * @param string $name 统计字段别名 - * @return integer + * @param Model $result 模型对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string|null $name 统计字段别名 + * @return int */ public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', ?string &$name = null); @@ -78,10 +78,10 @@ interface ModelRelationInterface /** * 根据关联条件查询当前模型 * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 + * @param string $operator 比较操作符 + * @param int $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', ?Query $query = null): Query; diff --git a/src/think/exception/ErrorException.php b/src/think/exception/ErrorException.php index faf1038..de0753e 100644 --- a/src/think/exception/ErrorException.php +++ b/src/think/exception/ErrorException.php @@ -24,17 +24,17 @@ class ErrorException extends Exception { /** * 用于保存错误级别 - * @var integer + * @var int */ protected $severity; /** * 错误异常构造函数 * @access public - * @param integer $severity 错误级别 - * @param string $message 错误详细信息 - * @param string $file 出错文件路径 - * @param integer $line 出错行号 + * @param int $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param int $line 出错行号 */ public function __construct(int $severity, string $message, string $file, int $line) { @@ -48,7 +48,7 @@ class ErrorException extends Exception /** * 获取错误级别 * @access public - * @return integer 错误级别 + * @return int 错误级别 */ final public function getSeverity() { diff --git a/src/think/exception/Handle.php b/src/think/exception/Handle.php index c9db836..0c75094 100644 --- a/src/think/exception/Handle.php +++ b/src/think/exception/Handle.php @@ -270,7 +270,7 @@ class Handle * ErrorException则使用错误级别作为错误编码 * @access protected * @param Throwable $exception - * @return integer 错误编码 + * @return int 错误编码 */ protected function getCode(Throwable $exception) { diff --git a/src/think/facade/App.php b/src/think/facade/App.php index f99d1f4..f15061d 100644 --- a/src/think/facade/App.php +++ b/src/think/facade/App.php @@ -36,7 +36,7 @@ use think\Facade; * @method static string getConfigPath() 获取应用配置目录 * @method static string getConfigExt() 获取配置后缀 * @method static float getBeginTime() 获取应用开启时间 - * @method static integer getBeginMem() 获取应用初始内存占用 + * @method static int getBeginMem() 获取应用初始内存占用 * @method static \think\App initialize() 初始化应用 * @method static bool initialized() 是否初始化过 * @method static void loadLangPack(string $langset) 加载语言包 diff --git a/src/think/facade/Request.php b/src/think/facade/Request.php index afc60df..9b0f2fe 100644 --- a/src/think/facade/Request.php +++ b/src/think/facade/Request.php @@ -1,4 +1,5 @@ $this->config['max_files']) { set_error_handler(fn() => null); + usort($files, function($a, $b) { + return filemtime($a) - filemtime($b); + }); unlink($files[0]); restore_error_handler(); } diff --git a/src/think/response/File.php b/src/think/response/File.php index 85d5134..2584e92 100644 --- a/src/think/response/File.php +++ b/src/think/response/File.php @@ -91,7 +91,7 @@ class File extends Response /** * 设置有效期 * @access public - * @param integer $expire 有效期 + * @param int $expire 有效期 * @return $this */ public function expire(int $expire) diff --git a/src/think/route/Dispatch.php b/src/think/route/Dispatch.php index 59c1266..7d89303 100644 --- a/src/think/route/Dispatch.php +++ b/src/think/route/Dispatch.php @@ -8,7 +8,7 @@ // +---------------------------------------------------------------------- // | Author: liu21st // +---------------------------------------------------------------------- -declare (strict_types=1); +declare (strict_types = 1); namespace think\route; @@ -28,9 +28,20 @@ use think\Validate; */ abstract class Dispatch { + /** + * 控制器名 + * @var string + */ + protected $controller; + + /** + * 操作名 + * @var string + */ + protected $actionName; + /** * 应用对象 - * * @var App */ protected $app; @@ -41,7 +52,6 @@ abstract class Dispatch /** * 执行路由调度 - * * @access public * @return Response */ @@ -53,23 +63,23 @@ abstract class Dispatch protected function autoResponse($data): Response { - if($data instanceof Response) { + if ($data instanceof Response) { $response = $data; - } elseif($data instanceof ResponseInterface) { - $response = Response::create((string)$data->getBody(), 'html', $data->getStatusCode()); + } elseif ($data instanceof ResponseInterface) { + $response = Response::create((string) $data->getBody(), 'html', $data->getStatusCode()); foreach ($data->getHeaders() as $header => $values) { $response->header([$header => implode(", ", $values)]); } - } elseif(!is_null($data)) { + } elseif (!is_null($data)) { // 默认自动识别响应输出类型 - $type = $this->request->isJson() ? 'json' : 'html'; + $type = $this->request->isJson() ? 'json' : 'html'; $response = Response::create($data, $type); } else { $data = ob_get_clean(); - $content = false === $data ? '' : $data; - $status = '' === $content && $this->request->isJson() ? 204 : 200; + $content = false === $data ? '' : $data; + $status = '' === $content && $this->request->isJson() ? 204 : 200; $response = Response::create($content, 'html', $status); } @@ -78,7 +88,6 @@ abstract class Dispatch /** * 检查路由后置操作 - * * @access protected * @return void */ @@ -87,8 +96,8 @@ abstract class Dispatch $option = $this->option; // 添加中间件 - if(!empty($option['middleware'])) { - if(isset($option['without_middleware'])) { + if (!empty($option['middleware'])) { + if (isset($option['without_middleware'])) { $middleware = !empty($option['without_middleware']) ? array_diff($option['middleware'], $option['without_middleware']) : []; } else { $middleware = $option['middleware']; @@ -96,12 +105,12 @@ abstract class Dispatch $this->app->middleware->import($middleware, 'route'); } - if(!empty($option['append'])) { + if (!empty($option['append'])) { $this->param = array_merge($this->param, $option['append']); } // 绑定模型数据 - if(!empty($option['model'])) { + if (!empty($option['model'])) { $this->createBindModel($option['model'], $this->param); } @@ -112,14 +121,13 @@ abstract class Dispatch $this->request->setRoute($this->param); // 数据自动验证 - if(isset($option['validate'])) { + if (isset($option['validate'])) { $this->autoValidate($option['validate']); } } /** * 获取操作的绑定参数 - * * @access protected * @return array */ @@ -135,11 +143,9 @@ abstract class Dispatch /** * 执行中间件调度 - * * @access public - * - * @param object $controller 控制器实例 - * + * @param object $instance 控制器实例 + * @param string $action * @return void */ protected function responseWithMiddlewarePipeline($instance, $action) @@ -153,20 +159,20 @@ abstract class Dispatch $suffix = $this->rule->config('action_suffix'); $action = $action . $suffix; - if(is_callable([$instance, $action])) { + if (is_callable([$instance, $action])) { $vars = $this->getActionBindVars(); try { $reflect = new ReflectionMethod($instance, $action); // 严格获取当前操作方法名 $actionName = $reflect->getName(); - if($suffix) { + if ($suffix) { $actionName = substr($actionName, 0, -strlen($suffix)); } $this->request->setAction($actionName); } catch (ReflectionException $e) { $reflect = new ReflectionMethod($instance, '__call'); - $vars = [$action, $vars]; + $vars = [$action, $vars]; $this->request->setAction($action); } } else { @@ -182,47 +188,45 @@ abstract class Dispatch /** * 使用反射机制注册控制器中间件 - * * @access public - * * @param object $controller 控制器实例 - * * @return void */ protected function registerControllerMiddleware($controller): void { $class = new ReflectionClass($controller); - if($class->hasProperty('middleware')) { + if ($class->hasProperty('middleware')) { $reflectionProperty = $class->getProperty('middleware'); - if(PHP_VERSION_ID < 80100) { + + if (PHP_VERSION_ID < 80100) { $reflectionProperty->setAccessible(true); } $middlewares = $reflectionProperty->getValue($controller); - $action = $this->request->action(true); + $action = $this->request->action(true); foreach ($middlewares as $key => $val) { - if(!is_int($key)) { + if (!is_int($key)) { $middleware = $key; - $options = $val; - } elseif(isset($val['middleware'])) { + $options = $val; + } elseif (isset($val['middleware'])) { $middleware = $val['middleware']; - $options = $val['options'] ?? []; + $options = $val['options'] ?? []; } else { $middleware = $val; - $options = []; + $options = []; } - if(isset($options['only']) && !in_array($action, $this->parseActions($options['only']))) { + if (isset($options['only']) && !in_array($action, $this->parseActions($options['only']))) { continue; - } elseif(isset($options['except']) && in_array($action, $this->parseActions($options['except']))) { + } elseif (isset($options['except']) && in_array($action, $this->parseActions($options['except']))) { continue; } - if(is_string($middleware) && str_contains($middleware, ':')) { + if (is_string($middleware) && str_contains($middleware, ':')) { $middleware = explode(':', $middleware); - if(count($middleware) > 1) { + if (count($middleware) > 1) { $middleware = [$middleware[0], array_slice($middleware, 1)]; } } @@ -241,26 +245,23 @@ abstract class Dispatch /** * 路由绑定模型实例 - * * @access protected - * * @param array $bindModel 绑定模型 * @param array $matches 路由变量 - * * @return void */ protected function createBindModel(array $bindModel, array $matches): void { foreach ($bindModel as $key => $val) { - if($val instanceof \Closure) { + if ($val instanceof \Closure) { $result = $this->app->invokeFunction($val, $matches); } else { $fields = explode('&', $key); - if(is_array($val)) { + if (is_array($val)) { [$model, $exception] = $val; } else { - $model = $val; + $model = $val; $exception = true; } @@ -268,7 +269,7 @@ abstract class Dispatch $match = true; foreach ($fields as $field) { - if(!isset($matches[$field])) { + if (!isset($matches[$field])) { $match = false; break; } else { @@ -276,12 +277,12 @@ abstract class Dispatch } } - if($match) { + if ($match) { $result = $model::where($where)->failException($exception)->find(); } } - if(!empty($result)) { + if (!empty($result)) { // 注入容器 $this->app->instance($result::class, $result); } @@ -290,11 +291,8 @@ abstract class Dispatch /** * 验证数据 - * * @access protected - * * @param array $option - * * @return void * @throws \think\exception\ValidateException */ @@ -302,7 +300,7 @@ abstract class Dispatch { [$validate, $scene, $message, $batch] = $option; - if(is_array($validate)) { + if (is_array($validate)) { // 指定验证规则 $v = new Validate(); $v->rule($validate); @@ -312,7 +310,7 @@ abstract class Dispatch $v = new $class(); - if(!empty($scene)) { + if (!empty($scene)) { $v->scene($scene); } } @@ -336,14 +334,26 @@ abstract class Dispatch abstract public function exec(); - public function __sleep() + public function __serialize(): array { - return ['rule', 'dispatch', 'param', 'controller', 'actionName']; + return [ + 'rule' => $this->rule, + 'dispatch' => $this->dispatch, + 'param' => $this->param, + 'controller' => $this->controller, + 'actionName' => $this->actionName, + ]; } - public function __wakeup() + public function __unserialize(array $data): void { - $this->app = Container::pull('app'); + $this->rule = $data['rule']; + $this->dispatch = $data['dispatch']; + $this->param = $data['param']; + $this->controller = $data['controller']; + $this->actionName = $data['actionName']; + + $this->app = Container::pull('app'); $this->request = $this->app->request; } diff --git a/src/think/route/Rule.php b/src/think/route/Rule.php index 73d7dd7..8167b20 100644 --- a/src/think/route/Rule.php +++ b/src/think/route/Rule.php @@ -1030,13 +1030,29 @@ abstract class Rule return call_user_func_array([$this, 'setOption'], $args); } - public function __sleep() + public function __serialize(): array { - return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern']; + return [ + 'name' => $this->name, + 'rule' => $this->rule, + 'route' => $this->route, + 'method' => $this->method, + 'vars' => $this->vars, + 'option' => $this->option, + 'pattern' => $this->pattern, + ]; } - public function __wakeup() + public function __unserialize(array $data): void { + $this->name = $data['name']; + $this->rule = $data['rule']; + $this->route = $data['route']; + $this->method = $data['method']; + $this->vars = $data['vars']; + $this->option = $data['option']; + $this->pattern = $data['pattern']; + $this->router = Container::pull('route'); } diff --git a/src/think/route/Url.php b/src/think/route/Url.php index 6090c30..f2388a0 100644 --- a/src/think/route/Url.php +++ b/src/think/route/Url.php @@ -218,7 +218,7 @@ class Url $controller = empty($path) ? $controller : array_pop($path); $url = $controller . '/' . $action; $auto = $this->route->getName('__think_auto_route__'); - if (!empty($auto) && !strpos($controller,'.')) { + if (!empty($auto) && !strpos($controller, '.')) { $module = empty($path) ? $request->layer() : array_pop($path); $url = $module . '/' . $url; } @@ -368,7 +368,7 @@ class Url } if ($url) { - $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''); + $checkName = $name ?? $url . (isset($info['query']) ? '?' . $info['query'] : ''); $checkDomain = $domain && is_string($domain) ? $domain : null; $rule = $this->route->getName($checkName, $checkDomain); diff --git a/src/think/session/driver/Cache.php b/src/think/session/driver/Cache.php index c5560f0..29d19cb 100644 --- a/src/think/session/driver/Cache.php +++ b/src/think/session/driver/Cache.php @@ -20,7 +20,7 @@ class Cache implements SessionHandlerInterface /** @var CacheInterface */ protected $handler; - /** @var integer */ + /** @var int */ protected $expire; /** @var string */ diff --git a/src/think/traits/DomainHandler.php b/src/think/traits/DomainHandler.php new file mode 100644 index 0000000..469e0a0 --- /dev/null +++ b/src/think/traits/DomainHandler.php @@ -0,0 +1,243 @@ +domain = $domain; + return $this; + } + + /** + * 获取当前域名 + * @access public + * @param bool $port 是否需要包含端口 + * @return string + */ + public function domain(bool $port = false): string + { + if (!$this->domain) { + $this->domain = $this->scheme() . '://' . $this->host(); + } + + return $port ? $this->domain : rtrim($this->domain, ':'); + } + + /** + * 设置域名根 + * @access public + * @param string $domain 域名根 + * @return $this + */ + public function setRootDomain(string $domain) + { + $this->rootDomain = $domain; + return $this; + } + + /** + * 获取域名根 + * @access public + * @return string + */ + public function rootDomain(): string + { + if (!$this->rootDomain) { + $item = explode('.', $this->host()); + $count = count($item); + $suffix = $this->config('app.domain_suffix'); + + if ($suffix && in_array($item[$count - 2], $this->domainSpecialSuffix)) { + $this->rootDomain = $item[$count - 3] . '.' . $item[$count - 2] . '.' . $item[$count - 1]; + } elseif ($suffix) { + $this->rootDomain = $item[$count - 2] . '.' . $item[$count - 1]; + } else { + $this->rootDomain = $item[$count - 2] . '.' . $item[$count - 1]; + } + } + + return $this->rootDomain; + } + + /** + * 设置子域名 + * @access public + * @param string $domain 子域名 + * @return $this + */ + public function setSubDomain(string $domain) + { + $this->subDomain = $domain; + return $this; + } + + /** + * 获取子域名 + * @access public + * @return string + */ + public function subDomain(): string + { + if (!$this->subDomain) { + if ($this->isCli()) { + return ''; + } + + $rootDomain = $this->rootDomain(); + if ($rootDomain) { + $this->subDomain = rtrim(strstr($this->host(), $rootDomain, true), '.'); + } else { + $this->subDomain = ''; + } + } + + return $this->subDomain; + } + + /** + * 设置泛域名 + * @access public + * @param string $domain 泛域名 + * @return $this + */ + public function setPanDomain(string $domain) + { + $this->panDomain = $domain; + return $this; + } + + /** + * 获取泛域名 + * @access public + * @return string + */ + public function panDomain(): string + { + if (!$this->panDomain) { + if ($this->isCli()) { + return ''; + } + + $rootDomain = $this->rootDomain(); + if ($rootDomain) { + $this->panDomain = '*' . $rootDomain; + } else { + $this->panDomain = ''; + } + } + + return $this->panDomain; + } + + /** + * 获取当前HOST + * @access public + * @param bool $strict 是否严格模式 + * @return string + */ + public function host(bool $strict = true): string + { + if ($this->server('HTTP_X_FORWARDED_HOST')) { + $host = $this->server('HTTP_X_FORWARDED_HOST'); + } elseif ($this->server('HTTP_HOST')) { + $host = $this->server('HTTP_HOST'); + } else { + $host = $this->server('SERVER_NAME') . ($this->server('SERVER_PORT') == '80' ? '' : ':' . $this->server('SERVER_PORT')); + } + + return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; + } + + /** + * 获取当前请求的协议 + * @access public + * @return string + */ + public function scheme(): string + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前是否SSL + * @access public + * @return bool + */ + public function isSsl(): bool + { + if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) { + return true; + } elseif ('https' == $this->server('REQUEST_SCHEME')) { + return true; + } elseif ('443' == $this->server('SERVER_PORT')) { + return true; + } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) { + return true; + } + + return false; + } + + /** + * 获取当前请求的端口 + * @access public + * @return int + */ + public function port(): int + { + return $this->server('SERVER_PORT') ?? 80; + } + + /** + * 获取当前请求的远程端口 + * @access public + * @return int + */ + public function remotePort(): int + { + return $this->server('REMOTE_PORT') ?? 0; + } +} \ No newline at end of file diff --git a/src/think/traits/HttpMethodHandler.php b/src/think/traits/HttpMethodHandler.php new file mode 100644 index 0000000..db19e05 --- /dev/null +++ b/src/think/traits/HttpMethodHandler.php @@ -0,0 +1,213 @@ +server('REQUEST_METHOD') ?: 'GET'; + } elseif (!$this->method) { + if (isset($_POST[$this->varMethod])) { + $this->method = strtoupper($_POST[$this->varMethod]); + $this->{$this->method}($_POST); + } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { + $this->method = $this->server('HTTP_X_HTTP_METHOD_OVERRIDE'); + } else { + $this->method = $this->server('REQUEST_METHOD') ?: 'GET'; + } + } + + return $this->method; + } + + /** + * 是否为GET请求 + * @access public + * @return bool + */ + public function isGet(): bool + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost(): bool + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut(): bool + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete(): bool + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead(): bool + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch(): bool + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions(): bool + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为CLI + * @access public + * @return bool + */ + public function isCli(): bool + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为CGI + * @access public + * @return bool + */ + public function isCgi(): bool + { + return str_starts_with(PHP_SAPI, 'cgi'); + } + + /** + * 是否为ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax(bool $ajax = false): bool + { + $result = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower') === 'xmlhttprequest'; + + if (true === $ajax) { + return $result; + } + + return $this->param($this->varAjax) ? true : $result; + } + + /** + * 是否为pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax(bool $pjax = false): bool + { + $result = !empty($this->server('HTTP_X_PJAX')); + + if (true === $pjax) { + return $result; + } + + return $this->param($this->varPjax) ? true : $result; + } + + /** + * 是否为JSON请求 + * @access public + * @return bool + */ + public function isJson(): bool + { + return false !== strpos($this->type(), 'json'); + } + + /** + * 是否为手机访问 + * @access public + * @return bool + */ + public function isMobile(): bool + { + if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), 'wap')) { + return true; + } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), 'VND.WAP.WML')) { + return true; + } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) { + return true; + } elseif ($this->server('HTTP_USER_AGENT') && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc-|htc_|htc-|iemobile|kindle|midp|mmp|mobile|novarra|o2 |opera mini|opera mobi|palm|palmos|pocket|portalmmm|proxynet|sharp-|sharp t-mobile|sonyericsson |sonyericsson|symbian|symbianos|up.browser|up.link|vodafone|wap |webos|windows ce|windows phone|xda |xda_)/i', $this->server('HTTP_USER_AGENT'))) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/think/traits/UrlHandler.php b/src/think/traits/UrlHandler.php new file mode 100644 index 0000000..7ba8489 --- /dev/null +++ b/src/think/traits/UrlHandler.php @@ -0,0 +1,233 @@ +url = $url; + return $this; + } + + /** + * 获取当前请求URL + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function url(bool $complete = false): string + { + if ($this->url) { + $url = $this->url; + } elseif ($this->server('HTTP_X_REWRITE_URL')) { + $url = $this->server('HTTP_X_REWRITE_URL'); + } elseif ($this->server('REQUEST_URI')) { + $url = $this->server('REQUEST_URI'); + } elseif ($this->server('ORIG_PATH_INFO')) { + $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : ''); + } elseif (isset($_SERVER['argv'][1])) { + $url = $_SERVER['argv'][1]; + } else { + $url = ''; + } + + return $complete ? $this->domain() . $url : $url; + } + + /** + * 设置当前URL 不含QUERY_STRING + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setBaseUrl(string $url) + { + $this->baseUrl = $url; + return $this; + } + + /** + * 获取当前URL 不含QUERY_STRING + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function baseUrl(bool $complete = false): string + { + if (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = str_contains($str, '?') ? strstr($str, '?', true) : $str; + } + + return $complete ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 获取当前执行的文件 SCRIPT_NAME + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function baseFile(bool $complete = false): string + { + if (!$this->baseFile) { + $url = ''; + if (!$this->isCli()) { + $script_name = basename($this->server('SCRIPT_FILENAME')); + if (basename($this->server('SCRIPT_NAME')) === $script_name) { + $url = $this->server('SCRIPT_NAME'); + } elseif (basename($this->server('PHP_SELF')) === $script_name) { + $url = $this->server('PHP_SELF'); + } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) { + $url = $this->server('ORIG_SCRIPT_NAME'); + } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) { + $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name; + } elseif ($this->server('DOCUMENT_ROOT') && str_starts_with($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT'))) { + $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME'))); + } + } + $this->baseFile = $url; + } + + return $complete ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置URL访问根地址 + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setRoot(string $url) + { + $this->root = $url; + return $this; + } + + /** + * 获取URL访问根地址 + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function root(bool $complete = false): string + { + if (!$this->root) { + $file = $this->baseFile(); + if ($file && !str_starts_with($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + + return $complete ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取URL访问根地址 + * @access public + * @return string + */ + public function rootUrl(): string + { + $base = $this->root(); + $root = '' === $base ? dirname($this->baseUrl()) : $base; + + return $root; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo(): string + { + if (isset($_SERVER['PATH_INFO'])) { + return ltrim($_SERVER['PATH_INFO'], '/'); + } + + $url = $this->url(); + $base = $this->rootUrl(); + + if ($base && str_starts_with($url, $base)) { + $url = substr($url, strlen($base)); + } + + return ltrim($url, '/'); + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access public + * @return string + */ + public function path(): string + { + $pathinfo = $this->pathinfo(); + $suffix = $this->config('url_html_suffix'); + + if (false === $suffix) { + return $pathinfo; + } + + return preg_replace('/\.(' . $suffix . ')$/i', '', $pathinfo); + } + + /** + * 获取URL后缀 + * @access public + * @return string + */ + public function ext(): string + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点数 + * @return float|int + */ + public function time(bool $float = false) + { + return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; + } +} \ No newline at end of file