4 Commits

Author SHA1 Message Date
fed4b76e28 Merge tag '8.1.3.3' into develop 2026-01-13 17:28:06 +08:00
2ee72e1363 Merge branch 'release/8.1.3.3' 2026-01-13 17:28:06 +08:00
5d6e2fc5e0 同步更新 2026-01-13 17:27:32 +08:00
45785dd33f Merge tag '8.1.3.2' into develop 2026-01-07 17:02:35 +08:00
38 changed files with 1211 additions and 305 deletions

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ Thumbs.db
/.buildpath /.buildpath
/.project /.project
.phpunit.result.cache .phpunit.result.cache
.php-cs-fixer.cache

48
.php-cs-fixer.dist.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
$finder = (new Finder())
->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)
;

View File

@@ -25,17 +25,18 @@
"ext-ctype": "*", "ext-ctype": "*",
"psr/log": "^1.0|^2.0|^3.0", "psr/log": "^1.0|^2.0|^3.0",
"psr/simple-cache": "^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-orm": "^3.0|^4.0",
"topthink/think-helper": "^3.1", "topthink/think-helper": "^3.1",
"fixtopthink/think-container": "^3.0", "fixtopthink/think-container": "^3.0",
"topthink/think-validate": "^3.0" "topthink/think-validate": "^3.0"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^3.92",
"guzzlehttp/psr7": "^2.1.0",
"mikey179/vfsstream": "^1.6", "mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.2", "mockery/mockery": "^1.2",
"phpunit/phpunit": "^9.5", "phpunit/phpunit": "^9.5"
"guzzlehttp/psr7": "^2.1.0"
}, },
"autoload": { "autoload": {
"files": [], "files": [],
@@ -54,6 +55,6 @@
"sort-packages": true "sort-packages": true
}, },
"scripts": { "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"
} }
} }

View File

@@ -42,9 +42,9 @@ use think\validate\ValidateRuleSet;
if (!function_exists('abort')) { if (!function_exists('abort')) {
/** /**
* 抛出HTTP异常 * 抛出HTTP异常
* @param integer|Response $code 状态码 或者 Response对象实例 * @param int|Response $code 状态码 或者 Response对象实例
* @param string $message 错误信息 * @param string $message 错误信息
* @param array $header 参数 * @param array $header 参数
*/ */
function abort($code, string $message = '', array $header = []) function abort($code, string $message = '', array $header = [])
{ {

View File

@@ -43,7 +43,7 @@ class App extends Container
* 核心框架版本 * 核心框架版本
* @deprecated 已经废弃 请改用version()方法 * @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; protected $beginMem;
@@ -427,7 +427,7 @@ class App extends Container
/** /**
* 获取应用初始内存占用 * 获取应用初始内存占用
* @access public * @access public
* @return integer * @return int
*/ */
public function getBeginMem(): int public function getBeginMem(): int
{ {
@@ -458,9 +458,8 @@ class App extends Container
public function initialize() public function initialize()
{ {
$this->initialized = true; $this->initialized = true;
$this->beginTime = microtime(true);
$this->beginTime = microtime(true); $this->beginMem = memory_get_usage();
$this->beginMem = memory_get_usage();
// 加载环境变量 // 加载环境变量
if ($this->baseEnvName) { if ($this->baseEnvName) {

View File

@@ -28,6 +28,7 @@ use think\console\command\make\Service;
use think\console\command\make\Subscribe; use think\console\command\make\Subscribe;
use think\console\command\make\Validate; use think\console\command\make\Validate;
use think\console\command\optimize\Config; use think\console\command\optimize\Config;
use think\console\command\optimize\Optimize;
use think\console\command\optimize\Route; use think\console\command\optimize\Route;
use think\console\command\optimize\Schema; use think\console\command\optimize\Schema;
use think\console\command\RouteList; use think\console\command\RouteList;
@@ -70,6 +71,7 @@ class Console
'make:listener' => Listener::class, 'make:listener' => Listener::class,
'make:service' => Service::class, 'make:service' => Service::class,
'make:subscribe' => Subscribe::class, 'make:subscribe' => Subscribe::class,
'optimize' => Optimize::class,
'optimize:config' => Config::class, 'optimize:config' => Config::class,
'optimize:route' => Route::class, 'optimize:route' => Route::class,
'optimize:schema' => Schema::class, 'optimize:schema' => Schema::class,
@@ -785,5 +787,4 @@ class Console
return $namespaces; return $namespaces;
} }
} }

View File

@@ -135,7 +135,7 @@ class Cookie
* @access public * @access public
* @param string $name cookie名称 * @param string $name cookie名称
* @param string $value cookie值 * @param string $value cookie值
* @param mixed $option 可选参数 可能会是 null|integer|string * @param mixed $option 可选参数 可能会是 null|int|string
* @return void * @return void
*/ */
public function forever(string $name, string $value = '', $option = null): void public function forever(string $name, string $value = '', $option = null): void

View File

@@ -191,9 +191,12 @@ class Event
if (empty($prefix) && $reflect->hasProperty('eventPrefix')) { if (empty($prefix) && $reflect->hasProperty('eventPrefix')) {
$reflectProperty = $reflect->getProperty('eventPrefix'); $reflectProperty = $reflect->getProperty('eventPrefix');
if(PHP_VERSION_ID < 80100) {
if (PHP_VERSION_ID < 80100) {
// 8.0 版本时调用
$reflectProperty->setAccessible(true); $reflectProperty->setAccessible(true);
} }
$prefix = $reflectProperty->getValue($observer); $prefix = $reflectProperty->getValue($observer);
} }

View File

@@ -17,6 +17,9 @@ use ArrayAccess;
use think\facade\Lang; use think\facade\Lang;
use think\file\UploadedFile; use think\file\UploadedFile;
use think\route\Rule; 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 class Request implements ArrayAccess
{ {
use UrlHandler, DomainHandler, HttpMethodHandler;
/** /**
* 兼容PATH_INFO获取 * 兼容PATH_INFO获取
* @var array * @var array
@@ -36,36 +40,7 @@ class Request implements ArrayAccess
*/ */
protected $varPathinfo = 's'; 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代理标识 * HTTPS代理标识
* @var string * @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']; 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 * pathinfo
* @var string * @var string
@@ -435,7 +358,7 @@ class Request implements ArrayAccess
*/ */
public function subDomain(): string public function subDomain(): string
{ {
if (is_null($this->subDomain)) { if ($this->subDomain === '') {
// 获取当前主域名 // 获取当前主域名
$rootDomain = $this->rootDomain(); $rootDomain = $this->rootDomain();
@@ -681,7 +604,7 @@ class Request implements ArrayAccess
* 获取当前请求的时间 * 获取当前请求的时间
* @access public * @access public
* @param bool $float 是否使用浮点类型 * @param bool $float 是否使用浮点类型
* @return integer|float * @return int|float
*/ */
public function time(bool $float = false) public function time(bool $float = false)
{ {
@@ -1682,7 +1605,7 @@ class Request implements ArrayAccess
* @param string $ip IP地址 * @param string $ip IP地址
* @param string $type IP地址类型 (ipv4, ipv6) * @param string $type IP地址类型 (ipv4, ipv6)
* *
* @return boolean * @return bool
*/ */
public function isValidIP(string $ip, string $type = ''): bool public function isValidIP(string $ip, string $type = ''): bool
{ {
@@ -2191,7 +2114,7 @@ class Request implements ArrayAccess
* 检测中间传递数据的值 * 检测中间传递数据的值
* @access public * @access public
* @param string $name 名称 * @param string $name 名称
* @return boolean * @return bool
*/ */
public function __isset(string $name): bool public function __isset(string $name): bool
{ {

View File

@@ -38,7 +38,7 @@ abstract class Response
/** /**
* 状态码 * 状态码
* @var integer * @var int
*/ */
protected $code = 200; protected $code = 200;
@@ -277,7 +277,7 @@ abstract class Response
/** /**
* 发送HTTP状态 * 发送HTTP状态
* @access public * @access public
* @param integer $code 状态码 * @param int $code 状态码
* @return $this * @return $this
*/ */
public function code(int $code) public function code(int $code)
@@ -406,7 +406,7 @@ abstract class Response
/** /**
* 获取状态码 * 获取状态码
* @access public * @access public
* @return integer * @return int
*/ */
public function getCode(): int public function getCode(): int
{ {

View File

@@ -36,13 +36,13 @@ abstract class Driver implements CacheHandlerInterface
/** /**
* 缓存读取次数 * 缓存读取次数
* @var integer * @var int
*/ */
protected $readTimes = 0; protected $readTimes = 0;
/** /**
* 缓存写入次数 * 缓存写入次数
* @var integer * @var int
*/ */
protected $writeTimes = 0; protected $writeTimes = 0;
@@ -61,7 +61,7 @@ abstract class Driver implements CacheHandlerInterface
/** /**
* 获取有效期 * 获取有效期
* @access protected * @access protected
* @param integer|DateInterval|DateTimeInterface $expire 有效期 * @param int|DateInterval|DateTimeInterface $expire 有效期
* @return int * @return int
*/ */
protected function getExpireTime(int | DateInterval | DateTimeInterface $expire): int protected function getExpireTime(int | DateInterval | DateTimeInterface $expire): int

View File

@@ -33,9 +33,9 @@ class TagSet
/** /**
* 写入缓存 * 写入缓存
* @access public * @access public
* @param string $name 缓存变量名 * @param string $name 缓存变量名
* @param mixed $value 存储数据 * @param mixed $value 存储数据
* @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒) * @param int|DateInterval|DateTimeInterface $expire 有效时间(秒)
* @return bool * @return bool
*/ */
public function set($name, $value, $expire = null): bool public function set($name, $value, $expire = null): bool

View File

@@ -120,9 +120,9 @@ class Memcached extends Driver
/** /**
* 写入缓存 * 写入缓存
* @access public * @access public
* @param string $name 缓存变量名 * @param string $name 缓存变量名
* @param mixed $value 存储数据 * @param mixed $value 存储数据
* @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒) * @param int|DateInterval|DateTimeInterface $expire 有效时间(秒)
* @return bool * @return bool
*/ */
public function set($name, $value, $expire = null): bool public function set($name, $value, $expire = null): bool

View File

@@ -133,9 +133,9 @@ class Redis extends Driver
/** /**
* 写入缓存 * 写入缓存
* @access public * @access public
* @param string $name 缓存变量名 * @param string $name 缓存变量名
* @param mixed $value 存储数据 * @param mixed $value 存储数据
* @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒) * @param int|DateInterval|DateTimeInterface $expire 有效时间(秒)
* @return bool * @return bool
*/ */
public function set($name, $value, $expire = null): bool public function set($name, $value, $expire = null): bool

View File

@@ -86,9 +86,9 @@ class Wincache extends Driver
/** /**
* 写入缓存 * 写入缓存
* @access public * @access public
* @param string $name 缓存变量名 * @param string $name 缓存变量名
* @param mixed $value 存储数据 * @param mixed $value 存储数据
* @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒) * @param int|DateInterval|DateTimeInterface $expire 有效时间(秒)
* @return bool * @return bool
*/ */
public function set($name, $value, $expire = null): bool public function set($name, $value, $expire = null): bool

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; namespace think\console\command\optimize;
use InvalidArgumentException;
use think\console\Command; use think\console\Command;
use think\console\Input; use think\console\Input;
use think\console\input\Argument; use think\console\input\Argument;
use think\console\Output; use think\console\Output;
use Throwable;
class Config extends Command class Config extends Command
{ {
use Discoverable;
protected function configure() protected function configure()
{ {
$this->setName('optimize:config') $this->setName('optimize:config')
@@ -26,39 +30,54 @@ class Config extends Command
protected function execute(Input $input, Output $output) protected function execute(Input $input, Output $output)
{ {
// 加载配置文件 $dirs = ((array) $input->getArgument('dir')) ?: $this->getDefaultDirs();
$dir = $input->getArgument('dir') ?: '';
$path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : ''); foreach ($dirs as $dir) {
if (!is_dir($path)) { $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '');
try { try {
mkdir($path, 0755, true); $cache = $this->buildCache($dir);
} catch (\Exception $e) { 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); $output->info('Succeed!');
$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>");
}
} }
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; $path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'config' . DIRECTORY_SEPARATOR;
$files = []; if (! is_dir($path)) {
throw new InvalidArgumentException("{$path} directory does not exist");
if (is_dir($configPath)) {
$files = glob($configPath . '*' . $this->app->getConfigExt());
} }
foreach ($files as $file) { // 使用 clone 防止多应用配置污染
$this->app->config->load($file, pathinfo($file, PATHINFO_FILENAME)); $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; namespace think\console\command\optimize;
use DirectoryIterator; use DirectoryIterator;
use InvalidArgumentException;
use think\console\Command; use think\console\Command;
use think\console\Input; use think\console\Input;
use think\console\input\Argument; use think\console\input\Argument;
use think\console\Output; use think\console\Output;
use think\event\RouteLoaded; use think\event\RouteLoaded;
use Throwable;
class Route extends Command class Route extends Command
{ {
use Discoverable;
protected function configure() protected function configure()
{ {
$this->setName('optimize:route') $this->setName('optimize:route')
@@ -28,18 +32,22 @@ class Route extends Command
protected function execute(Input $input, Output $output) 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 : ''); foreach ($dirs as $dir) {
if (!is_dir($path)) { $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '');
try { try {
mkdir($path, 0755, true); $cache = $this->buildRouteCache($dir);
} catch (\Exception $e) { 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) protected function scanRoute($path, $root, $autoGroup)
@@ -53,7 +61,7 @@ class Route extends Command
if ($fileinfo->getType() == 'file' && $fileinfo->getExtension() == 'php') { if ($fileinfo->getType() == 'file' && $fileinfo->getExtension() == 'php') {
$groupName = str_replace('\\', '/', substr_replace($fileinfo->getPath(), '', 0, strlen($root))); $groupName = str_replace('\\', '/', substr_replace($fileinfo->getPath(), '', 0, strlen($root)));
if ($groupName) { if ($groupName) {
$this->app->route->group($groupName, function() use ($fileinfo) { $this->app->route->group($groupName, function () use ($fileinfo) {
include $fileinfo->getRealPath(); include $fileinfo->getRealPath();
}); });
} else { } else {
@@ -73,6 +81,9 @@ class Route extends Command
// 路由检测 // 路由检测
$autoGroup = $this->app->route->config('route_auto_group'); $autoGroup = $this->app->route->config('route_auto_group');
$path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR; $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); $this->scanRoute($path, $path, $autoGroup);
@@ -83,4 +94,17 @@ class Route extends Command
return '<?php ' . PHP_EOL . 'return ' . var_export($rules, true) . ';'; 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; namespace think\console\command\optimize;
use Exception; use Exception;
use InvalidArgumentException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
use SplFileInfo;
use think\console\Command; use think\console\Command;
use think\console\Input; use think\console\Input;
use think\console\input\Argument; use think\console\input\Argument;
use think\console\input\Option; use think\console\input\Option;
use think\console\Output; use think\console\Output;
use think\db\PDOConnection; use think\db\PDOConnection;
use Throwable;
class Schema extends Command class Schema extends Command
{ {
use Discoverable;
protected function configure() protected function configure()
{ {
$this->setName('optimize:schema') $this->setName('optimize:schema')
@@ -31,71 +39,38 @@ class Schema extends Command
protected function execute(Input $input, Output $output) protected function execute(Input $input, Output $output)
{ {
$dir = $input->getArgument('dir') ?: ''; try {
if ($table = $input->hasOption('table')) {
if ($input->hasOption('table')) { $this->cacheTable($table, $input->getOption('connection'));
$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');
} else { } else {
[$dbName, $table] = explode('.', $table); $dirs = ((array) $input->getArgument('dir')) ?: $this->getDefaultDirs();
} foreach ($dirs as $dir) {
$this->cacheModel($dir);
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;
} }
$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 protected function buildModelSchema(string $class): void
{ {
$reflect = new \ReflectionClass($class); $reflect = new ReflectionClass($class);
if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { if ($reflect->isAbstract() || ! $reflect->isSubclassOf('\think\Model')) {
try { return;
/** @var \think\Model $model */ }
$model = new $class; try {
$connection = $model->db()->getConnection(); /** @var \think\Model $model */
if ($connection instanceof PDOConnection) { $model = new $class;
$table = $model->getTable(); $connection = $model->db()->getConnection();
//预读字段信息 if ($connection instanceof PDOConnection) {
$connection->getSchemaInfo($table, true); $table = $model->getTable();
} //预读字段信息
} catch (Exception $e) { $connection->getSchemaInfo($table, true);
} }
} catch (Exception $e) {
} }
} }
@@ -106,4 +81,81 @@ class Schema extends Command
$connection->getSchemaInfo("{$dbName}.{$table}", true); $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;
}
} }

View File

@@ -212,7 +212,7 @@ class Descriptor
*/ */
protected function describeConsole(Console $console, array $options = []) protected function describeConsole(Console $console, array $options = [])
{ {
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $describedNamespace = $options['namespace'] ?? null;
$description = new ConsoleDescription($console, $describedNamespace); $description = new ConsoleDescription($console, $describedNamespace);
if (isset($options['raw_text']) && $options['raw_text']) { if (isset($options['raw_text']) && $options['raw_text']) {

View File

@@ -120,11 +120,11 @@ class Console
]); ]);
for ($i = 0, $count = count($trace); $i < $count; ++$i) { for ($i = 0, $count = count($trace); $i < $count; ++$i) {
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; $class = $trace[$i]['class'] ?? '';
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; $type = $trace[$i]['type'] ?? '';
$function = $trace[$i]['function']; $function = $trace[$i]['function'];
$file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; $file = $trace[$i]['file'] ?? 'n/a';
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; $line = $trace[$i]['line'] ?? 'n/a';
$this->write(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr); $this->write(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr);
} }

View File

@@ -55,12 +55,12 @@ interface ModelRelationInterface
/** /**
* 关联统计 * 关联统计
* @access public * @access public
* @param Model $result 模型对象 * @param Model $result 模型对象
* @param Closure $closure 闭包 * @param Closure $closure 闭包
* @param string $aggregate 聚合查询方法 * @param string $aggregate 聚合查询方法
* @param string $field 字段 * @param string $field 字段
* @param string $name 统计字段别名 * @param string|null $name 统计字段别名
* @return integer * @return int
*/ */
public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', ?string &$name = null); public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', ?string &$name = null);
@@ -78,10 +78,10 @@ interface ModelRelationInterface
/** /**
* 根据关联条件查询当前模型 * 根据关联条件查询当前模型
* @access public * @access public
* @param string $operator 比较操作符 * @param string $operator 比较操作符
* @param integer $count 个数 * @param int $count 个数
* @param string $id 关联表的统计字段 * @param string $id 关联表的统计字段
* @param string $joinType JOIN类型 * @param string $joinType JOIN类型
* @return Query * @return Query
*/ */
public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', ?Query $query = null): Query; public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', ?Query $query = null): Query;

View File

@@ -24,17 +24,17 @@ class ErrorException extends Exception
{ {
/** /**
* 用于保存错误级别 * 用于保存错误级别
* @var integer * @var int
*/ */
protected $severity; protected $severity;
/** /**
* 错误异常构造函数 * 错误异常构造函数
* @access public * @access public
* @param integer $severity 错误级别 * @param int $severity 错误级别
* @param string $message 错误详细信息 * @param string $message 错误详细信息
* @param string $file 出错文件路径 * @param string $file 出错文件路径
* @param integer $line 出错行号 * @param int $line 出错行号
*/ */
public function __construct(int $severity, string $message, string $file, int $line) public function __construct(int $severity, string $message, string $file, int $line)
{ {
@@ -48,7 +48,7 @@ class ErrorException extends Exception
/** /**
* 获取错误级别 * 获取错误级别
* @access public * @access public
* @return integer 错误级别 * @return int 错误级别
*/ */
final public function getSeverity() final public function getSeverity()
{ {

View File

@@ -270,7 +270,7 @@ class Handle
* ErrorException则使用错误级别作为错误编码 * ErrorException则使用错误级别作为错误编码
* @access protected * @access protected
* @param Throwable $exception * @param Throwable $exception
* @return integer 错误编码 * @return int 错误编码
*/ */
protected function getCode(Throwable $exception) protected function getCode(Throwable $exception)
{ {

View File

@@ -36,7 +36,7 @@ use think\Facade;
* @method static string getConfigPath() 获取应用配置目录 * @method static string getConfigPath() 获取应用配置目录
* @method static string getConfigExt() 获取配置后缀 * @method static string getConfigExt() 获取配置后缀
* @method static float getBeginTime() 获取应用开启时间 * @method static float getBeginTime() 获取应用开启时间
* @method static integer getBeginMem() 获取应用初始内存占用 * @method static int getBeginMem() 获取应用初始内存占用
* @method static \think\App initialize() 初始化应用 * @method static \think\App initialize() 初始化应用
* @method static bool initialized() 是否初始化过 * @method static bool initialized() 是否初始化过
* @method static void loadLangPack(string $langset) 加载语言包 * @method static void loadLangPack(string $langset) 加载语言包

View File

@@ -1,4 +1,5 @@
<?php <?php
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ] // | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
@@ -38,7 +39,7 @@ use think\route\Rule;
* @method static \think\Request setPathinfo(string $pathinfo) 设置当前请求的pathinfo * @method static \think\Request setPathinfo(string $pathinfo) 设置当前请求的pathinfo
* @method static string pathinfo() 获取当前请求URL的pathinfo信息含URL后缀 * @method static string pathinfo() 获取当前请求URL的pathinfo信息含URL后缀
* @method static string ext() 当前URL的访问后缀 * @method static string ext() 当前URL的访问后缀
* @method static integer|float time(bool $float = false) 获取当前请求的时间 * @method static int|float time(bool $float = false) 获取当前请求的时间
* @method static string type() 当前请求的资源类型 * @method static string type() 当前请求的资源类型
* @method static void mimeType(string|array $type, string $val = '') 设置资源类型 * @method static void mimeType(string|array $type, string $val = '') 设置资源类型
* @method static \think\Request setMethod(string $method) 设置请求类型 * @method static \think\Request setMethod(string $method) 设置请求类型
@@ -81,7 +82,7 @@ use think\route\Rule;
* @method static bool isAjax(bool $ajax = false) 当前是否Ajax请求 * @method static bool isAjax(bool $ajax = false) 当前是否Ajax请求
* @method static bool isPjax(bool $pjax = false) 当前是否Pjax请求 * @method static bool isPjax(bool $pjax = false) 当前是否Pjax请求
* @method static string ip() 获取客户端IP地址 * @method static string ip() 获取客户端IP地址
* @method static boolean isValidIP(string $ip, string $type = '') 检测是否是合法的IP地址 * @method static bool isValidIP(string $ip, string $type = '') 检测是否是合法的IP地址
* @method static string ip2bin(string $ip) 将IP地址转换为二进制字符串 * @method static string ip2bin(string $ip) 将IP地址转换为二进制字符串
* @method static bool isMobile() 检测是否使用手机访问 * @method static bool isMobile() 检测是否使用手机访问
* @method static string scheme() 当前URL地址中的scheme参数 * @method static string scheme() 当前URL地址中的scheme参数
@@ -116,7 +117,7 @@ use think\route\Rule;
* @method static \think\Request withRoute(array $route) 设置ROUTE变量 * @method static \think\Request withRoute(array $route) 设置ROUTE变量
* @method static mixed __set(string $name, mixed $value) 设置中间传递数据 * @method static mixed __set(string $name, mixed $value) 设置中间传递数据
* @method static mixed __get(string $name) 获取中间传递数据的值 * @method static mixed __get(string $name) 获取中间传递数据的值
* @method static boolean __isset(string $name) 检测中间传递数据的值 * @method static bool __isset(string $name) 检测中间传递数据的值
* @method static bool offsetExists(mixed $name) * @method static bool offsetExists(mixed $name)
* @method static mixed offsetGet(mixed $name) * @method static mixed offsetGet(mixed $name)
* @method static mixed offsetSet(mixed $name, $value) * @method static mixed offsetSet(mixed $name, $value)

View File

@@ -65,10 +65,10 @@ class Error
/** /**
* Error Handler * Error Handler
* @access public * @access public
* @param integer $errno 错误编号 * @param int $errno 错误编号
* @param string $errstr 详细错误信息 * @param string $errstr 详细错误信息
* @param string $errfile 出错的文件 * @param string $errfile 出错的文件
* @param integer $errline 出错行号 * @param int $errline 出错行号
* @throws ErrorException * @throws ErrorException
*/ */
public function appError(int $errno, string $errstr, string $errfile = '', int $errline = 0): void public function appError(int $errno, string $errstr, string $errfile = '', int $errline = 0): void

View File

@@ -132,6 +132,9 @@ class File implements LogHandlerInterface
try { try {
if (count($files) > $this->config['max_files']) { if (count($files) > $this->config['max_files']) {
set_error_handler(fn() => null); set_error_handler(fn() => null);
usort($files, function($a, $b) {
return filemtime($a) - filemtime($b);
});
unlink($files[0]); unlink($files[0]);
restore_error_handler(); restore_error_handler();
} }

View File

@@ -91,7 +91,7 @@ class File extends Response
/** /**
* 设置有效期 * 设置有效期
* @access public * @access public
* @param integer $expire 有效期 * @param int $expire 有效期
* @return $this * @return $this
*/ */
public function expire(int $expire) public function expire(int $expire)

View File

@@ -8,7 +8,7 @@
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com> // | Author: liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
declare (strict_types=1); declare (strict_types = 1);
namespace think\route; namespace think\route;
@@ -28,9 +28,20 @@ use think\Validate;
*/ */
abstract class Dispatch abstract class Dispatch
{ {
/**
* 控制器名
* @var string
*/
protected $controller;
/**
* 操作名
* @var string
*/
protected $actionName;
/** /**
* 应用对象 * 应用对象
*
* @var App * @var App
*/ */
protected $app; protected $app;
@@ -41,7 +52,6 @@ abstract class Dispatch
/** /**
* 执行路由调度 * 执行路由调度
*
* @access public * @access public
* @return Response * @return Response
*/ */
@@ -53,23 +63,23 @@ abstract class Dispatch
protected function autoResponse($data): Response protected function autoResponse($data): Response
{ {
if($data instanceof Response) { if ($data instanceof Response) {
$response = $data; $response = $data;
} elseif($data instanceof ResponseInterface) { } elseif ($data instanceof ResponseInterface) {
$response = Response::create((string)$data->getBody(), 'html', $data->getStatusCode()); $response = Response::create((string) $data->getBody(), 'html', $data->getStatusCode());
foreach ($data->getHeaders() as $header => $values) { foreach ($data->getHeaders() as $header => $values) {
$response->header([$header => implode(", ", $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); $response = Response::create($data, $type);
} else { } else {
$data = ob_get_clean(); $data = ob_get_clean();
$content = false === $data ? '' : $data; $content = false === $data ? '' : $data;
$status = '' === $content && $this->request->isJson() ? 204 : 200; $status = '' === $content && $this->request->isJson() ? 204 : 200;
$response = Response::create($content, 'html', $status); $response = Response::create($content, 'html', $status);
} }
@@ -78,7 +88,6 @@ abstract class Dispatch
/** /**
* 检查路由后置操作 * 检查路由后置操作
*
* @access protected * @access protected
* @return void * @return void
*/ */
@@ -87,8 +96,8 @@ abstract class Dispatch
$option = $this->option; $option = $this->option;
// 添加中间件 // 添加中间件
if(!empty($option['middleware'])) { if (!empty($option['middleware'])) {
if(isset($option['without_middleware'])) { if (isset($option['without_middleware'])) {
$middleware = !empty($option['without_middleware']) ? array_diff($option['middleware'], $option['without_middleware']) : []; $middleware = !empty($option['without_middleware']) ? array_diff($option['middleware'], $option['without_middleware']) : [];
} else { } else {
$middleware = $option['middleware']; $middleware = $option['middleware'];
@@ -96,12 +105,12 @@ abstract class Dispatch
$this->app->middleware->import($middleware, 'route'); $this->app->middleware->import($middleware, 'route');
} }
if(!empty($option['append'])) { if (!empty($option['append'])) {
$this->param = array_merge($this->param, $option['append']); $this->param = array_merge($this->param, $option['append']);
} }
// 绑定模型数据 // 绑定模型数据
if(!empty($option['model'])) { if (!empty($option['model'])) {
$this->createBindModel($option['model'], $this->param); $this->createBindModel($option['model'], $this->param);
} }
@@ -112,14 +121,13 @@ abstract class Dispatch
$this->request->setRoute($this->param); $this->request->setRoute($this->param);
// 数据自动验证 // 数据自动验证
if(isset($option['validate'])) { if (isset($option['validate'])) {
$this->autoValidate($option['validate']); $this->autoValidate($option['validate']);
} }
} }
/** /**
* 获取操作的绑定参数 * 获取操作的绑定参数
*
* @access protected * @access protected
* @return array * @return array
*/ */
@@ -135,11 +143,9 @@ abstract class Dispatch
/** /**
* 执行中间件调度 * 执行中间件调度
*
* @access public * @access public
* * @param object $instance 控制器实例
* @param object $controller 控制器实例 * @param string $action
*
* @return void * @return void
*/ */
protected function responseWithMiddlewarePipeline($instance, $action) protected function responseWithMiddlewarePipeline($instance, $action)
@@ -153,20 +159,20 @@ abstract class Dispatch
$suffix = $this->rule->config('action_suffix'); $suffix = $this->rule->config('action_suffix');
$action = $action . $suffix; $action = $action . $suffix;
if(is_callable([$instance, $action])) { if (is_callable([$instance, $action])) {
$vars = $this->getActionBindVars(); $vars = $this->getActionBindVars();
try { try {
$reflect = new ReflectionMethod($instance, $action); $reflect = new ReflectionMethod($instance, $action);
// 严格获取当前操作方法名 // 严格获取当前操作方法名
$actionName = $reflect->getName(); $actionName = $reflect->getName();
if($suffix) { if ($suffix) {
$actionName = substr($actionName, 0, -strlen($suffix)); $actionName = substr($actionName, 0, -strlen($suffix));
} }
$this->request->setAction($actionName); $this->request->setAction($actionName);
} catch (ReflectionException $e) { } catch (ReflectionException $e) {
$reflect = new ReflectionMethod($instance, '__call'); $reflect = new ReflectionMethod($instance, '__call');
$vars = [$action, $vars]; $vars = [$action, $vars];
$this->request->setAction($action); $this->request->setAction($action);
} }
} else { } else {
@@ -182,47 +188,45 @@ abstract class Dispatch
/** /**
* 使用反射机制注册控制器中间件 * 使用反射机制注册控制器中间件
*
* @access public * @access public
*
* @param object $controller 控制器实例 * @param object $controller 控制器实例
*
* @return void * @return void
*/ */
protected function registerControllerMiddleware($controller): void protected function registerControllerMiddleware($controller): void
{ {
$class = new ReflectionClass($controller); $class = new ReflectionClass($controller);
if($class->hasProperty('middleware')) { if ($class->hasProperty('middleware')) {
$reflectionProperty = $class->getProperty('middleware'); $reflectionProperty = $class->getProperty('middleware');
if(PHP_VERSION_ID < 80100) {
if (PHP_VERSION_ID < 80100) {
$reflectionProperty->setAccessible(true); $reflectionProperty->setAccessible(true);
} }
$middlewares = $reflectionProperty->getValue($controller); $middlewares = $reflectionProperty->getValue($controller);
$action = $this->request->action(true); $action = $this->request->action(true);
foreach ($middlewares as $key => $val) { foreach ($middlewares as $key => $val) {
if(!is_int($key)) { if (!is_int($key)) {
$middleware = $key; $middleware = $key;
$options = $val; $options = $val;
} elseif(isset($val['middleware'])) { } elseif (isset($val['middleware'])) {
$middleware = $val['middleware']; $middleware = $val['middleware'];
$options = $val['options'] ?? []; $options = $val['options'] ?? [];
} else { } else {
$middleware = $val; $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; continue;
} elseif(isset($options['except']) && in_array($action, $this->parseActions($options['except']))) { } elseif (isset($options['except']) && in_array($action, $this->parseActions($options['except']))) {
continue; continue;
} }
if(is_string($middleware) && str_contains($middleware, ':')) { if (is_string($middleware) && str_contains($middleware, ':')) {
$middleware = explode(':', $middleware); $middleware = explode(':', $middleware);
if(count($middleware) > 1) { if (count($middleware) > 1) {
$middleware = [$middleware[0], array_slice($middleware, 1)]; $middleware = [$middleware[0], array_slice($middleware, 1)];
} }
} }
@@ -241,26 +245,23 @@ abstract class Dispatch
/** /**
* 路由绑定模型实例 * 路由绑定模型实例
*
* @access protected * @access protected
*
* @param array $bindModel 绑定模型 * @param array $bindModel 绑定模型
* @param array $matches 路由变量 * @param array $matches 路由变量
*
* @return void * @return void
*/ */
protected function createBindModel(array $bindModel, array $matches): void protected function createBindModel(array $bindModel, array $matches): void
{ {
foreach ($bindModel as $key => $val) { foreach ($bindModel as $key => $val) {
if($val instanceof \Closure) { if ($val instanceof \Closure) {
$result = $this->app->invokeFunction($val, $matches); $result = $this->app->invokeFunction($val, $matches);
} else { } else {
$fields = explode('&', $key); $fields = explode('&', $key);
if(is_array($val)) { if (is_array($val)) {
[$model, $exception] = $val; [$model, $exception] = $val;
} else { } else {
$model = $val; $model = $val;
$exception = true; $exception = true;
} }
@@ -268,7 +269,7 @@ abstract class Dispatch
$match = true; $match = true;
foreach ($fields as $field) { foreach ($fields as $field) {
if(!isset($matches[$field])) { if (!isset($matches[$field])) {
$match = false; $match = false;
break; break;
} else { } else {
@@ -276,12 +277,12 @@ abstract class Dispatch
} }
} }
if($match) { if ($match) {
$result = $model::where($where)->failException($exception)->find(); $result = $model::where($where)->failException($exception)->find();
} }
} }
if(!empty($result)) { if (!empty($result)) {
// 注入容器 // 注入容器
$this->app->instance($result::class, $result); $this->app->instance($result::class, $result);
} }
@@ -290,11 +291,8 @@ abstract class Dispatch
/** /**
* 验证数据 * 验证数据
*
* @access protected * @access protected
*
* @param array $option * @param array $option
*
* @return void * @return void
* @throws \think\exception\ValidateException * @throws \think\exception\ValidateException
*/ */
@@ -302,7 +300,7 @@ abstract class Dispatch
{ {
[$validate, $scene, $message, $batch] = $option; [$validate, $scene, $message, $batch] = $option;
if(is_array($validate)) { if (is_array($validate)) {
// 指定验证规则 // 指定验证规则
$v = new Validate(); $v = new Validate();
$v->rule($validate); $v->rule($validate);
@@ -312,7 +310,7 @@ abstract class Dispatch
$v = new $class(); $v = new $class();
if(!empty($scene)) { if (!empty($scene)) {
$v->scene($scene); $v->scene($scene);
} }
} }
@@ -336,14 +334,26 @@ abstract class Dispatch
abstract public function exec(); 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; $this->request = $this->app->request;
} }

View File

@@ -1030,13 +1030,29 @@ abstract class Rule
return call_user_func_array([$this, 'setOption'], $args); 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'); $this->router = Container::pull('route');
} }

View File

@@ -218,7 +218,7 @@ class Url
$controller = empty($path) ? $controller : array_pop($path); $controller = empty($path) ? $controller : array_pop($path);
$url = $controller . '/' . $action; $url = $controller . '/' . $action;
$auto = $this->route->getName('__think_auto_route__'); $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); $module = empty($path) ? $request->layer() : array_pop($path);
$url = $module . '/' . $url; $url = $module . '/' . $url;
} }
@@ -368,7 +368,7 @@ class Url
} }
if ($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; $checkDomain = $domain && is_string($domain) ? $domain : null;
$rule = $this->route->getName($checkName, $checkDomain); $rule = $this->route->getName($checkName, $checkDomain);

View File

@@ -20,7 +20,7 @@ class Cache implements SessionHandlerInterface
/** @var CacheInterface */ /** @var CacheInterface */
protected $handler; protected $handler;
/** @var integer */ /** @var int */
protected $expire; protected $expire;
/** @var string */ /** @var string */

View File

@@ -0,0 +1,243 @@
<?php
declare (strict_types=1);
namespace think\traits;
/**
* 域名处理 trait
*/
trait DomainHandler
{
/**
* 域名(含协议及端口)
* @var string
*/
protected $domain;
/**
* 域名根
* @var string
*/
protected $rootDomain = '';
/**
* 子域名
* @var string
*/
protected $subDomain = '';
/**
* 泛域名
* @var string
*/
protected $panDomain = '';
/**
* 特殊域名根标识 用于识别com.cn org.cn 这种
* @var array
*/
protected $domainSpecialSuffix = ['com', 'net', 'org', 'edu', 'gov', 'mil', 'co', 'info'];
/**
* 设置当前域名
* @access public
* @param string $domain 域名
* @return $this
*/
public function setDomain(string $domain)
{
$this->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;
}
}

View File

@@ -0,0 +1,213 @@
<?php
declare (strict_types=1);
namespace think\traits;
/**
* HTTP方法处理 trait
*/
trait HttpMethodHandler
{
/**
* 请求类型
* @var string
*/
protected $varMethod = '_method';
/**
* 表单ajax伪装变量
* @var string
*/
protected $varAjax = '_ajax';
/**
* 表单pjax伪装变量
* @var string
*/
protected $varPjax = '_pjax';
/**
* 请求类型
* @var string
*/
protected $method;
/**
* 获取请求类型
* @access public
* @param bool $origin 是否获取原始请求类型
* @return string
*/
public function method(bool $origin = false): string
{
if ($origin) {
return $this->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;
}
}

View File

@@ -0,0 +1,233 @@
<?php
declare (strict_types=1);
namespace think\traits;
/**
* URL处理 trait
*/
trait UrlHandler
{
/**
* 当前URL地址
* @var string
*/
protected $url;
/**
* 当前URL地址 不含QUERY_STRING
* @var string
*/
protected $baseUrl;
/**
* 当前执行的文件 SCRIPT_NAME
* @var string
*/
protected $baseFile;
/**
* URL访问根地址
* @var string
*/
protected $root;
/**
* 设置当前请求的URL
* @access public
* @param string $url URL地址
* @return $this
*/
public function setUrl(string $url)
{
$this->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'];
}
}