+ *
+ * @internal
+ */
+class PhpToken implements \Stringable
+{
+ /**
+ * @var int
+ */
+ public $id;
+
+ /**
+ * @var string
+ */
+ public $text;
+
+ /**
+ * @var int
+ */
+ public $line;
+
+ /**
+ * @var int
+ */
+ public $pos;
+
+ public function __construct(int $id, string $text, int $line = -1, int $position = -1)
+ {
+ $this->id = $id;
+ $this->text = $text;
+ $this->line = $line;
+ $this->pos = $position;
+ }
+
+ public function getTokenName(): ?string
+ {
+ if ('UNKNOWN' === $name = token_name($this->id)) {
+ $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text;
+ }
+
+ return $name;
+ }
+
+ /**
+ * @param int|string|array $kind
+ */
+ public function is($kind): bool
+ {
+ foreach ((array) $kind as $value) {
+ if (\in_array($value, [$this->id, $this->text], true)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function isIgnorable(): bool
+ {
+ return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true);
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->text;
+ }
+
+ /**
+ * @return static[]
+ */
+ public static function tokenize(string $code, int $flags = 0): array
+ {
+ $line = 1;
+ $position = 0;
+ $tokens = token_get_all($code, $flags);
+ foreach ($tokens as $index => $token) {
+ if (\is_string($token)) {
+ $id = \ord($token);
+ $text = $token;
+ } else {
+ [$id, $text, $line] = $token;
+ }
+ $tokens[$index] = new static($id, $text, $line, $position);
+ $position += \strlen($text);
+ }
+
+ return $tokens;
+ }
+}
diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php
new file mode 100644
index 0000000..72f1081
--- /dev/null
+++ b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php
@@ -0,0 +1,7 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Caster;
+
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+final class MysqliCaster
+{
+ public static function castMysqliDriver(\mysqli_driver $c, array $a, Stub $stub, bool $isNested): array
+ {
+ foreach ($a as $k => $v) {
+ if (isset($c->$k)) {
+ $a[$k] = $c->$k;
+ }
+ }
+
+ return $a;
+ }
+}
diff --git a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php
index 819a618..2a74b25 100644
--- a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php
+++ b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php
@@ -134,7 +134,7 @@ class ReflectionCaster
array_unshift($trace, [
'function' => 'yield',
'file' => $function->getExecutingFile(),
- 'line' => $function->getExecutingLine() - 1,
+ 'line' => $function->getExecutingLine() - (int) (\PHP_VERSION_ID < 80100),
]);
$trace[] = $frame;
$a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1);
@@ -263,15 +263,17 @@ class ReflectionCaster
unset($a[$prefix.'allowsNull']);
}
- try {
- $a[$prefix.'default'] = $v = $c->getDefaultValue();
- if ($c->isDefaultValueConstant()) {
- $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v);
- }
- if (null === $v) {
- unset($a[$prefix.'allowsNull']);
+ if ($c->isOptional()) {
+ try {
+ $a[$prefix.'default'] = $v = $c->getDefaultValue();
+ if ($c->isDefaultValueConstant()) {
+ $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v);
+ }
+ if (null === $v) {
+ unset($a[$prefix.'allowsNull']);
+ }
+ } catch (\ReflectionException $e) {
}
- } catch (\ReflectionException $e) {
}
return $a;
diff --git a/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php
index e7a0f64..9f5a375 100644
--- a/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php
+++ b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php
@@ -1,4 +1,5 @@
['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'],
'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'],
+ 'mysqli_driver' => ['Symfony\Component\VarDumper\Caster\MysqliCaster', 'castMysqliDriver'],
+
'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'],
':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'],
diff --git a/vendor/symfony/var-dumper/Dumper/AbstractDumper.php b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php
index 4ddaf5e..2d3bb01 100644
--- a/vendor/symfony/var-dumper/Dumper/AbstractDumper.php
+++ b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php
@@ -46,8 +46,7 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
{
$this->flags = $flags;
$this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8');
- $this->decimalPoint = localeconv();
- $this->decimalPoint = $this->decimalPoint['decimal_point'];
+ $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point'];
$this->setOutput($output ?: static::$defaultOutput);
if (!$output && \is_string(static::$defaultOutput)) {
static::$defaultOutput = $this->outputStream;
@@ -122,8 +121,7 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
*/
public function dump(Data $data, $output = null)
{
- $this->decimalPoint = localeconv();
- $this->decimalPoint = $this->decimalPoint['decimal_point'];
+ $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point'];
if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(\LC_NUMERIC, 0) : null) {
setlocale(\LC_NUMERIC, 'C');
diff --git a/vendor/symfony/var-dumper/Dumper/HtmlDumper.php b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php
index 8409a0c..4db0f08 100644
--- a/vendor/symfony/var-dumper/Dumper/HtmlDumper.php
+++ b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php
@@ -371,7 +371,7 @@ return function (root, x) {
if (/\bsf-dump-toggle\b/.test(a.className)) {
e.preventDefault();
if (!toggle(a, isCtrlKey(e))) {
- var r = doc.getElementById(a.getAttribute('href').substr(1)),
+ var r = doc.getElementById(a.getAttribute('href').slice(1)),
s = r.previousSibling,
f = r.parentNode,
t = a.parentNode;
@@ -438,7 +438,7 @@ return function (root, x) {
toggle(a);
}
} else if (/\bsf-dump-ref\b/.test(elt.className) && (a = elt.getAttribute('href'))) {
- a = a.substr(1);
+ a = a.slice(1);
elt.className += ' '+a;
if (/[\[{]$/.test(elt.previousSibling.nodeValue)) {
@@ -978,7 +978,7 @@ EOHTML
}
$this->lastDepth = $depth;
- $this->line = mb_convert_encoding($this->line, 'HTML-ENTITIES', 'UTF-8');
+ $this->line = mb_encode_numericentity($this->line, [0x80, 0xFFFF, 0, 0xFFFF], 'UTF-8');
if (-1 === $depth) {
AbstractDumper::dumpLine(0);
diff --git a/vendor/symfony/var-dumper/LICENSE b/vendor/symfony/var-dumper/LICENSE
index c1f0aac..a843ec1 100644
--- a/vendor/symfony/var-dumper/LICENSE
+++ b/vendor/symfony/var-dumper/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2021 Fabien Potencier
+Copyright (c) 2014-2022 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/symfony/var-dumper/Resources/bin/var-dump-server b/vendor/symfony/var-dumper/Resources/bin/var-dump-server
old mode 100644
new mode 100755
diff --git a/vendor/topthink/framework/.gitignore b/vendor/topthink/framework/.gitignore
new file mode 100644
index 0000000..b267fba
--- /dev/null
+++ b/vendor/topthink/framework/.gitignore
@@ -0,0 +1,7 @@
+/vendor
+composer.phar
+composer.lock
+.DS_Store
+Thumbs.db
+/.idea
+/.vscode
\ No newline at end of file
diff --git a/vendor/topthink/framework/README.md b/vendor/topthink/framework/README.md
index 0ea03c9..aa16486 100644
--- a/vendor/topthink/framework/README.md
+++ b/vendor/topthink/framework/README.md
@@ -35,7 +35,7 @@ ThinkPHP6.0底层架构采用PHP7.1改写和进一步优化。
* 统一和精简大量用法
-> ThinkPHP6.0的运行环境要求PHP7.1+,兼容PHP8.0。
+> ThinkPHP6.0的运行环境要求PHP7.2+,兼容PHP8.1
## 安装
diff --git a/vendor/topthink/framework/composer.json b/vendor/topthink/framework/composer.json
index 4be2ae0..90793d3 100644
--- a/vendor/topthink/framework/composer.json
+++ b/vendor/topthink/framework/composer.json
@@ -27,13 +27,15 @@
"psr/log": "~1.0",
"psr/container": "~1.0",
"psr/simple-cache": "^1.0",
+ "psr/http-message": "^1.0",
"topthink/think-orm": "^2.0",
"topthink/think-helper": "^3.1.1"
},
"require-dev": {
"mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.2",
- "phpunit/phpunit": "^7.0"
+ "phpunit/phpunit": "^7.0",
+ "guzzlehttp/psr7": "^2.1.0"
},
"autoload": {
"files": [],
diff --git a/vendor/topthink/framework/src/helper.php b/vendor/topthink/framework/src/helper.php
index 650edcb..e436e14 100644
--- a/vendor/topthink/framework/src/helper.php
+++ b/vendor/topthink/framework/src/helper.php
@@ -149,7 +149,7 @@ if (!function_exists('cookie')) {
{
if (is_null($value)) {
// 删除
- Cookie::delete($name);
+ Cookie::delete($name, $option ?: []);
} elseif ('' === $value) {
// 获取
return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1)) : Cookie::get($name);
diff --git a/vendor/topthink/framework/src/think/App.php b/vendor/topthink/framework/src/think/App.php
index 056a341..073a41a 100644
--- a/vendor/topthink/framework/src/think/App.php
+++ b/vendor/topthink/framework/src/think/App.php
@@ -39,7 +39,7 @@ use think\initializer\RegisterService;
*/
class App extends Container
{
- const VERSION = '6.0.9';
+ const VERSION = '6.0.12LTS';
/**
* 应用调试模式
@@ -168,7 +168,7 @@ class App extends Container
*/
public function __construct(string $rootPath = '')
{
- $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR;
+ $this->thinkPath = realpath(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
$this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
$this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
$this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
@@ -450,13 +450,8 @@ class App extends Container
// 加载全局初始化文件
$this->load();
- // 加载框架默认语言包
- $langSet = $this->lang->defaultLangSet();
-
- $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php');
-
// 加载应用默认语言包
- $this->loadLangPack($langSet);
+ $this->loadLangPack();
// 监听AppInit
$this->event->trigger(AppInit::class);
@@ -482,25 +477,13 @@ class App extends Container
/**
* 加载语言包
- * @param string $langset 语言
* @return void
*/
- public function loadLangPack($langset)
+ public function loadLangPack()
{
- if (empty($langset)) {
- return;
- }
-
- // 加载系统语言包
- $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
- $this->lang->load($files);
-
- // 加载扩展(自定义)语言包
- $list = $this->config->get('lang.extend_list', []);
-
- if (isset($list[$langset])) {
- $this->lang->load($list[$langset]);
- }
+ // 加载默认语言包
+ $langSet = $this->lang->defaultLangSet();
+ $this->lang->switchLangSet($langSet);
}
/**
diff --git a/vendor/topthink/framework/src/think/Container.php b/vendor/topthink/framework/src/think/Container.php
index 74026bb..e3a4341 100644
--- a/vendor/topthink/framework/src/think/Container.php
+++ b/vendor/topthink/framework/src/think/Container.php
@@ -27,6 +27,7 @@ use ReflectionMethod;
use think\exception\ClassNotFoundException;
use think\exception\FuncNotFoundException;
use think\helper\Str;
+use Traversable;
/**
* 容器管理类 支持PSR-11
@@ -520,34 +521,38 @@ class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, C
$this->delete($name);
}
- public function offsetExists($key)
+ #[\ReturnTypeWillChange]
+ public function offsetExists($key): bool
{
return $this->exists($key);
}
+ #[\ReturnTypeWillChange]
public function offsetGet($key)
{
return $this->make($key);
}
+ #[\ReturnTypeWillChange]
public function offsetSet($key, $value)
{
$this->bind($key, $value);
}
+ #[\ReturnTypeWillChange]
public function offsetUnset($key)
{
$this->delete($key);
}
//Countable
- public function count()
+ public function count(): int
{
return count($this->instances);
}
//IteratorAggregate
- public function getIterator()
+ public function getIterator(): Traversable
{
return new ArrayIterator($this->instances);
}
diff --git a/vendor/topthink/framework/src/think/Cookie.php b/vendor/topthink/framework/src/think/Cookie.php
index ebbfd64..04774a6 100644
--- a/vendor/topthink/framework/src/think/Cookie.php
+++ b/vendor/topthink/framework/src/think/Cookie.php
@@ -158,11 +158,13 @@ class Cookie
* Cookie删除
* @access public
* @param string $name cookie名称
+ * @param array $options cookie参数
* @return void
*/
- public function delete(string $name): void
+ public function delete(string $name, array $options = []): void
{
- $this->setCookie($name, '', time() - 3600, $this->config);
+ $config = array_merge($this->config, array_change_key_case($options));
+ $this->setCookie($name, '', time() - 3600, $config);
}
/**
diff --git a/vendor/topthink/framework/src/think/Env.php b/vendor/topthink/framework/src/think/Env.php
index 4c26b33..9e1b7f4 100644
--- a/vendor/topthink/framework/src/think/Env.php
+++ b/vendor/topthink/framework/src/think/Env.php
@@ -26,6 +26,17 @@ class Env implements ArrayAccess
*/
protected $data = [];
+ /**
+ * 数据转换映射
+ * @var array
+ */
+ protected $convert = [
+ 'true' => true,
+ 'false' => false,
+ 'off' => false,
+ 'on' => true,
+ ];
+
public function __construct()
{
$this->data = $_ENV;
@@ -39,7 +50,7 @@ class Env implements ArrayAccess
*/
public function load(string $file): void
{
- $env = parse_ini_file($file, true) ?: [];
+ $env = parse_ini_file($file, true, INI_SCANNER_RAW) ?: [];
$this->set($env);
}
@@ -57,9 +68,14 @@ class Env implements ArrayAccess
}
$name = strtoupper(str_replace('.', '_', $name));
-
if (isset($this->data[$name])) {
- return $this->data[$name];
+ $result = $this->data[$name];
+
+ if (is_string($result) && isset($this->convert[$result])) {
+ return $this->convert[$result];
+ }
+
+ return $result;
}
return $this->getEnv($name, $default);
@@ -159,21 +175,25 @@ class Env implements ArrayAccess
}
// ArrayAccess
+ #[\ReturnTypeWillChange]
public function offsetSet($name, $value): void
{
$this->set($name, $value);
}
+ #[\ReturnTypeWillChange]
public function offsetExists($name): bool
{
return $this->__isset($name);
}
- public function offsetUnset($name)
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($name): void
{
throw new Exception('not support: unset');
}
+ #[\ReturnTypeWillChange]
public function offsetGet($name)
{
return $this->get($name);
diff --git a/vendor/topthink/framework/src/think/File.php b/vendor/topthink/framework/src/think/File.php
index f7c37bd..087e808 100644
--- a/vendor/topthink/framework/src/think/File.php
+++ b/vendor/topthink/framework/src/think/File.php
@@ -176,7 +176,7 @@ class File extends SplFileInfo
$this->hashName = call_user_func($rule);
break;
default:
- $this->hashName = date('Ymd') . DIRECTORY_SEPARATOR . md5((string) microtime(true));
+ $this->hashName = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true) . $this->getPathname());
break;
}
}
diff --git a/vendor/topthink/framework/src/think/Lang.php b/vendor/topthink/framework/src/think/Lang.php
index 0b79b76..0226f81 100644
--- a/vendor/topthink/framework/src/think/Lang.php
+++ b/vendor/topthink/framework/src/think/Lang.php
@@ -18,6 +18,8 @@ namespace think;
*/
class Lang
{
+ protected $app;
+
/**
* 配置参数
* @var array
@@ -62,15 +64,26 @@ class Lang
* @access public
* @param array $config
*/
- public function __construct(array $config = [])
+ public function __construct(App $app, array $config = [])
{
$this->config = array_merge($this->config, array_change_key_case($config));
$this->range = $this->config['default_lang'];
+ $this->app = $app;
+ }
+
+ public static function __make(App $app, Config $config)
+ {
+ return new static($app, $config->get('lang'));
}
- public static function __make(Config $config)
+ /**
+ * 获取当前语言配置
+ * @access public
+ * @return array
+ */
+ public function getConfig(): array
{
- return new static($config->get('lang'));
+ return $this->config;
}
/**
@@ -104,6 +117,35 @@ class Lang
return $this->config['default_lang'];
}
+ /**
+ * 切换语言
+ * @access public
+ * @param string $langset 语言
+ * @return void
+ */
+ public function switchLangSet(string $langset)
+ {
+ if (empty($langset)) {
+ return;
+ }
+
+ // 加载系统语言包
+ $this->load([
+ $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php',
+ ]);
+
+ // 加载系统语言包
+ $files = glob($this->app->getAppPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
+ $this->load($files);
+
+ // 加载扩展(自定义)语言包
+ $list = $this->app->config->get('lang.extend_list', []);
+
+ if (isset($list[$langset])) {
+ $this->load($list[$langset]);
+ }
+ }
+
/**
* 加载语言定义(不区分大小写)
* @access public
@@ -202,6 +244,10 @@ class Lang
{
$range = $range ?: $this->range;
+ if (!isset($this->lang[$range])) {
+ $this->switchLangSet($range);
+ }
+
// 空参数返回所有定义
if (is_null($name)) {
return $this->lang[$range] ?? [];
@@ -241,6 +287,7 @@ class Lang
/**
* 自动侦测设置获取语言选择
+ * @deprecated
* @access public
* @param Request $request
* @return string
@@ -280,6 +327,7 @@ class Lang
/**
* 保存当前语言到Cookie
+ * @deprecated
* @access public
* @param Cookie $cookie Cookie对象
* @return void
diff --git a/vendor/topthink/framework/src/think/Request.php b/vendor/topthink/framework/src/think/Request.php
index 1c15f63..fe9938d 100644
--- a/vendor/topthink/framework/src/think/Request.php
+++ b/vendor/topthink/framework/src/think/Request.php
@@ -13,6 +13,7 @@ declare (strict_types = 1);
namespace think;
use ArrayAccess;
+use think\facade\Lang;
use think\file\UploadedFile;
use think\route\Rule;
@@ -1227,7 +1228,7 @@ class Request implements ArrayAccess
7 => 'file write error',
];
- $msg = $fileUploadErrors[$error];
+ $msg = Lang::get($fileUploadErrors[$error]);
throw new Exception($msg, $error);
}
@@ -2150,19 +2151,23 @@ class Request implements ArrayAccess
}
// ArrayAccess
+ #[\ReturnTypeWillChange]
public function offsetExists($name): bool
{
return $this->has($name);
}
+ #[\ReturnTypeWillChange]
public function offsetGet($name)
{
return $this->param($name);
}
+ #[\ReturnTypeWillChange]
public function offsetSet($name, $value)
{}
+ #[\ReturnTypeWillChange]
public function offsetUnset($name)
{}
diff --git a/vendor/topthink/framework/src/think/Response.php b/vendor/topthink/framework/src/think/Response.php
index a8a61ff..49f26dc 100644
--- a/vendor/topthink/framework/src/think/Response.php
+++ b/vendor/topthink/framework/src/think/Response.php
@@ -130,16 +130,19 @@ abstract class Response
// 处理输出数据
$data = $this->getContent();
- if (!headers_sent() && !empty($this->header)) {
- // 发送状态码
- http_response_code($this->code);
- // 发送头部信息
- foreach ($this->header as $name => $val) {
- header($name . (!is_null($val) ? ':' . $val : ''));
+ if (!headers_sent()) {
+ if (!empty($this->header)) {
+ // 发送状态码
+ http_response_code($this->code);
+ // 发送头部信息
+ foreach ($this->header as $name => $val) {
+ header($name . (!is_null($val) ? ':' . $val : ''));
+ }
+ }
+
+ if ($this->cookie) {
+ $this->cookie->save();
}
- }
- if ($this->cookie) {
- $this->cookie->save();
}
$this->sendData($data);
diff --git a/vendor/topthink/framework/src/think/cache/Driver.php b/vendor/topthink/framework/src/think/cache/Driver.php
index 5813c7b..37ffbc9 100644
--- a/vendor/topthink/framework/src/think/cache/Driver.php
+++ b/vendor/topthink/framework/src/think/cache/Driver.php
@@ -249,7 +249,7 @@ abstract class Driver implements CacheInterface, CacheHandlerInterface
* @param string $data 缓存数据
* @return mixed
*/
- protected function unserialize(string $data)
+ protected function unserialize($data)
{
if (is_numeric($data)) {
return $data;
diff --git a/vendor/topthink/framework/src/think/console/command/Make.php b/vendor/topthink/framework/src/think/console/command/Make.php
index 662b337..a74e9e8 100644
--- a/vendor/topthink/framework/src/think/console/command/Make.php
+++ b/vendor/topthink/framework/src/think/console/command/Make.php
@@ -67,7 +67,7 @@ abstract class Make extends Command
protected function getPathName(string $name): string
{
- $name = str_replace('app\\', '', $name);
+ $name = substr($name, 4);
return $this->app->getBasePath() . ltrim(str_replace('\\', '/', $name), '/') . '.php';
}
diff --git a/vendor/topthink/framework/src/think/console/command/RouteList.php b/vendor/topthink/framework/src/think/console/command/RouteList.php
index ed579b8..e9d4c5d 100644
--- a/vendor/topthink/framework/src/think/console/command/RouteList.php
+++ b/vendor/topthink/framework/src/think/console/command/RouteList.php
@@ -91,14 +91,13 @@ class RouteList extends Command
foreach ($routeList as $item) {
$item['route'] = $item['route'] instanceof \Closure ? '' : $item['route'];
+ $row = [$item['rule'], $item['route'], $item['method'], $item['name']];
if ($this->input->hasOption('more')) {
- $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $item['domain'], json_encode($item['option']), json_encode($item['pattern'])];
- } else {
- $item = [$item['rule'], $item['route'], $item['method'], $item['name']];
+ array_push($row, $item['domain'], json_encode($item['option']), json_encode($item['pattern']));
}
- $rows[] = $item;
+ $rows[] = $row;
}
if ($this->input->getOption('sort')) {
diff --git a/vendor/topthink/framework/src/think/console/input/Option.php b/vendor/topthink/framework/src/think/console/input/Option.php
index 19c7e1e..d7e6514 100644
--- a/vendor/topthink/framework/src/think/console/input/Option.php
+++ b/vendor/topthink/framework/src/think/console/input/Option.php
@@ -18,7 +18,7 @@ namespace think\console\input;
class Option
{
// 无需传值
- const VALUE_NONE = 1;
+ const VALUE_NONE = 1;
// 必须传值
const VALUE_REQUIRED = 2;
// 可选传值
@@ -30,13 +30,13 @@ class Option
* 选项名
* @var string
*/
- private $name;
+ private $name = '';
/**
* 选项短名称
* @var string
*/
- private $shortcut;
+ private $shortcut = '';
/**
* 选项类型
@@ -54,7 +54,7 @@ class Option
* 选项描述
* @var string
*/
- private $description;
+ private $description = '';
/**
* 构造方法
@@ -76,10 +76,10 @@ class Option
}
if (empty($shortcut)) {
- $shortcut = null;
+ $shortcut = '';
}
- if (null !== $shortcut) {
+ if ('' !== $shortcut) {
if (is_array($shortcut)) {
$shortcut = implode('|', $shortcut);
}
@@ -114,7 +114,7 @@ class Option
* 获取短名称
* @return string
*/
- public function getShortcut()
+ public function getShortcut(): string
{
return $this->shortcut;
}
@@ -123,7 +123,7 @@ class Option
* 获取选项名
* @return string
*/
- public function getName()
+ public function getName(): string
{
return $this->name;
}
@@ -132,7 +132,7 @@ class Option
* 是否可以设置值
* @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false
*/
- public function acceptValue()
+ public function acceptValue(): bool
{
return $this->isValueRequired() || $this->isValueOptional();
}
@@ -141,7 +141,7 @@ class Option
* 是否必须
* @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false
*/
- public function isValueRequired()
+ public function isValueRequired(): bool
{
return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
}
@@ -150,7 +150,7 @@ class Option
* 是否可选
* @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false
*/
- public function isValueOptional()
+ public function isValueOptional(): bool
{
return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
}
@@ -159,7 +159,7 @@ class Option
* 选项值是否接受数组
* @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false
*/
- public function isArray()
+ public function isArray(): bool
{
return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
}
@@ -199,7 +199,7 @@ class Option
* 获取描述文字
* @return string
*/
- public function getDescription()
+ public function getDescription(): string
{
return $this->description;
}
@@ -209,7 +209,7 @@ class Option
* @param Option $option
* @return bool
*/
- public function equals(Option $option)
+ public function equals(Option $option): bool
{
return $option->getName() === $this->getName()
&& $option->getShortcut() === $this->getShortcut()
diff --git a/vendor/topthink/framework/src/think/filesystem/Driver.php b/vendor/topthink/framework/src/think/filesystem/Driver.php
index 6712959..0e61cf4 100644
--- a/vendor/topthink/framework/src/think/filesystem/Driver.php
+++ b/vendor/topthink/framework/src/think/filesystem/Driver.php
@@ -17,6 +17,7 @@ use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\Cached\CachedAdapter;
use League\Flysystem\Cached\Storage\Memory as MemoryStore;
use League\Flysystem\Filesystem;
+use RuntimeException;
use think\Cache;
use think\File;
@@ -91,6 +92,16 @@ abstract class Driver
return $path;
}
+ protected function concatPathToUrl($url, $path)
+ {
+ return rtrim($url, '/') . '/' . ltrim($path, '/');
+ }
+
+ public function url(string $path): string
+ {
+ throw new RuntimeException('This driver does not support retrieving URLs.');
+ }
+
/**
* 保存文件
* @param string $path 路径
diff --git a/vendor/topthink/framework/src/think/filesystem/driver/Local.php b/vendor/topthink/framework/src/think/filesystem/driver/Local.php
index c10ccc3..31172d0 100644
--- a/vendor/topthink/framework/src/think/filesystem/driver/Local.php
+++ b/vendor/topthink/framework/src/think/filesystem/driver/Local.php
@@ -41,4 +41,12 @@ class Local extends Driver
$permissions
);
}
+
+ public function url(string $path): string
+ {
+ if (isset($this->config['url'])) {
+ return $this->concatPathToUrl($this->config['url'], $path);
+ }
+ return parent::url($path);
+ }
}
diff --git a/vendor/topthink/framework/src/think/log/driver/File.php b/vendor/topthink/framework/src/think/log/driver/File.php
index e5682fc..1f726b0 100644
--- a/vendor/topthink/framework/src/think/log/driver/File.php
+++ b/vendor/topthink/framework/src/think/log/driver/File.php
@@ -139,7 +139,9 @@ class File implements LogHandlerInterface
try {
if (count($files) > $this->config['max_files']) {
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) {});
unlink($files[0]);
+ restore_error_handler();
}
} catch (\Exception $e) {
//
diff --git a/vendor/topthink/framework/src/think/middleware/LoadLangPack.php b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php
index 478e29c..d6bf6a4 100644
--- a/vendor/topthink/framework/src/think/middleware/LoadLangPack.php
+++ b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php
@@ -14,6 +14,8 @@ namespace think\middleware;
use Closure;
use think\App;
+use think\Config;
+use think\Cookie;
use think\Lang;
use think\Request;
use think\Response;
@@ -24,13 +26,14 @@ use think\Response;
class LoadLangPack
{
protected $app;
-
protected $lang;
+ protected $config;
- public function __construct(App $app, Lang $lang)
+ public function __construct(App $app, Lang $lang, Config $config)
{
- $this->app = $app;
- $this->lang = $lang;
+ $this->app = $app;
+ $this->lang = $lang;
+ $this->config = $lang->getConfig();
}
/**
@@ -43,19 +46,71 @@ class LoadLangPack
public function handle($request, Closure $next)
{
// 自动侦测当前语言
- $langset = $this->lang->detect($request);
+ $langset = $this->detect($request);
if ($this->lang->defaultLangSet() != $langset) {
- // 加载系统语言包
- $this->lang->load([
- $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php',
- ]);
-
- $this->app->LoadLangPack($langset);
+ $this->lang->switchLangSet($langset);
}
- $this->lang->saveToCookie($this->app->cookie);
+ $this->saveToCookie($this->app->cookie, $langset);
return $next($request);
}
+
+ /**
+ * 自动侦测设置获取语言选择
+ * @access protected
+ * @param Request $request
+ * @return string
+ */
+ protected function detect(Request $request): string
+ {
+ // 自动侦测设置获取语言选择
+ $langSet = '';
+
+ if ($request->get($this->config['detect_var'])) {
+ // url中设置了语言变量
+ $langSet = strtolower($request->get($this->config['detect_var']));
+ } elseif ($request->header($this->config['header_var'])) {
+ // Header中设置了语言变量
+ $langSet = strtolower($request->header($this->config['header_var']));
+ } elseif ($request->cookie($this->config['cookie_var'])) {
+ // Cookie中设置了语言变量
+ $langSet = strtolower($request->cookie($this->config['cookie_var']));
+ } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) {
+ // 自动侦测浏览器语言
+ $match = preg_match('/^([a-z\d\-]+)/i', $request->server('HTTP_ACCEPT_LANGUAGE'), $matches);
+ if ($match) {
+ $langSet = strtolower($matches[1]);
+ if (isset($this->config['accept_language'][$langSet])) {
+ $langSet = $this->config['accept_language'][$langSet];
+ }
+ }
+ }
+
+ if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) {
+ // 合法的语言
+ $range = $langSet;
+ $this->lang->setLangSet($range);
+ } else {
+ $range = $this->lang->getLangSet();
+ }
+
+ return $range;
+ }
+
+ /**
+ * 保存当前语言到Cookie
+ * @access protected
+ * @param Cookie $cookie Cookie对象
+ * @param string $langSet 语言
+ * @return void
+ */
+ protected function saveToCookie(Cookie $cookie, string $langSet)
+ {
+ if ($this->config['use_cookie']) {
+ $cookie->set($this->config['cookie_var'], $langSet);
+ }
+ }
+
}
diff --git a/vendor/topthink/framework/src/think/route/Dispatch.php b/vendor/topthink/framework/src/think/route/Dispatch.php
index e77e299..0599dc1 100644
--- a/vendor/topthink/framework/src/think/route/Dispatch.php
+++ b/vendor/topthink/framework/src/think/route/Dispatch.php
@@ -12,6 +12,7 @@ declare (strict_types = 1);
namespace think\route;
+use Psr\Http\Message\ResponseInterface;
use think\App;
use think\Container;
use think\Request;
@@ -94,6 +95,12 @@ abstract class Dispatch
{
if ($data instanceof Response) {
$response = $data;
+ } elseif ($data instanceof ResponseInterface) {
+ $response = Response::create($data->getBody()->getContents(), 'html', $data->getStatusCode());
+
+ foreach ($data->getHeaders() as $header => $values) {
+ $response->header([$header => implode(", ", $values)]);
+ }
} elseif (!is_null($data)) {
// 默认自动识别响应输出类型
$type = $this->request->isJson() ? 'json' : 'html';
diff --git a/vendor/topthink/framework/src/think/route/Resource.php b/vendor/topthink/framework/src/think/route/Resource.php
index bb37cb6..5185cdc 100644
--- a/vendor/topthink/framework/src/think/route/Resource.php
+++ b/vendor/topthink/framework/src/think/route/Resource.php
@@ -213,7 +213,7 @@ class Resource extends RuleGroup
}
/**
- * 绑定资源模型
+ * 绑定资源中间件
* @access public
* @param array|string $name 资源类型或者中间件定义
* @param array|string $middleware 中间件定义
diff --git a/vendor/topthink/framework/src/think/route/Rule.php b/vendor/topthink/framework/src/think/route/Rule.php
index 31b2e0e..2d864cd 100644
--- a/vendor/topthink/framework/src/think/route/Rule.php
+++ b/vendor/topthink/framework/src/think/route/Rule.php
@@ -532,6 +532,17 @@ abstract class Rule
return $this->setOption('view', $view);
}
+ /**
+ * 通过闭包检查路由是否匹配
+ * @access public
+ * @param callable $match 闭包
+ * @return $this
+ */
+ public function match(callable $match)
+ {
+ return $this->setOption('match', $match);
+ }
+
/**
* 设置路由完整匹配
* @access public
@@ -694,6 +705,13 @@ abstract class Rule
*/
protected function checkOption(array $option, Request $request): bool
{
+ // 检查当前路由是否匹配
+ if (isset($option['match']) && is_callable($option['match'])) {
+ if (false === $option['match']($this, $request)) {
+ return false;
+ }
+ }
+
// 请求类型检测
if (!empty($option['method'])) {
if (is_string($option['method']) && false === stripos($option['method'], $request->method())) {
diff --git a/vendor/topthink/framework/src/think/route/RuleItem.php b/vendor/topthink/framework/src/think/route/RuleItem.php
index 1f9aa52..e4e3246 100644
--- a/vendor/topthink/framework/src/think/route/RuleItem.php
+++ b/vendor/topthink/framework/src/think/route/RuleItem.php
@@ -196,7 +196,7 @@ class RuleItem extends Rule
$url = $this->urlSuffixCheck($request, $url, $option);
if (is_null($match)) {
- $match = $this->match($url, $option, $pattern, $completeMatch);
+ $match = $this->checkMatch($url, $option, $pattern, $completeMatch);
}
if (false !== $match) {
@@ -252,7 +252,7 @@ class RuleItem extends Rule
* @param bool $completeMatch 是否完全匹配
* @return array|false
*/
- private function match(string $url, array $option, array $pattern, bool $completeMatch)
+ private function checkMatch(string $url, array $option, array $pattern, bool $completeMatch)
{
if (isset($option['complete_match'])) {
$completeMatch = $option['complete_match'];
diff --git a/vendor/topthink/framework/src/think/route/Url.php b/vendor/topthink/framework/src/think/route/Url.php
index 8dd410c..b3f8b9f 100644
--- a/vendor/topthink/framework/src/think/route/Url.php
+++ b/vendor/topthink/framework/src/think/route/Url.php
@@ -320,7 +320,7 @@ class Url
}
if (empty($pattern)) {
- return [rtrim($url, '?/-'), $domain, $suffix];
+ return [rtrim($url, '?-'), $domain, $suffix];
}
$type = $this->route->config('url_common_param');
@@ -331,11 +331,11 @@ class Url
$url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? (string) $vars[$key] : urlencode((string) $vars[$key]), $url);
$keys[] = $key;
$url = str_replace(['/?', '-?'], ['/', '-'], $url);
- $result = [rtrim($url, '?/-'), $domain, $suffix];
+ $result = [rtrim($url, '?-'), $domain, $suffix];
} elseif (2 == $val) {
$url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url);
$url = str_replace(['/?', '-?'], ['/', '-'], $url);
- $result = [rtrim($url, '?/-'), $domain, $suffix];
+ $result = [rtrim($url, '?-'), $domain, $suffix];
} else {
$result = null;
$keys = [];
diff --git a/vendor/topthink/framework/src/think/route/dispatch/Controller.php b/vendor/topthink/framework/src/think/route/dispatch/Controller.php
index 611101b..f18512c 100644
--- a/vendor/topthink/framework/src/think/route/dispatch/Controller.php
+++ b/vendor/topthink/framework/src/think/route/dispatch/Controller.php
@@ -113,6 +113,13 @@ class Controller extends Dispatch
});
}
+ protected function parseActions($actions)
+ {
+ return array_map(function ($item) {
+ return strtolower($item);
+ }, is_string($actions) ? explode(",", $actions) : $actions);
+ }
+
/**
* 使用反射机制注册控制器中间件
* @access public
@@ -128,30 +135,34 @@ class Controller extends Dispatch
$reflectionProperty->setAccessible(true);
$middlewares = $reflectionProperty->getValue($controller);
+ $action = $this->request->action(true);
foreach ($middlewares as $key => $val) {
if (!is_int($key)) {
- if (isset($val['only']) && !in_array($this->request->action(true), array_map(function ($item) {
- return strtolower($item);
- }, is_string($val['only']) ? explode(",", $val['only']) : $val['only']))) {
- continue;
- } elseif (isset($val['except']) && in_array($this->request->action(true), array_map(function ($item) {
- return strtolower($item);
- }, is_string($val['except']) ? explode(',', $val['except']) : $val['except']))) {
- continue;
- } else {
- $val = $key;
- }
+ $middleware = $key;
+ $options = $val;
+ } elseif (isset($val['middleware'])) {
+ $middleware = $val['middleware'];
+ $options = $val['options'] ?? [];
+ } else {
+ $middleware = $val;
+ $options = [];
+ }
+
+ if (isset($options['only']) && !in_array($action, $this->parseActions($options['only']))) {
+ continue;
+ } elseif (isset($options['except']) && in_array($action, $this->parseActions($options['except']))) {
+ continue;
}
- if (is_string($val) && strpos($val, ':')) {
- $val = explode(':', $val);
- if (count($val) > 1) {
- $val = [$val[0], array_slice($val, 1)];
+ if (is_string($middleware) && strpos($middleware, ':')) {
+ $middleware = explode(':', $middleware);
+ if (count($middleware) > 1) {
+ $middleware = [$middleware[0], array_slice($middleware, 1)];
}
}
- $this->app->middleware->controller($val);
+ $this->app->middleware->controller($middleware);
}
}
}
diff --git a/vendor/topthink/framework/tests/AppTest.php b/vendor/topthink/framework/tests/AppTest.php
new file mode 100644
index 0000000..6b86015
--- /dev/null
+++ b/vendor/topthink/framework/tests/AppTest.php
@@ -0,0 +1,215 @@
+ 'class',
+ ];
+
+ public function register()
+ {
+
+ }
+
+ public function boot()
+ {
+
+ }
+}
+
+/**
+ * @property array initializers
+ */
+class AppTest extends TestCase
+{
+ /** @var App */
+ protected $app;
+
+ protected function setUp()
+ {
+ $this->app = new App();
+ }
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ public function testService()
+ {
+ $this->app->register(stdClass::class);
+
+ $this->assertInstanceOf(stdClass::class, $this->app->getService(stdClass::class));
+
+ $service = m::mock(SomeService::class);
+
+ $service->shouldReceive('register')->once();
+
+ $this->app->register($service);
+
+ $this->assertEquals($service, $this->app->getService(SomeService::class));
+
+ $service2 = m::mock(SomeService::class);
+
+ $service2->shouldReceive('register')->once();
+
+ $this->app->register($service2);
+
+ $this->assertEquals($service, $this->app->getService(SomeService::class));
+
+ $this->app->register($service2, true);
+
+ $this->assertEquals($service2, $this->app->getService(SomeService::class));
+
+ $service->shouldReceive('boot')->once();
+ $service2->shouldReceive('boot')->once();
+
+ $this->app->boot();
+ }
+
+ public function testDebug()
+ {
+ $this->app->debug(false);
+
+ $this->assertFalse($this->app->isDebug());
+
+ $this->app->debug(true);
+
+ $this->assertTrue($this->app->isDebug());
+ }
+
+ public function testNamespace()
+ {
+ $namespace = 'test';
+
+ $this->app->setNamespace($namespace);
+
+ $this->assertEquals($namespace, $this->app->getNamespace());
+ }
+
+ public function testVersion()
+ {
+ $this->assertEquals(App::VERSION, $this->app->version());
+ }
+
+ public function testPath()
+ {
+ $rootPath = __DIR__ . DIRECTORY_SEPARATOR;
+
+ $app = new App($rootPath);
+
+ $this->assertEquals($rootPath, $app->getRootPath());
+
+ $this->assertEquals(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR, $app->getThinkPath());
+
+ $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getAppPath());
+
+ $appPath = $rootPath . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR;
+ $app->setAppPath($appPath);
+ $this->assertEquals($appPath, $app->getAppPath());
+
+ $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getBasePath());
+
+ $this->assertEquals($rootPath . 'config' . DIRECTORY_SEPARATOR, $app->getConfigPath());
+
+ $this->assertEquals($rootPath . 'runtime' . DIRECTORY_SEPARATOR, $app->getRuntimePath());
+
+ $runtimePath = $rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR;
+ $app->setRuntimePath($runtimePath);
+ $this->assertEquals($runtimePath, $app->getRuntimePath());
+ }
+
+ /**
+ * @param vfsStreamDirectory $root
+ * @param bool $debug
+ * @return App
+ */
+ protected function prepareAppForInitialize(vfsStreamDirectory $root, $debug = true)
+ {
+ $rootPath = $root->url() . DIRECTORY_SEPARATOR;
+
+ $app = new App($rootPath);
+
+ $initializer = m::mock();
+ $initializer->shouldReceive('init')->once()->with($app);
+
+ $app->instance($initializer->mockery_getName(), $initializer);
+
+ (function () use ($initializer) {
+ $this->initializers = [$initializer->mockery_getName()];
+ })->call($app);
+
+ $env = m::mock(Env::class);
+ $env->shouldReceive('load')->once()->with($rootPath . '.env');
+ $env->shouldReceive('get')->once()->with('config_ext', '.php')->andReturn('.php');
+ $env->shouldReceive('get')->once()->with('app_debug')->andReturn($debug);
+
+ $event = m::mock(Event::class);
+ $event->shouldReceive('trigger')->once()->with(AppInit::class);
+ $event->shouldReceive('bind')->once()->with([]);
+ $event->shouldReceive('listenEvents')->once()->with([]);
+ $event->shouldReceive('subscribe')->once()->with([]);
+
+ $app->instance('env', $env);
+ $app->instance('event', $event);
+
+ return $app;
+ }
+
+ public function testInitialize()
+ {
+ $root = vfsStream::setup('rootDir', null, [
+ '.env' => '',
+ 'app' => [
+ 'common.php' => '',
+ 'event.php' => '[],"listen"=>[],"subscribe"=>[]];',
+ 'provider.php' => ' [
+ 'app.php' => 'prepareAppForInitialize($root, true);
+
+ $app->debug(false);
+
+ $app->initialize();
+
+ $this->assertIsInt($app->getBeginMem());
+ $this->assertIsFloat($app->getBeginTime());
+
+ $this->assertTrue($app->initialized());
+ }
+
+ public function testFactory()
+ {
+ $this->assertInstanceOf(stdClass::class, App::factory(stdClass::class));
+
+ $this->expectException(ClassNotFoundException::class);
+
+ App::factory('SomeClass');
+ }
+
+ public function testParseClass()
+ {
+ $this->assertEquals('app\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class'));
+ $this->app->setNamespace('app2');
+ $this->assertEquals('app2\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class'));
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/CacheTest.php b/vendor/topthink/framework/tests/CacheTest.php
new file mode 100644
index 0000000..5b5a13c
--- /dev/null
+++ b/vendor/topthink/framework/tests/CacheTest.php
@@ -0,0 +1,149 @@
+app = m::mock(App::class)->makePartial();
+
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+
+ $this->cache = new Cache($this->app);
+ }
+
+ public function testGetConfig()
+ {
+ $config = [
+ 'default' => 'file',
+ ];
+
+ $this->config->shouldReceive('get')->with('cache')->andReturn($config);
+
+ $this->assertEquals($config, $this->cache->getConfig());
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->cache->getStoreConfig('foo');
+ }
+
+ public function testCacheManagerInstances()
+ {
+ $this->config->shouldReceive('get')->with("cache.stores.single", null)->andReturn(['type' => 'file']);
+
+ $channel1 = $this->cache->store('single');
+ $channel2 = $this->cache->store('single');
+
+ $this->assertSame($channel1, $channel2);
+ }
+
+ public function testFileCache()
+ {
+ $root = vfsStream::setup();
+
+ $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('file');
+
+ $this->config->shouldReceive('get')->with("cache.stores.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]);
+
+ $this->cache->set('foo', 5);
+ $this->cache->inc('foo');
+ $this->assertEquals(6, $this->cache->get('foo'));
+ $this->cache->dec('foo', 2);
+ $this->assertEquals(4, $this->cache->get('foo'));
+
+ $this->cache->set('bar', true);
+ $this->assertTrue($this->cache->get('bar'));
+
+ $this->cache->set('baz', null);
+ $this->assertNull($this->cache->get('baz'));
+
+ $this->assertTrue($this->cache->has('baz'));
+ $this->cache->delete('baz');
+ $this->assertFalse($this->cache->has('baz'));
+ $this->assertNull($this->cache->get('baz'));
+ $this->assertFalse($this->cache->get('baz', false));
+
+ $this->assertTrue($root->hasChildren());
+ $this->cache->clear();
+ $this->assertFalse($root->hasChildren());
+
+ //tags
+ $this->cache->tag('foo')->set('bar', 'foobar');
+ $this->assertEquals('foobar', $this->cache->get('bar'));
+ $this->cache->tag('foo')->clear();
+ $this->assertFalse($this->cache->has('bar'));
+
+ //multiple
+ $this->cache->setMultiple(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']]);
+ $this->assertEquals(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']], $this->cache->getMultiple(['foo', 'foobar']));
+ $this->assertTrue($this->cache->deleteMultiple(['foo', 'foobar']));
+ }
+
+ public function testRedisCache()
+ {
+ if (extension_loaded('redis')) {
+ return;
+ }
+ $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('redis');
+ $this->config->shouldReceive('get')->with("cache.stores.redis", null)->andReturn(['type' => 'redis']);
+
+ $redis = m::mock('overload:\Predis\Client');
+
+ $redis->shouldReceive("set")->once()->with('foo', 5)->andReturnTrue();
+ $redis->shouldReceive("incrby")->once()->with('foo', 1)->andReturnTrue();
+ $redis->shouldReceive("decrby")->once()->with('foo', 2)->andReturnTrue();
+ $redis->shouldReceive("get")->once()->with('foo')->andReturn('6');
+ $redis->shouldReceive("get")->once()->with('foo')->andReturn('4');
+ $redis->shouldReceive("set")->once()->with('bar', serialize(true))->andReturnTrue();
+ $redis->shouldReceive("set")->once()->with('baz', serialize(null))->andReturnTrue();
+ $redis->shouldReceive("del")->once()->with('baz')->andReturnTrue();
+ $redis->shouldReceive("flushDB")->once()->andReturnTrue();
+ $redis->shouldReceive("set")->once()->with('bar', serialize('foobar'))->andReturnTrue();
+ $redis->shouldReceive("sAdd")->once()->with('tag:' . md5('foo'), 'bar')->andReturnTrue();
+ $redis->shouldReceive("sMembers")->once()->with('tag:' . md5('foo'))->andReturn(['bar']);
+ $redis->shouldReceive("del")->once()->with(['bar'])->andReturnTrue();
+ $redis->shouldReceive("del")->once()->with('tag:' . md5('foo'))->andReturnTrue();
+
+ $this->cache->set('foo', 5);
+ $this->cache->inc('foo');
+ $this->assertEquals(6, $this->cache->get('foo'));
+ $this->cache->dec('foo', 2);
+ $this->assertEquals(4, $this->cache->get('foo'));
+
+ $this->cache->set('bar', true);
+ $this->cache->set('baz', null);
+ $this->cache->delete('baz');
+ $this->cache->clear();
+
+ //tags
+ $this->cache->tag('foo')->set('bar', 'foobar');
+ $this->cache->tag('foo')->clear();
+ }
+}
diff --git a/vendor/topthink/framework/tests/ConfigTest.php b/vendor/topthink/framework/tests/ConfigTest.php
new file mode 100644
index 0000000..271a34f
--- /dev/null
+++ b/vendor/topthink/framework/tests/ConfigTest.php
@@ -0,0 +1,46 @@
+setContent(" 'value1','key2'=>'value2'];");
+ $root->addChild($file);
+
+ $config = new Config();
+
+ $config->load($file->url(), 'test');
+
+ $this->assertEquals('value1', $config->get('test.key1'));
+ $this->assertEquals('value2', $config->get('test.key2'));
+
+ $this->assertSame(['key1' => 'value1', 'key2' => 'value2'], $config->get('test'));
+ }
+
+ public function testSetAndGet()
+ {
+ $config = new Config();
+
+ $config->set([
+ 'key1' => 'value1',
+ 'key2' => [
+ 'key3' => 'value3',
+ ],
+ ], 'test');
+
+ $this->assertTrue($config->has('test.key1'));
+ $this->assertEquals('value1', $config->get('test.key1'));
+ $this->assertEquals('value3', $config->get('test.key2.key3'));
+
+ $this->assertEquals(['key3' => 'value3'], $config->get('test.key2'));
+ $this->assertFalse($config->has('test.key3'));
+ $this->assertEquals('none', $config->get('test.key3', 'none'));
+ }
+}
diff --git a/vendor/topthink/framework/tests/ContainerTest.php b/vendor/topthink/framework/tests/ContainerTest.php
new file mode 100644
index 0000000..e27deb0
--- /dev/null
+++ b/vendor/topthink/framework/tests/ContainerTest.php
@@ -0,0 +1,314 @@
+name = $name;
+ }
+
+ public function some(Container $container)
+ {
+ }
+
+ protected function protectionFun()
+ {
+ return true;
+ }
+
+ public static function test(Container $container)
+ {
+ return $container;
+ }
+
+ public static function __make()
+ {
+ return new self('Taylor');
+ }
+}
+
+class SomeClass
+{
+ public $container;
+
+ public $count = 0;
+
+ public function __construct(Container $container)
+ {
+ $this->container = $container;
+ }
+}
+
+class ContainerTest extends TestCase
+{
+ protected function tearDown(): void
+ {
+ Container::setInstance(null);
+ }
+
+ public function testClosureResolution()
+ {
+ $container = new Container;
+
+ Container::setInstance($container);
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertEquals('Taylor', $container->make('name'));
+
+ $this->assertEquals('Taylor', Container::pull('name'));
+ }
+
+ public function testGet()
+ {
+ $container = new Container;
+
+ $this->expectException(ClassNotFoundException::class);
+ $this->expectExceptionMessage('class not exists: name');
+ $container->get('name');
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertSame('Taylor', $container->get('name'));
+ }
+
+ public function testExist()
+ {
+ $container = new Container;
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertFalse($container->exists("name"));
+
+ $container->make('name');
+
+ $this->assertTrue($container->exists('name'));
+ }
+
+ public function testInstance()
+ {
+ $container = new Container;
+
+ $container->bind('name', function () {
+ return 'Taylor';
+ });
+
+ $this->assertEquals('Taylor', $container->get('name'));
+
+ $container->bind('name2', Taylor::class);
+
+ $object = new stdClass();
+
+ $this->assertFalse($container->exists('name2'));
+
+ $container->instance('name2', $object);
+
+ $this->assertTrue($container->exists('name2'));
+
+ $this->assertTrue($container->exists(Taylor::class));
+
+ $this->assertEquals($object, $container->make(Taylor::class));
+
+ unset($container->name1);
+
+ $this->assertFalse($container->exists('name1'));
+
+ $container->delete('name2');
+
+ $this->assertFalse($container->exists('name2'));
+
+ foreach ($container as $class => $instance) {
+
+ }
+ }
+
+ public function testBind()
+ {
+ $container = new Container;
+
+ $object = new stdClass();
+
+ $container->bind(['name' => Taylor::class]);
+
+ $container->bind('name2', $object);
+
+ $container->bind('name3', Taylor::class);
+ $container->bind('name3', Taylor::class);
+
+ $container->name4 = $object;
+
+ $container['name5'] = $object;
+
+ $this->assertTrue(isset($container->name4));
+
+ $this->assertTrue(isset($container['name5']));
+
+ $this->assertInstanceOf(Taylor::class, $container->get('name'));
+
+ $this->assertSame($object, $container->get('name2'));
+
+ $this->assertSame($object, $container->name4);
+
+ $this->assertSame($object, $container['name5']);
+
+ $this->assertInstanceOf(Taylor::class, $container->get('name3'));
+
+ unset($container['name']);
+
+ $this->assertFalse(isset($container['name']));
+
+ unset($container->name3);
+
+ $this->assertFalse(isset($container->name3));
+ }
+
+ public function testAutoConcreteResolution()
+ {
+ $container = new Container;
+
+ $taylor = $container->make(Taylor::class);
+
+ $this->assertInstanceOf(Taylor::class, $taylor);
+ $this->assertAttributeSame('Taylor', 'name', $taylor);
+ }
+
+ public function testGetAndSetInstance()
+ {
+ $this->assertInstanceOf(Container::class, Container::getInstance());
+
+ $object = new stdClass();
+
+ Container::setInstance($object);
+
+ $this->assertSame($object, Container::getInstance());
+
+ Container::setInstance(function () {
+ return $this;
+ });
+
+ $this->assertSame($this, Container::getInstance());
+ }
+
+ public function testResolving()
+ {
+ $container = new Container();
+ $container->bind(Container::class, $container);
+
+ $container->resolving(function (SomeClass $taylor, Container $container) {
+ $taylor->count++;
+ });
+ $container->resolving(SomeClass::class, function (SomeClass $taylor, Container $container) {
+ $taylor->count++;
+ });
+
+ /** @var SomeClass $someClass */
+ $someClass = $container->invokeClass(SomeClass::class);
+ $this->assertEquals(2, $someClass->count);
+ }
+
+ public function testInvokeFunctionWithoutMethodThrowsException()
+ {
+ $this->expectException(FuncNotFoundException::class);
+ $this->expectExceptionMessage('function not exists: ContainerTestCallStub()');
+ $container = new Container();
+ $container->invokeFunction('ContainerTestCallStub', []);
+ }
+
+ public function testInvokeProtectionMethod()
+ {
+ $container = new Container();
+ $this->assertTrue($container->invokeMethod([Taylor::class, 'protectionFun'], [], true));
+ }
+
+ public function testInvoke()
+ {
+ $container = new Container();
+
+ Container::setInstance($container);
+
+ $container->bind(Container::class, $container);
+
+ $stub = $this->createMock(Taylor::class);
+
+ $stub->expects($this->once())->method('some')->with($container)->will($this->returnSelf());
+
+ $container->invokeMethod([$stub, 'some']);
+
+ $this->assertEquals('48', $container->invoke('ord', ['0']));
+
+ $this->assertSame($container, $container->invoke(Taylor::class . '::test', []));
+
+ $this->assertSame($container, $container->invokeMethod(Taylor::class . '::test'));
+
+ $reflect = new ReflectionMethod($container, 'exists');
+
+ $this->assertTrue($container->invokeReflectMethod($container, $reflect, [Container::class]));
+
+ $this->assertSame($container, $container->invoke(function (Container $container) {
+ return $container;
+ }));
+
+ $this->assertSame($container, $container->invoke(Taylor::class . '::test'));
+
+ $object = $container->invokeClass(SomeClass::class);
+ $this->assertInstanceOf(SomeClass::class, $object);
+ $this->assertSame($container, $object->container);
+
+ $stdClass = new stdClass();
+
+ $container->invoke(function (Container $container, stdClass $stdObject, $key1, $lowKey, $key2 = 'default') use ($stdClass) {
+ $this->assertEquals('value1', $key1);
+ $this->assertEquals('default', $key2);
+ $this->assertEquals('value2', $lowKey);
+ $this->assertSame($stdClass, $stdObject);
+ return $container;
+ }, ['some' => $stdClass, 'key1' => 'value1', 'low_key' => 'value2']);
+ }
+
+ public function testInvokeMethodNotExists()
+ {
+ $container = $this->resolveContainer();
+ $this->expectException(FuncNotFoundException::class);
+
+ $container->invokeMethod([SomeClass::class, 'any']);
+ }
+
+ public function testInvokeClassNotExists()
+ {
+ $container = new Container();
+
+ Container::setInstance($container);
+
+ $container->bind(Container::class, $container);
+
+ $this->expectExceptionObject(new ClassNotFoundException('class not exists: SomeClass'));
+
+ $container->invokeClass('SomeClass');
+ }
+
+ protected function resolveContainer()
+ {
+ $container = new Container();
+
+ Container::setInstance($container);
+ return $container;
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/DbTest.php b/vendor/topthink/framework/tests/DbTest.php
new file mode 100644
index 0000000..3bd0c1e
--- /dev/null
+++ b/vendor/topthink/framework/tests/DbTest.php
@@ -0,0 +1,49 @@
+shouldReceive('get')->with('database.cache_store', null)->andReturn(null);
+ $cache->shouldReceive('store')->with(null)->andReturn($store);
+
+ $db = Db::__make($event, $config, $log, $cache);
+
+ $config->shouldReceive('get')->with('database.foo', null)->andReturn('foo');
+ $this->assertEquals('foo', $db->getConfig('foo'));
+
+ $config->shouldReceive('get')->with('database', [])->andReturn([]);
+ $this->assertEquals([], $db->getConfig());
+
+ $callback = function () {
+ };
+ $event->shouldReceive('listen')->with('db.some', $callback);
+ $db->event('some', $callback);
+
+ $event->shouldReceive('trigger')->with('db.some', null, false);
+ $db->trigger('some');
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/DispatchTest.php b/vendor/topthink/framework/tests/DispatchTest.php
new file mode 100644
index 0000000..2b9e413
--- /dev/null
+++ b/vendor/topthink/framework/tests/DispatchTest.php
@@ -0,0 +1,32 @@
+ ['tp', 'thinkphp'], 'psr' => 'psr-7'], '123');
+ }
+ };
+
+ $response = $dispatch->run();
+
+ $this->assertInstanceOf(\think\Response::class, $response);
+ $this->assertEquals('123', $response->getContent());
+ $this->assertEquals('tp, thinkphp', $response->getHeader('framework'));
+ $this->assertEquals('psr-7', $response->getHeader('psr'));
+ }
+}
diff --git a/vendor/topthink/framework/tests/EnvTest.php b/vendor/topthink/framework/tests/EnvTest.php
new file mode 100644
index 0000000..9b3b615
--- /dev/null
+++ b/vendor/topthink/framework/tests/EnvTest.php
@@ -0,0 +1,80 @@
+setContent("key1=value1\nkey2=value2");
+ $root->addChild($envFile);
+
+ $env = new Env();
+
+ $env->load($envFile->url());
+
+ $this->assertEquals('value1', $env->get('key1'));
+ $this->assertEquals('value2', $env->get('key2'));
+ }
+
+ public function testServerEnv()
+ {
+ $env = new Env();
+
+ $this->assertEquals('value2', $env->get('key2', 'value2'));
+
+ putenv('PHP_KEY7=value7');
+ putenv('PHP_KEY8=false');
+ putenv('PHP_KEY9=true');
+
+ $this->assertEquals('value7', $env->get('key7'));
+ $this->assertFalse($env->get('KEY8'));
+ $this->assertTrue($env->get('key9'));
+ }
+
+ public function testSetEnv()
+ {
+ $env = new Env();
+
+ $env->set([
+ 'key1' => 'value1',
+ 'key2' => [
+ 'key1' => 'value1-2',
+ ],
+ ]);
+
+ $env->set('key3', 'value3');
+
+ $env->key4 = 'value4';
+
+ $env['key5'] = 'value5';
+
+ $this->assertEquals('value1', $env->get('key1'));
+ $this->assertEquals('value1-2', $env->get('key2.key1'));
+
+ $this->assertEquals('value3', $env->get('key3'));
+
+ $this->assertEquals('value4', $env->key4);
+
+ $this->assertEquals('value5', $env['key5']);
+
+ $this->expectException(Exception::class);
+
+ unset($env['key5']);
+ }
+
+ public function testHasEnv()
+ {
+ $env = new Env();
+ $env->set(['foo' => 'bar']);
+ $this->assertTrue($env->has('foo'));
+ $this->assertTrue(isset($env->foo));
+ $this->assertTrue($env->offsetExists('foo'));
+ }
+}
diff --git a/vendor/topthink/framework/tests/EventTest.php b/vendor/topthink/framework/tests/EventTest.php
new file mode 100644
index 0000000..ded5a36
--- /dev/null
+++ b/vendor/topthink/framework/tests/EventTest.php
@@ -0,0 +1,134 @@
+app = m::mock(App::class)->makePartial();
+
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+
+ $this->event = new Event($this->app);
+ }
+
+ public function testBasic()
+ {
+ $this->event->bind(['foo' => 'baz']);
+
+ $this->event->listen('foo', function ($bar) {
+ $this->assertEquals('bar', $bar);
+ });
+
+ $this->assertTrue($this->event->hasListener('foo'));
+
+ $this->event->trigger('baz', 'bar');
+
+ $this->event->remove('foo');
+
+ $this->assertFalse($this->event->hasListener('foo'));
+ }
+
+ public function testOnceEvent()
+ {
+ $this->event->listen('AppInit', function ($bar) {
+ $this->assertEquals('bar', $bar);
+ return 'foo';
+ });
+
+ $this->assertEquals('foo', $this->event->trigger('AppInit', 'bar', true));
+ $this->assertEquals(['foo'], $this->event->trigger('AppInit', 'bar'));
+ }
+
+ public function testClassListener()
+ {
+ $listener = m::mock("overload:SomeListener", TestListener::class);
+
+ $listener->shouldReceive('handle')->andReturnTrue();
+
+ $this->event->listen('some', "SomeListener");
+
+ $this->assertTrue($this->event->until('some'));
+ }
+
+ public function testSubscribe()
+ {
+ $listener = m::mock("overload:SomeListener", TestListener::class);
+
+ $listener->shouldReceive('subscribe')->andReturnUsing(function (Event $event) use ($listener) {
+
+ $listener->shouldReceive('onBar')->once()->andReturnFalse();
+
+ $event->listenEvents(['SomeListener::onBar' => [[$listener, 'onBar']]]);
+ });
+
+ $this->event->subscribe('SomeListener');
+
+ $this->assertTrue($this->event->hasListener('SomeListener::onBar'));
+
+ $this->event->trigger('SomeListener::onBar');
+ }
+
+ public function testAutoObserve()
+ {
+ $listener = m::mock("overload:SomeListener", TestListener::class);
+
+ $listener->shouldReceive('onBar')->once();
+
+ $this->app->shouldReceive('make')->with('SomeListener')->andReturn($listener);
+
+ $this->event->observe('SomeListener');
+
+ $this->event->trigger('bar');
+ }
+
+}
+
+class TestListener
+{
+ public function handle()
+ {
+
+ }
+
+ public function onBar()
+ {
+
+ }
+
+ public function onFoo()
+ {
+
+ }
+
+ public function subscribe()
+ {
+
+ }
+}
diff --git a/vendor/topthink/framework/tests/FilesystemTest.php b/vendor/topthink/framework/tests/FilesystemTest.php
new file mode 100644
index 0000000..df5ffe2
--- /dev/null
+++ b/vendor/topthink/framework/tests/FilesystemTest.php
@@ -0,0 +1,131 @@
+app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class);
+ $this->config->shouldReceive('get')->with('filesystem.default', null)->andReturn('local');
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+ $this->filesystem = new Filesystem($this->app);
+
+ $this->root = vfsStream::setup('rootDir');
+ }
+
+ protected function tearDown(): void
+ {
+ m::close();
+ }
+
+ public function testDisk()
+ {
+ $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([
+ 'type' => 'local',
+ 'root' => $this->root->url(),
+ ]);
+
+ $this->config->shouldReceive('get')->with('filesystem.disks.foo', null)->andReturn([
+ 'type' => 'local',
+ 'root' => $this->root->url(),
+ ]);
+
+ $this->assertInstanceOf(Local::class, $this->filesystem->disk());
+
+ $this->assertInstanceOf(Local::class, $this->filesystem->disk('foo'));
+ }
+
+ public function testCache()
+ {
+ $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([
+ 'type' => 'local',
+ 'root' => $this->root->url(),
+ 'cache' => true,
+ ]);
+
+ $this->assertInstanceOf(Local::class, $this->filesystem->disk());
+
+ $this->config->shouldReceive('get')->with('filesystem.disks.cache', null)->andReturn([
+ 'type' => NullDriver::class,
+ 'root' => $this->root->url(),
+ 'cache' => [
+ 'store' => 'flysystem',
+ ],
+ ]);
+
+ $cache = m::mock(Cache::class);
+
+ $cacheDriver = m::mock(File::class);
+
+ $cache->shouldReceive('store')->once()->with('flysystem')->andReturn($cacheDriver);
+
+ $this->app->shouldReceive('make')->with(Cache::class)->andReturn($cache);
+
+ $cacheDriver->shouldReceive('get')->with('flysystem')->once()->andReturn(null);
+
+ $cacheDriver->shouldReceive('set')->withAnyArgs();
+
+ $this->filesystem->disk('cache')->put('test.txt', 'aa');
+ }
+
+ public function testPutFile()
+ {
+ $root = vfsStream::setup('rootDir', null, [
+ 'foo.jpg' => 'hello',
+ ]);
+
+ $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([
+ 'type' => NullDriver::class,
+ 'root' => $root->url(),
+ 'cache' => true,
+ ]);
+
+ $file = m::mock(\think\File::class);
+
+ $file->shouldReceive('hashName')->with(null)->once()->andReturn('foo.jpg');
+
+ $file->shouldReceive('getRealPath')->once()->andReturn($root->getChild('foo.jpg')->url());
+
+ $this->filesystem->putFile('test', $file);
+ }
+}
+
+class NullDriver extends Driver
+{
+ protected function createAdapter(): AdapterInterface
+ {
+ return new NullAdapter();
+ }
+}
diff --git a/vendor/topthink/framework/tests/HttpTest.php b/vendor/topthink/framework/tests/HttpTest.php
new file mode 100644
index 0000000..c3e0abd
--- /dev/null
+++ b/vendor/topthink/framework/tests/HttpTest.php
@@ -0,0 +1,155 @@
+app = m::mock(App::class)->makePartial();
+
+ $this->http = m::mock(Http::class, [$this->app])->shouldAllowMockingProtectedMethods()->makePartial();
+ }
+
+ protected function prepareApp($request, $response)
+ {
+ $this->app->shouldReceive('instance')->once()->with('request', $request);
+ $this->app->shouldReceive('initialized')->once()->andReturnFalse();
+ $this->app->shouldReceive('initialize')->once();
+ $this->app->shouldReceive('get')->with('request')->andReturn($request);
+
+ $route = m::mock(Route::class);
+
+ $route->shouldReceive('dispatch')->withArgs(function ($req, $withRoute) use ($request) {
+ if ($withRoute) {
+ $withRoute();
+ }
+ return $req === $request;
+ })->andReturn($response);
+
+ $route->shouldReceive('config')->with('route_annotation')->andReturn(true);
+
+ $this->app->shouldReceive('get')->with('route')->andReturn($route);
+
+ $console = m::mock(Console::class);
+
+ $console->shouldReceive('call');
+
+ $this->app->shouldReceive('get')->with('console')->andReturn($console);
+ }
+
+ public function testRun()
+ {
+ $root = vfsStream::setup('rootDir', null, [
+ 'app' => [
+ 'controller' => [],
+ 'middleware.php' => ' [
+ 'route.php' => 'app->shouldReceive('getBasePath')->andReturn($root->getChild('app')->url() . DIRECTORY_SEPARATOR);
+ $this->app->shouldReceive('getRootPath')->andReturn($root->url() . DIRECTORY_SEPARATOR);
+
+ $request = m::mock(Request::class)->makePartial();
+ $response = m::mock(Response::class)->makePartial();
+
+ $this->prepareApp($request, $response);
+
+ $this->assertEquals($response, $this->http->run($request));
+ }
+
+ public function multiAppRunProvider()
+ {
+ $request1 = m::mock(Request::class)->makePartial();
+ $request1->shouldReceive('subDomain')->andReturn('www');
+ $request1->shouldReceive('host')->andReturn('www.domain.com');
+
+ $request2 = m::mock(Request::class)->makePartial();
+ $request2->shouldReceive('subDomain')->andReturn('app2');
+ $request2->shouldReceive('host')->andReturn('app2.domain.com');
+
+ $request3 = m::mock(Request::class)->makePartial();
+ $request3->shouldReceive('pathinfo')->andReturn('some1/a/b/c');
+
+ $request4 = m::mock(Request::class)->makePartial();
+ $request4->shouldReceive('pathinfo')->andReturn('app3/a/b/c');
+
+ $request5 = m::mock(Request::class)->makePartial();
+ $request5->shouldReceive('pathinfo')->andReturn('some2/a/b/c');
+
+ return [
+ [$request1, true, 'app1'],
+ [$request2, true, 'app2'],
+ [$request3, true, 'app3'],
+ [$request4, true, null],
+ [$request5, true, 'some2', 'path'],
+ [$request1, false, 'some3'],
+ ];
+ }
+
+ public function testRunWithException()
+ {
+ $request = m::mock(Request::class);
+ $response = m::mock(Response::class);
+
+ $this->app->shouldReceive('instance')->once()->with('request', $request);
+ $this->app->shouldReceive('initialize')->once();
+
+ $exception = new Exception();
+
+ $this->http->shouldReceive('runWithRequest')->once()->with($request)->andThrow($exception);
+
+ $handle = m::mock(Handle::class);
+
+ $handle->shouldReceive('report')->once()->with($exception);
+ $handle->shouldReceive('render')->once()->with($request, $exception)->andReturn($response);
+
+ $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle);
+
+ $this->assertEquals($response, $this->http->run($request));
+ }
+
+ public function testEnd()
+ {
+ $response = m::mock(Response::class);
+ $event = m::mock(Event::class);
+ $event->shouldReceive('trigger')->once()->with(HttpEnd::class, $response);
+ $this->app->shouldReceive('get')->once()->with('event')->andReturn($event);
+ $log = m::mock(Log::class);
+ $log->shouldReceive('save')->once();
+ $this->app->shouldReceive('get')->once()->with('log')->andReturn($log);
+
+ $this->http->end($response);
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/InteractsWithApp.php b/vendor/topthink/framework/tests/InteractsWithApp.php
new file mode 100644
index 0000000..f4fcf73
--- /dev/null
+++ b/vendor/topthink/framework/tests/InteractsWithApp.php
@@ -0,0 +1,30 @@
+app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->app->shouldReceive('isDebug')->andReturnTrue();
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->config->shouldReceive('get')->with('app.show_error_msg')->andReturnTrue();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+ $this->app->shouldReceive('runningInConsole')->andReturn(false);
+ }
+}
diff --git a/vendor/topthink/framework/tests/LogTest.php b/vendor/topthink/framework/tests/LogTest.php
new file mode 100644
index 0000000..981110f
--- /dev/null
+++ b/vendor/topthink/framework/tests/LogTest.php
@@ -0,0 +1,130 @@
+prepareApp();
+
+ $this->log = new Log($this->app);
+ }
+
+ public function testGetConfig()
+ {
+ $config = [
+ 'default' => 'file',
+ ];
+
+ $this->config->shouldReceive('get')->with('log')->andReturn($config);
+
+ $this->assertEquals($config, $this->log->getConfig());
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->log->getChannelConfig('foo');
+ }
+
+ public function testChannel()
+ {
+ $this->assertInstanceOf(ChannelSet::class, $this->log->channel(['file', 'mail']));
+ }
+
+ public function testLogManagerInstances()
+ {
+ $this->config->shouldReceive('get')->with("log.channels.single", null)->andReturn(['type' => 'file']);
+
+ $channel1 = $this->log->channel('single');
+ $channel2 = $this->log->channel('single');
+
+ $this->assertSame($channel1, $channel2);
+ }
+
+ public function testFileLog()
+ {
+ $root = vfsStream::setup();
+
+ $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file');
+
+ $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]);
+
+ $this->log->info('foo');
+
+ $this->assertEquals($this->log->getLog(), ['info' => ['foo']]);
+
+ $this->log->clear();
+
+ $this->assertEmpty($this->log->getLog());
+
+ $this->log->error('foo');
+ $this->assertArrayHasKey('error', $this->log->getLog());
+
+ $this->log->emergency('foo');
+ $this->assertArrayHasKey('emergency', $this->log->getLog());
+
+ $this->log->alert('foo');
+ $this->assertArrayHasKey('alert', $this->log->getLog());
+
+ $this->log->critical('foo');
+ $this->assertArrayHasKey('critical', $this->log->getLog());
+
+ $this->log->warning('foo');
+ $this->assertArrayHasKey('warning', $this->log->getLog());
+
+ $this->log->notice('foo');
+ $this->assertArrayHasKey('notice', $this->log->getLog());
+
+ $this->log->debug('foo');
+ $this->assertArrayHasKey('debug', $this->log->getLog());
+
+ $this->log->sql('foo');
+ $this->assertArrayHasKey('sql', $this->log->getLog());
+
+ $this->log->custom('foo');
+ $this->assertArrayHasKey('custom', $this->log->getLog());
+
+ $this->log->write('foo');
+ $this->assertTrue($root->hasChildren());
+ $this->assertEmpty($this->log->getLog());
+
+ $this->log->close();
+
+ $this->log->info('foo');
+
+ $this->assertEmpty($this->log->getLog());
+ }
+
+ public function testSave()
+ {
+ $root = vfsStream::setup();
+
+ $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file');
+
+ $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]);
+
+ $this->log->info('foo');
+
+ $this->log->save();
+
+ $this->assertTrue($root->hasChildren());
+ }
+
+}
diff --git a/vendor/topthink/framework/tests/MiddlewareTest.php b/vendor/topthink/framework/tests/MiddlewareTest.php
new file mode 100644
index 0000000..aa53059
--- /dev/null
+++ b/vendor/topthink/framework/tests/MiddlewareTest.php
@@ -0,0 +1,108 @@
+prepareApp();
+
+ $this->middleware = new Middleware($this->app);
+ }
+
+ public function testSetMiddleware()
+ {
+ $this->middleware->add('BarMiddleware', 'bar');
+
+ $this->assertEquals(1, count($this->middleware->all('bar')));
+
+ $this->middleware->controller('BarMiddleware');
+ $this->assertEquals(1, count($this->middleware->all('controller')));
+
+ $this->middleware->import(['FooMiddleware']);
+ $this->assertEquals(1, count($this->middleware->all()));
+
+ $this->middleware->unshift(['BazMiddleware', 'baz']);
+ $this->assertEquals(2, count($this->middleware->all()));
+ $this->assertEquals([['BazMiddleware', 'handle'], 'baz'], $this->middleware->all()[0]);
+
+ $this->config->shouldReceive('get')->with('middleware.alias', [])->andReturn(['foo' => ['FooMiddleware', 'FarMiddleware']]);
+
+ $this->middleware->add('foo');
+ $this->assertEquals(3, count($this->middleware->all()));
+ $this->middleware->add(function () {
+ });
+ $this->middleware->add(function () {
+ });
+ $this->assertEquals(5, count($this->middleware->all()));
+ }
+
+ public function testPipelineAndEnd()
+ {
+ $bar = m::mock("overload:BarMiddleware");
+ $foo = m::mock("overload:FooMiddleware", Foo::class);
+
+ $request = m::mock(Request::class);
+ $response = m::mock(Response::class);
+
+ $e = new Exception();
+
+ $handle = m::mock(Handle::class);
+ $handle->shouldReceive('report')->with($e)->andReturnNull();
+ $handle->shouldReceive('render')->with($request, $e)->andReturn($response);
+
+ $foo->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) {
+ return $next($request);
+ });
+ $bar->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) use ($e) {
+ $next($request);
+ throw $e;
+ });
+
+ $foo->shouldReceive('end')->once()->with($response)->andReturnNull();
+
+ $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle);
+
+ $this->config->shouldReceive('get')->once()->with('middleware.priority', [])->andReturn(['FooMiddleware', 'BarMiddleware']);
+
+ $this->middleware->import([function ($request, $next) {
+ return $next($request);
+ }, 'BarMiddleware', 'FooMiddleware']);
+
+ $this->assertInstanceOf(Pipeline::class, $pipeline = $this->middleware->pipeline());
+
+ $pipeline->send($request)->then(function ($request) use ($e, $response) {
+ throw $e;
+ });
+
+ $this->middleware->end($response);
+ }
+}
+
+class Foo
+{
+ public function end(Response $response)
+ {
+ }
+}
diff --git a/vendor/topthink/framework/tests/RouteTest.php b/vendor/topthink/framework/tests/RouteTest.php
new file mode 100644
index 0000000..6a7f8ee
--- /dev/null
+++ b/vendor/topthink/framework/tests/RouteTest.php
@@ -0,0 +1,291 @@
+prepareApp();
+ $this->route = new Route($this->app);
+ }
+
+ /**
+ * @param $path
+ * @param string $method
+ * @param string $host
+ * @return m\Mock|Request
+ */
+ protected function makeRequest($path, $method = 'GET', $host = 'localhost')
+ {
+ $request = m::mock(Request::class)->makePartial();
+ $request->shouldReceive('host')->andReturn($host);
+ $request->shouldReceive('pathinfo')->andReturn($path);
+ $request->shouldReceive('url')->andReturn('/' . $path);
+ $request->shouldReceive('method')->andReturn(strtoupper($method));
+ return $request;
+ }
+
+ public function testSimpleRequest()
+ {
+ $this->route->get('foo', function () {
+ return 'get-foo';
+ });
+
+ $this->route->put('foo', function () {
+ return 'put-foo';
+ });
+
+ $this->route->group(function () {
+ $this->route->post('foo', function () {
+ return 'post-foo';
+ });
+ });
+
+ $request = $this->makeRequest('foo', 'post');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(200, $response->getCode());
+ $this->assertEquals('post-foo', $response->getContent());
+
+ $request = $this->makeRequest('foo', 'get');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(200, $response->getCode());
+ $this->assertEquals('get-foo', $response->getContent());
+ }
+
+ public function testOptionsRequest()
+ {
+ $this->route->get('foo', function () {
+ return 'get-foo';
+ });
+
+ $this->route->put('foo', function () {
+ return 'put-foo';
+ });
+
+ $this->route->group(function () {
+ $this->route->post('foo', function () {
+ return 'post-foo';
+ });
+ });
+ $this->route->group('abc', function () {
+ $this->route->post('foo/:id', function () {
+ return 'post-abc-foo';
+ });
+ });
+
+ $this->route->post('foo/:id', function () {
+ return 'post-abc-foo';
+ });
+
+ $this->route->resource('bar', 'SomeClass');
+
+ $request = $this->makeRequest('foo', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, PUT, POST', $response->getHeader('Allow'));
+
+ $request = $this->makeRequest('bar', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, POST', $response->getHeader('Allow'));
+
+ $request = $this->makeRequest('bar/1', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, PUT, DELETE', $response->getHeader('Allow'));
+
+ $request = $this->makeRequest('xxxx', 'options');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals(204, $response->getCode());
+ $this->assertEquals('GET, POST, PUT, DELETE', $response->getHeader('Allow'));
+ }
+
+ public function testAllowCrossDomain()
+ {
+ $this->route->get('foo', function () {
+ return 'get-foo';
+ })->allowCrossDomain(['some' => 'bar']);
+
+ $request = $this->makeRequest('foo', 'get');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals('bar', $response->getHeader('some'));
+ $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader());
+
+ $request = $this->makeRequest('foo2', 'options');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals(204, $response->getCode());
+ $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader());
+ $this->assertEquals('GET, POST, PUT, DELETE', $response->getHeader('Allow'));
+ }
+
+ public function testControllerDispatch()
+ {
+ $this->route->get('foo', 'foo/bar');
+
+ $controller = m::Mock(\stdClass::class);
+
+ $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $controller->shouldReceive('bar')->andReturn('bar');
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ public function testEmptyControllerDispatch()
+ {
+ $this->route->get('foo', 'foo/bar');
+
+ $controller = m::Mock(\stdClass::class);
+
+ $this->app->shouldReceive('parseClass')->with('controller', 'Error')->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $controller->shouldReceive('bar')->andReturn('bar');
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ protected function createMiddleware($times = 1)
+ {
+ $middleware = m::mock(Str::random(5));
+ $middleware->shouldReceive('handle')->times($times)->andReturnUsing(function ($request, Closure $next) {
+ return $next($request);
+ });
+ $this->app->shouldReceive('make')->with($middleware->mockery_getName())->andReturn($middleware);
+
+ return $middleware;
+ }
+
+ public function testControllerWithMiddleware()
+ {
+ $this->route->get('foo', 'foo/bar');
+
+ $controller = m::mock(FooClass::class);
+
+ $controller->middleware = [
+ $this->createMiddleware()->mockery_getName() . ":params1:params2",
+ $this->createMiddleware(0)->mockery_getName() => ['except' => 'bar'],
+ $this->createMiddleware()->mockery_getName() => ['only' => 'bar'],
+ [
+ 'middleware' => [$this->createMiddleware()->mockery_getName(), [new \stdClass()]],
+ 'options' => ['only' => 'bar'],
+ ],
+ ];
+
+ $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $controller->shouldReceive('bar')->once()->andReturn('bar');
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ public function testUrlDispatch()
+ {
+ $controller = m::mock(FooClass::class);
+ $controller->shouldReceive('index')->andReturn('bar');
+
+ $this->app->shouldReceive('parseClass')->once()->with('controller', 'Foo')
+ ->andReturn($controller->mockery_getName());
+ $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller);
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+ $this->assertEquals('bar', $response->getContent());
+ }
+
+ public function testRedirectDispatch()
+ {
+ $this->route->redirect('foo', 'http://localhost', 302);
+
+ $request = $this->makeRequest('foo');
+ $this->app->shouldReceive('make')->with(Request::class)->andReturn($request);
+ $response = $this->route->dispatch($request);
+
+ $this->assertInstanceOf(Redirect::class, $response);
+ $this->assertEquals(302, $response->getCode());
+ $this->assertEquals('http://localhost', $response->getData());
+ }
+
+ public function testViewDispatch()
+ {
+ $this->route->view('foo', 'index/hello', ['city' => 'shanghai']);
+
+ $request = $this->makeRequest('foo');
+ $response = $this->route->dispatch($request);
+
+ $this->assertInstanceOf(View::class, $response);
+ $this->assertEquals(['city' => 'shanghai'], $response->getVars());
+ $this->assertEquals('index/hello', $response->getData());
+ }
+
+ public function testResponseDispatch()
+ {
+ $this->route->get('hello/:name', response()
+ ->data('Hello,ThinkPHP')
+ ->code(200)
+ ->contentType('text/plain'));
+
+ $request = $this->makeRequest('hello/some');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals('Hello,ThinkPHP', $response->getContent());
+ $this->assertEquals(200, $response->getCode());
+ }
+
+ public function testDomainBindResponse()
+ {
+ $this->route->domain('test', function () {
+ $this->route->get('/', function () {
+ return 'Hello,ThinkPHP';
+ });
+ });
+
+ $request = $this->makeRequest('', 'get', 'test.domain.com');
+ $response = $this->route->dispatch($request);
+
+ $this->assertEquals('Hello,ThinkPHP', $response->getContent());
+ $this->assertEquals(200, $response->getCode());
+ }
+
+}
+
+class FooClass
+{
+ public $middleware = [];
+
+ public function bar()
+ {
+
+ }
+}
diff --git a/vendor/topthink/framework/tests/SessionTest.php b/vendor/topthink/framework/tests/SessionTest.php
new file mode 100644
index 0000000..b3b48a7
--- /dev/null
+++ b/vendor/topthink/framework/tests/SessionTest.php
@@ -0,0 +1,225 @@
+app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+ $handlerClass = "\\think\\session\\driver\\Test" . Str::random(10);
+ $this->config->shouldReceive("get")->with("session.type", "file")->andReturn($handlerClass);
+ $this->session = new Session($this->app);
+
+ $this->handler = m::mock('overload:' . $handlerClass, SessionHandlerInterface::class);
+ }
+
+ public function testLoadData()
+ {
+ $data = [
+ "bar" => 'foo',
+ ];
+
+ $id = md5(uniqid());
+
+ $this->handler->shouldReceive("read")->once()->with($id)->andReturn(serialize($data));
+
+ $this->session->setId($id);
+ $this->session->init();
+
+ $this->assertEquals('foo', $this->session->get('bar'));
+ $this->assertTrue($this->session->has('bar'));
+ $this->assertFalse($this->session->has('foo'));
+
+ $this->session->set('foo', 'bar');
+ $this->assertTrue($this->session->has('foo'));
+
+ $this->assertEquals('bar', $this->session->pull('foo'));
+ $this->assertFalse($this->session->has('foo'));
+ }
+
+ public function testSave()
+ {
+
+ $id = md5(uniqid());
+
+ $this->handler->shouldReceive('read')->once()->with($id)->andReturn("");
+
+ $this->handler->shouldReceive('write')->once()->with($id, serialize([
+ "bar" => 'foo',
+ ]))->andReturnTrue();
+
+ $this->session->setId($id);
+ $this->session->init();
+
+ $this->session->set('bar', 'foo');
+
+ $this->session->save();
+ }
+
+ public function testFlash()
+ {
+ $this->session->flash('foo', 'bar');
+ $this->session->flash('bar', 0);
+ $this->session->flash('baz', true);
+
+ $this->assertTrue($this->session->has('foo'));
+ $this->assertEquals('bar', $this->session->get('foo'));
+ $this->assertEquals(0, $this->session->get('bar'));
+ $this->assertTrue($this->session->get('baz'));
+
+ $this->session->clearFlashData();
+
+ $this->assertTrue($this->session->has('foo'));
+ $this->assertEquals('bar', $this->session->get('foo'));
+ $this->assertEquals(0, $this->session->get('bar'));
+
+ $this->session->clearFlashData();
+
+ $this->assertFalse($this->session->has('foo'));
+ $this->assertNull($this->session->get('foo'));
+
+ $this->session->flash('foo', 'bar');
+ $this->assertTrue($this->session->has('foo'));
+ $this->session->clearFlashData();
+ $this->session->reflash();
+ $this->session->clearFlashData();
+
+ $this->assertTrue($this->session->has('foo'));
+ }
+
+ public function testClear()
+ {
+ $this->session->set('bar', 'foo');
+ $this->assertEquals('foo', $this->session->get('bar'));
+ $this->session->clear();
+ $this->assertFalse($this->session->has('foo'));
+ }
+
+ public function testSetName()
+ {
+ $this->session->setName('foo');
+ $this->assertEquals('foo', $this->session->getName());
+ }
+
+ public function testDestroy()
+ {
+ $id = md5(uniqid());
+
+ $this->handler->shouldReceive('read')->once()->with($id)->andReturn("");
+ $this->handler->shouldReceive('delete')->once()->with($id)->andReturnTrue();
+
+ $this->session->setId($id);
+ $this->session->init();
+
+ $this->session->set('bar', 'foo');
+
+ $this->session->destroy();
+
+ $this->assertFalse($this->session->has('bar'));
+
+ $this->assertNotEquals($id, $this->session->getId());
+ }
+
+ public function testFileHandler()
+ {
+ $root = vfsStream::setup();
+
+ vfsStream::newFile('bar')
+ ->at($root)
+ ->lastModified(time());
+
+ vfsStream::newFile('bar')
+ ->at(vfsStream::newDirectory("foo")->at($root))
+ ->lastModified(100);
+
+ $this->assertTrue($root->hasChild("bar"));
+ $this->assertTrue($root->hasChild("foo/bar"));
+
+ $handler = new TestFileHandle($this->app, [
+ 'path' => $root->url(),
+ 'gc_probability' => 1,
+ 'gc_divisor' => 1,
+ ]);
+
+ $this->assertTrue($root->hasChild("bar"));
+ $this->assertFalse($root->hasChild("foo/bar"));
+
+ $id = md5(uniqid());
+ $handler->write($id, "bar");
+
+ $this->assertTrue($root->hasChild("sess_{$id}"));
+
+ $this->assertEquals("bar", $handler->read($id));
+
+ $handler->delete($id);
+
+ $this->assertFalse($root->hasChild("sess_{$id}"));
+ }
+
+ public function testCacheHandler()
+ {
+ $id = md5(uniqid());
+
+ $cache = m::mock(\think\Cache::class);
+
+ $store = m::mock(Driver::class);
+
+ $cache->shouldReceive('store')->once()->with('redis')->andReturn($store);
+
+ $handler = new Cache($cache, ['store' => 'redis']);
+
+ $store->shouldReceive("set")->with($id, "bar", 1440)->once()->andReturnTrue();
+ $handler->write($id, "bar");
+
+ $store->shouldReceive("get")->with($id)->once()->andReturn("bar");
+ $this->assertEquals("bar", $handler->read($id));
+
+ $store->shouldReceive("delete")->with($id)->once()->andReturnTrue();
+ $handler->delete($id);
+ }
+}
+
+class TestFileHandle extends File
+{
+ protected function writeFile($path, $content): bool
+ {
+ return (bool) file_put_contents($path, $content);
+ }
+}
diff --git a/vendor/topthink/framework/tests/ViewTest.php b/vendor/topthink/framework/tests/ViewTest.php
new file mode 100644
index 0000000..e413510
--- /dev/null
+++ b/vendor/topthink/framework/tests/ViewTest.php
@@ -0,0 +1,127 @@
+app = m::mock(App::class)->makePartial();
+ Container::setInstance($this->app);
+
+ $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
+ $this->config = m::mock(Config::class)->makePartial();
+ $this->app->shouldReceive('get')->with('config')->andReturn($this->config);
+
+ $this->view = new View($this->app);
+ }
+
+ public function testAssignData()
+ {
+ $this->view->assign('foo', 'bar');
+ $this->view->assign(['baz' => 'boom']);
+ $this->view->qux = "corge";
+
+ $this->assertEquals('bar', $this->view->foo);
+ $this->assertEquals('boom', $this->view->baz);
+ $this->assertEquals('corge', $this->view->qux);
+ $this->assertTrue(isset($this->view->qux));
+ }
+
+ public function testRender()
+ {
+ $this->config->shouldReceive("get")->with("view.type", 'php')->andReturn(TestTemplate::class);
+
+ $this->view->filter(function ($content) {
+ return $content;
+ });
+
+ $this->assertEquals("fetch", $this->view->fetch('foo'));
+ $this->assertEquals("display", $this->view->display('foo'));
+ }
+
+}
+
+class TestTemplate implements TemplateHandlerInterface
+{
+
+ /**
+ * 检测是否存在模板文件
+ * @access public
+ * @param string $template 模板文件或者模板规则
+ * @return bool
+ */
+ public function exists(string $template): bool
+ {
+ return true;
+ }
+
+ /**
+ * 渲染模板文件
+ * @access public
+ * @param string $template 模板文件
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function fetch(string $template, array $data = []): void
+ {
+ echo "fetch";
+ }
+
+ /**
+ * 渲染模板内容
+ * @access public
+ * @param string $content 模板内容
+ * @param array $data 模板变量
+ * @return void
+ */
+ public function display(string $content, array $data = []): void
+ {
+ echo "display";
+ }
+
+ /**
+ * 配置模板引擎
+ * @access private
+ * @param array $config 参数
+ * @return void
+ */
+ public function config(array $config): void
+ {
+ // TODO: Implement config() method.
+ }
+
+ /**
+ * 获取模板引擎配置
+ * @access public
+ * @param string $name 参数名
+ * @return void
+ */
+ public function getConfig(string $name)
+ {
+ // TODO: Implement getConfig() method.
+ }
+}
diff --git a/vendor/topthink/framework/tests/bootstrap.php b/vendor/topthink/framework/tests/bootstrap.php
new file mode 100644
index 0000000..3459061
--- /dev/null
+++ b/vendor/topthink/framework/tests/bootstrap.php
@@ -0,0 +1,3 @@
+ 以下类库都在`\\think\\helper`命名空间下
## Str
diff --git a/vendor/topthink/think-helper/composer.json b/vendor/topthink/think-helper/composer.json
index b68c43b..0892fa6 100644
--- a/vendor/topthink/think-helper/composer.json
+++ b/vendor/topthink/think-helper/composer.json
@@ -10,7 +10,10 @@
],
"require": {
"php": ">=7.1.0"
- },
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5"
+ },
"autoload": {
"psr-4": {
"think\\": "src"
@@ -18,5 +21,16 @@
"files": [
"src/helper.php"
]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\": "tests"
+ }
+ },
+ "scripts": {
+ "test": "./vendor/bin/phpunit --colors"
+ },
+ "scripts-descriptions": {
+ "test": "Run all tests."
}
}
diff --git a/vendor/topthink/think-helper/phpunit.xml.dist b/vendor/topthink/think-helper/phpunit.xml.dist
new file mode 100644
index 0000000..c083f14
--- /dev/null
+++ b/vendor/topthink/think-helper/phpunit.xml.dist
@@ -0,0 +1,17 @@
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src
+
+
+
diff --git a/vendor/topthink/think-helper/src/Collection.php b/vendor/topthink/think-helper/src/Collection.php
index fa408c2..5b19ffc 100644
--- a/vendor/topthink/think-helper/src/Collection.php
+++ b/vendor/topthink/think-helper/src/Collection.php
@@ -20,6 +20,7 @@ use JsonSerializable;
use think\contract\Arrayable;
use think\contract\Jsonable;
use think\helper\Arr;
+use Traversable;
/**
* 数据集管理类
@@ -142,7 +143,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
public function intersect($items, string $indexKey = null)
{
if ($this->isEmpty() || is_scalar($this->items[0])) {
- return new static(array_diff($this->items, $this->convertToArray($items)));
+ return new static(array_intersect($this->items, $this->convertToArray($items)));
}
$intersect = [];
@@ -579,16 +580,19 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
}
// ArrayAccess
- public function offsetExists($offset)
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset) : bool
{
return array_key_exists($offset, $this->items);
}
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->items[$offset];
}
+ #[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
@@ -598,24 +602,27 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
}
}
+ #[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
unset($this->items[$offset]);
}
//Countable
- public function count()
+ public function count(): int
{
return count($this->items);
}
//IteratorAggregate
- public function getIterator()
+ #[\ReturnTypeWillChange]
+ public function getIterator(): Traversable
{
return new ArrayIterator($this->items);
}
//JsonSerializable
+ #[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->toArray();
@@ -627,7 +634,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
* @param integer $options json参数
* @return string
*/
- public function toJson(int $options = JSON_UNESCAPED_UNICODE) : string
+ public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
{
return json_encode($this->toArray(), $options);
}
diff --git a/vendor/topthink/think-helper/src/helper/Str.php b/vendor/topthink/think-helper/src/helper/Str.php
index 7391fbd..664dba2 100644
--- a/vendor/topthink/think-helper/src/helper/Str.php
+++ b/vendor/topthink/think-helper/src/helper/Str.php
@@ -179,7 +179,7 @@ class Str
}
if (!ctype_lower($value)) {
- $value = preg_replace('/\s+/u', '', $value);
+ $value = preg_replace('/\s+/u', '', ucwords($value));
$value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
}
diff --git a/vendor/topthink/think-helper/tests/ArrTest.php b/vendor/topthink/think-helper/tests/ArrTest.php
new file mode 100644
index 0000000..a8effe7
--- /dev/null
+++ b/vendor/topthink/think-helper/tests/ArrTest.php
@@ -0,0 +1,342 @@
+ 'ThinkPHP'], 'price', 100);
+ $this->assertSame(['name' => 'ThinkPHP', 'price' => 100], $array);
+ }
+
+ public function testCrossJoin()
+ {
+ // Single dimension
+ $this->assertSame(
+ [[1, 'a'], [1, 'b'], [1, 'c']],
+ Arr::crossJoin([1], ['a', 'b', 'c'])
+ );
+ // Square matrix
+ $this->assertSame(
+ [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']],
+ Arr::crossJoin([1, 2], ['a', 'b'])
+ );
+ // Rectangular matrix
+ $this->assertSame(
+ [[1, 'a'], [1, 'b'], [1, 'c'], [2, 'a'], [2, 'b'], [2, 'c']],
+ Arr::crossJoin([1, 2], ['a', 'b', 'c'])
+ );
+ // 3D matrix
+ $this->assertSame(
+ [
+ [1, 'a', 'I'], [1, 'a', 'II'], [1, 'a', 'III'],
+ [1, 'b', 'I'], [1, 'b', 'II'], [1, 'b', 'III'],
+ [2, 'a', 'I'], [2, 'a', 'II'], [2, 'a', 'III'],
+ [2, 'b', 'I'], [2, 'b', 'II'], [2, 'b', 'III'],
+ ],
+ Arr::crossJoin([1, 2], ['a', 'b'], ['I', 'II', 'III'])
+ );
+ // With 1 empty dimension
+ $this->assertSame([], Arr::crossJoin([], ['a', 'b'], ['I', 'II', 'III']));
+ $this->assertSame([], Arr::crossJoin([1, 2], [], ['I', 'II', 'III']));
+ $this->assertSame([], Arr::crossJoin([1, 2], ['a', 'b'], []));
+ // With empty arrays
+ $this->assertSame([], Arr::crossJoin([], [], []));
+ $this->assertSame([], Arr::crossJoin([], []));
+ $this->assertSame([], Arr::crossJoin([]));
+ // Not really a proper usage, still, test for preserving BC
+ $this->assertSame([[]], Arr::crossJoin());
+ }
+
+ public function testDivide()
+ {
+ list($keys, $values) = Arr::divide(['name' => 'ThinkPHP']);
+ $this->assertSame(['name'], $keys);
+ $this->assertSame(['ThinkPHP'], $values);
+ }
+
+ public function testDot()
+ {
+ $array = Arr::dot(['foo' => ['bar' => 'baz']]);
+ $this->assertSame(['foo.bar' => 'baz'], $array);
+ $array = Arr::dot([]);
+ $this->assertSame([], $array);
+ $array = Arr::dot(['foo' => []]);
+ $this->assertSame(['foo' => []], $array);
+ $array = Arr::dot(['foo' => ['bar' => []]]);
+ $this->assertSame(['foo.bar' => []], $array);
+ }
+
+ public function testExcept()
+ {
+ $array = ['name' => 'ThinkPHP', 'price' => 100];
+ $array = Arr::except($array, ['price']);
+ $this->assertSame(['name' => 'ThinkPHP'], $array);
+ }
+
+ public function testExists()
+ {
+ $this->assertTrue(Arr::exists([1], 0));
+ $this->assertTrue(Arr::exists([null], 0));
+ $this->assertTrue(Arr::exists(['a' => 1], 'a'));
+ $this->assertTrue(Arr::exists(['a' => null], 'a'));
+ $this->assertFalse(Arr::exists([1], 1));
+ $this->assertFalse(Arr::exists([null], 1));
+ $this->assertFalse(Arr::exists(['a' => 1], 0));
+ }
+
+ public function testFirst()
+ {
+ $array = [100, 200, 300];
+ $value = Arr::first($array, function ($value) {
+ return $value >= 150;
+ });
+ $this->assertSame(200, $value);
+ $this->assertSame(100, Arr::first($array));
+
+ $this->assertSame('default', Arr::first([], null, 'default'));
+
+ $this->assertSame('default', Arr::first([], function () {
+ return false;
+ }, 'default'));
+ }
+
+ public function testLast()
+ {
+ $array = [100, 200, 300];
+ $last = Arr::last($array, function ($value) {
+ return $value < 250;
+ });
+ $this->assertSame(200, $last);
+ $last = Arr::last($array, function ($value, $key) {
+ return $key < 2;
+ });
+ $this->assertSame(200, $last);
+ $this->assertSame(300, Arr::last($array));
+ }
+
+ public function testFlatten()
+ {
+ // Flat arrays are unaffected
+ $array = ['#foo', '#bar', '#baz'];
+ $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten(['#foo', '#bar', '#baz']));
+ // Nested arrays are flattened with existing flat items
+ $array = [['#foo', '#bar'], '#baz'];
+ $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array));
+ // Flattened array includes "null" items
+ $array = [['#foo', null], '#baz', null];
+ $this->assertSame(['#foo', null, '#baz', null], Arr::flatten($array));
+ // Sets of nested arrays are flattened
+ $array = [['#foo', '#bar'], ['#baz']];
+ $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array));
+ // Deeply nested arrays are flattened
+ $array = [['#foo', ['#bar']], ['#baz']];
+ $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array));
+ // Nested arrays are flattened alongside arrays
+ $array = [new Collection(['#foo', '#bar']), ['#baz']];
+ $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array));
+ // Nested arrays containing plain arrays are flattened
+ $array = [new Collection(['#foo', ['#bar']]), ['#baz']];
+ $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array));
+ // Nested arrays containing arrays are flattened
+ $array = [['#foo', new Collection(['#bar'])], ['#baz']];
+ $this->assertSame(['#foo', '#bar', '#baz'], Arr::flatten($array));
+ // Nested arrays containing arrays containing arrays are flattened
+ $array = [['#foo', new Collection(['#bar', ['#zap']])], ['#baz']];
+ $this->assertSame(['#foo', '#bar', '#zap', '#baz'], Arr::flatten($array));
+ }
+
+ public function testFlattenWithDepth()
+ {
+ // No depth flattens recursively
+ $array = [['#foo', ['#bar', ['#baz']]], '#zap'];
+ $this->assertSame(['#foo', '#bar', '#baz', '#zap'], Arr::flatten($array));
+ // Specifying a depth only flattens to that depth
+ $array = [['#foo', ['#bar', ['#baz']]], '#zap'];
+ $this->assertSame(['#foo', ['#bar', ['#baz']], '#zap'], Arr::flatten($array, 1));
+ $array = [['#foo', ['#bar', ['#baz']]], '#zap'];
+ $this->assertSame(['#foo', '#bar', ['#baz'], '#zap'], Arr::flatten($array, 2));
+ }
+
+ public function testGet()
+ {
+ $array = ['products.item' => ['price' => 100]];
+ $this->assertSame(['price' => 100], Arr::get($array, 'products.item'));
+ $array = ['products' => ['item' => ['price' => 100]]];
+ $value = Arr::get($array, 'products.item');
+ $this->assertSame(['price' => 100], $value);
+ // Test null array values
+ $array = ['foo' => null, 'bar' => ['baz' => null]];
+ $this->assertNull(Arr::get($array, 'foo', 'default'));
+ $this->assertNull(Arr::get($array, 'bar.baz', 'default'));
+ // Test null key returns the whole array
+ $array = ['foo', 'bar'];
+ $this->assertSame($array, Arr::get($array, null));
+ // Test $array is empty and key is null
+ $this->assertSame([], Arr::get([], null));
+ $this->assertSame([], Arr::get([], null, 'default'));
+ }
+
+ public function testHas()
+ {
+ $array = ['products.item' => ['price' => 100]];
+ $this->assertTrue(Arr::has($array, 'products.item'));
+ $array = ['products' => ['item' => ['price' => 100]]];
+ $this->assertTrue(Arr::has($array, 'products.item'));
+ $this->assertTrue(Arr::has($array, 'products.item.price'));
+ $this->assertFalse(Arr::has($array, 'products.foo'));
+ $this->assertFalse(Arr::has($array, 'products.item.foo'));
+ $array = ['foo' => null, 'bar' => ['baz' => null]];
+ $this->assertTrue(Arr::has($array, 'foo'));
+ $this->assertTrue(Arr::has($array, 'bar.baz'));
+ $array = ['foo', 'bar'];
+ $this->assertFalse(Arr::has($array, null));
+ $this->assertFalse(Arr::has([], null));
+ $array = ['products' => ['item' => ['price' => 100]]];
+ $this->assertTrue(Arr::has($array, ['products.item']));
+ $this->assertTrue(Arr::has($array, ['products.item', 'products.item.price']));
+ $this->assertTrue(Arr::has($array, ['products', 'products']));
+ $this->assertFalse(Arr::has($array, ['foo']));
+ $this->assertFalse(Arr::has($array, []));
+ $this->assertFalse(Arr::has($array, ['products.item', 'products.price']));
+ $this->assertFalse(Arr::has([], [null]));
+ }
+
+ public function testIsAssoc()
+ {
+ $this->assertTrue(Arr::isAssoc(['a' => 'a', 0 => 'b']));
+ $this->assertTrue(Arr::isAssoc([1 => 'a', 0 => 'b']));
+ $this->assertTrue(Arr::isAssoc([1 => 'a', 2 => 'b']));
+ $this->assertFalse(Arr::isAssoc([0 => 'a', 1 => 'b']));
+ $this->assertFalse(Arr::isAssoc(['a', 'b']));
+ }
+
+ public function testOnly()
+ {
+ $array = ['name' => 'ThinkPHP', 'price' => 100, 'orders' => 10];
+ $array = Arr::only($array, ['name', 'price']);
+ $this->assertSame(['name' => 'ThinkPHP', 'price' => 100], $array);
+ }
+
+ public function testPrepend()
+ {
+ $array = Arr::prepend(['one', 'two', 'three', 'four'], 'zero');
+ $this->assertSame(['zero', 'one', 'two', 'three', 'four'], $array);
+ $array = Arr::prepend(['one' => 1, 'two' => 2], 0, 'zero');
+ $this->assertSame(['zero' => 0, 'one' => 1, 'two' => 2], $array);
+ }
+
+ public function testPull()
+ {
+ $array = ['name' => 'ThinkPHP', 'price' => 100];
+ $name = Arr::pull($array, 'name');
+ $this->assertSame('ThinkPHP', $name);
+ $this->assertSame(['price' => 100], $array);
+ // Only works on first level keys
+ $array = ['i@example.com' => 'Joe', 'jack@localhost' => 'Jane'];
+ $name = Arr::pull($array, 'i@example.com');
+ $this->assertSame('Joe', $name);
+ $this->assertSame(['jack@localhost' => 'Jane'], $array);
+ // Does not work for nested keys
+ $array = ['emails' => ['i@example.com' => 'Joe', 'jack@localhost' => 'Jane']];
+ $name = Arr::pull($array, 'emails.i@example.com');
+ $this->assertNull($name);
+ $this->assertSame(['emails' => ['i@example.com' => 'Joe', 'jack@localhost' => 'Jane']], $array);
+ }
+
+ public function testRandom()
+ {
+ $randomValue = Arr::random(['foo', 'bar', 'baz']);
+ $this->assertContains($randomValue, ['foo', 'bar', 'baz']);
+ $randomValues = Arr::random(['foo', 'bar', 'baz'], 1);
+ $this->assertIsArray($randomValues);
+ $this->assertCount(1, $randomValues);
+ $this->assertContains($randomValues[0], ['foo', 'bar', 'baz']);
+ $randomValues = Arr::random(['foo', 'bar', 'baz'], 2);
+ $this->assertIsArray($randomValues);
+ $this->assertCount(2, $randomValues);
+ $this->assertContains($randomValues[0], ['foo', 'bar', 'baz']);
+ $this->assertContains($randomValues[1], ['foo', 'bar', 'baz']);
+ }
+
+ public function testSet()
+ {
+ $array = ['products' => ['item' => ['price' => 100]]];
+ Arr::set($array, 'products.item.price', 200);
+ Arr::set($array, 'goods.item.price', 200);
+ $this->assertSame(['products' => ['item' => ['price' => 200]], 'goods' => ['item' => ['price' => 200]]], $array);
+ }
+
+ public function testWhere()
+ {
+ $array = [100, '200', 300, '400', 500];
+ $array = Arr::where($array, function ($value, $key) {
+ return is_string($value);
+ });
+ $this->assertSame([1 => '200', 3 => '400'], $array);
+ }
+
+ public function testWhereKey()
+ {
+ $array = ['10' => 1, 'foo' => 3, 20 => 2];
+ $array = Arr::where($array, function ($value, $key) {
+ return is_numeric($key);
+ });
+ $this->assertSame(['10' => 1, 20 => 2], $array);
+ }
+
+ public function testForget()
+ {
+ $array = ['products' => ['item' => ['price' => 100]]];
+ Arr::forget($array, null);
+ $this->assertSame(['products' => ['item' => ['price' => 100]]], $array);
+ $array = ['products' => ['item' => ['price' => 100]]];
+ Arr::forget($array, []);
+ $this->assertSame(['products' => ['item' => ['price' => 100]]], $array);
+ $array = ['products' => ['item' => ['price' => 100]]];
+ Arr::forget($array, 'products.item');
+ $this->assertSame(['products' => []], $array);
+ $array = ['products' => ['item' => ['price' => 100]]];
+ Arr::forget($array, 'products.item.price');
+ $this->assertSame(['products' => ['item' => []]], $array);
+ $array = ['products' => ['item' => ['price' => 100]]];
+ Arr::forget($array, 'products.final.price');
+ $this->assertSame(['products' => ['item' => ['price' => 100]]], $array);
+ $array = ['shop' => ['cart' => [150 => 0]]];
+ Arr::forget($array, 'shop.final.cart');
+ $this->assertSame(['shop' => ['cart' => [150 => 0]]], $array);
+ $array = ['products' => ['item' => ['price' => ['original' => 50, 'taxes' => 60]]]];
+ Arr::forget($array, 'products.item.price.taxes');
+ $this->assertSame(['products' => ['item' => ['price' => ['original' => 50]]]], $array);
+ $array = ['products' => ['item' => ['price' => ['original' => 50, 'taxes' => 60]]]];
+ Arr::forget($array, 'products.item.final.taxes');
+ $this->assertSame(['products' => ['item' => ['price' => ['original' => 50, 'taxes' => 60]]]], $array);
+ $array = ['products' => ['item' => ['price' => 50], null => 'something']];
+ Arr::forget($array, ['products.amount.all', 'products.item.price']);
+ $this->assertSame(['products' => ['item' => [], null => 'something']], $array);
+ // Only works on first level keys
+ $array = ['i@example.com' => 'Joe', 'i@thinkphp.com' => 'Jane'];
+ Arr::forget($array, 'i@example.com');
+ $this->assertSame(['i@thinkphp.com' => 'Jane'], $array);
+ // Does not work for nested keys
+ $array = ['emails' => ['i@example.com' => ['name' => 'Joe'], 'jack@localhost' => ['name' => 'Jane']]];
+ Arr::forget($array, ['emails.i@example.com', 'emails.jack@localhost']);
+ $this->assertSame(['emails' => ['i@example.com' => ['name' => 'Joe']]], $array);
+ }
+
+ public function testWrap()
+ {
+ $string = 'a';
+ $array = ['a'];
+ $object = new stdClass();
+ $object->value = 'a';
+ $this->assertSame(['a'], Arr::wrap($string));
+ $this->assertSame($array, Arr::wrap($array));
+ $this->assertSame([$object], Arr::wrap($object));
+ }
+}
diff --git a/vendor/topthink/think-helper/tests/CollectionTest.php b/vendor/topthink/think-helper/tests/CollectionTest.php
new file mode 100644
index 0000000..b69c175
--- /dev/null
+++ b/vendor/topthink/think-helper/tests/CollectionTest.php
@@ -0,0 +1,70 @@
+ 'Hello']);
+ $this->assertSame(['name' => 'Hello', 'id' => 1], $c->merge(['id' => 1])->all());
+ }
+
+ public function testFirst()
+ {
+ $c = new Collection(['name' => 'Hello', 'age' => 25]);
+
+ $this->assertSame('Hello', $c->first());
+ }
+
+ public function testLast()
+ {
+ $c = new Collection(['name' => 'Hello', 'age' => 25]);
+
+ $this->assertSame(25, $c->last());
+ }
+
+ public function testToArray()
+ {
+ $c = new Collection(['name' => 'Hello', 'age' => 25]);
+
+ $this->assertSame(['name' => 'Hello', 'age' => 25], $c->toArray());
+ }
+
+ public function testToJson()
+ {
+ $c = new Collection(['name' => 'Hello', 'age' => 25]);
+
+ $this->assertSame(json_encode(['name' => 'Hello', 'age' => 25]), $c->toJson());
+ $this->assertSame(json_encode(['name' => 'Hello', 'age' => 25]), (string) $c);
+ $this->assertSame(json_encode(['name' => 'Hello', 'age' => 25]), json_encode($c));
+ }
+
+ public function testSerialize()
+ {
+ $c = new Collection(['name' => 'Hello', 'age' => 25]);
+
+ $sc = serialize($c);
+ $c = unserialize($sc);
+
+ $this->assertSame(['name' => 'Hello', 'age' => 25], $c->all());
+ }
+
+ public function testGetIterator()
+ {
+ $c = new Collection(['name' => 'Hello', 'age' => 25]);
+
+ $this->assertInstanceOf(\ArrayIterator::class, $c->getIterator());
+
+ $this->assertSame(['name' => 'Hello', 'age' => 25], $c->getIterator()->getArrayCopy());
+ }
+
+ public function testCount()
+ {
+ $c = new Collection(['name' => 'Hello', 'age' => 25]);
+
+ $this->assertCount(2, $c);
+ }
+}
diff --git a/vendor/topthink/think-helper/tests/StrTest.php b/vendor/topthink/think-helper/tests/StrTest.php
new file mode 100644
index 0000000..813ad4c
--- /dev/null
+++ b/vendor/topthink/think-helper/tests/StrTest.php
@@ -0,0 +1,59 @@
+assertSame('fooBar', Str::camel('FooBar'));
+ $this->assertSame('fooBar', Str::camel('FooBar'));
+ $this->assertSame('fooBar', Str::camel('foo_bar'));
+ $this->assertSame('fooBar', Str::camel('_foo_bar'));
+ $this->assertSame('fooBar', Str::camel('_foo_bar_'));
+ }
+
+ public function testStudly()
+ {
+ $this->assertSame('FooBar', Str::studly('fooBar'));
+ $this->assertSame('FooBar', Str::studly('_foo_bar'));
+ $this->assertSame('FooBar', Str::studly('_foo_bar_'));
+ $this->assertSame('FooBar', Str::studly('_foo_bar_'));
+ }
+
+ public function testSnake()
+ {
+ $this->assertSame('think_p_h_p_framework', Str::snake('ThinkPHPFramework'));
+ $this->assertSame('think_php_framework', Str::snake('ThinkPhpFramework'));
+ $this->assertSame('think php framework', Str::snake('ThinkPhpFramework', ' '));
+ $this->assertSame('think_php_framework', Str::snake('Think Php Framework'));
+ $this->assertSame('think_php_framework', Str::snake('Think Php Framework '));
+ // ensure cache keys don't overlap
+ $this->assertSame('think__php__framework', Str::snake('ThinkPhpFramework', '__'));
+ $this->assertSame('think_php_framework_', Str::snake('ThinkPhpFramework_', '_'));
+ $this->assertSame('think_php_framework', Str::snake('think php Framework'));
+ $this->assertSame('think_php_frame_work', Str::snake('think php FrameWork'));
+ // prevent breaking changes
+ $this->assertSame('foo-bar', Str::snake('foo-bar'));
+ $this->assertSame('foo-_bar', Str::snake('Foo-Bar'));
+ $this->assertSame('foo__bar', Str::snake('Foo_Bar'));
+ $this->assertSame('żółtałódka', Str::snake('ŻółtaŁódka'));
+ }
+
+ public function testTitle()
+ {
+ $this->assertSame('Welcome Back', Str::title('welcome back'));
+ }
+
+ public function testRandom()
+ {
+ $this->assertIsString(Str::random(10));
+ }
+
+ public function testUpper()
+ {
+ $this->assertSame('USERNAME', Str::upper('username'));
+ $this->assertSame('USERNAME', Str::upper('userNaMe'));
+ }
+}
diff --git a/vendor/topthink/think-helper/tests/TestCase.php b/vendor/topthink/think-helper/tests/TestCase.php
new file mode 100644
index 0000000..87f6cb3
--- /dev/null
+++ b/vendor/topthink/think-helper/tests/TestCase.php
@@ -0,0 +1,13 @@
+
+ */
+class TestCase extends BaseTestCase
+{
+}
diff --git a/vendor/topthink/think-orm/.gitignore b/vendor/topthink/think-orm/.gitignore
new file mode 100644
index 0000000..82cfc4e
--- /dev/null
+++ b/vendor/topthink/think-orm/.gitignore
@@ -0,0 +1,3 @@
+.idea
+composer.lock
+vendor
diff --git a/vendor/topthink/think-orm/src/Model.php b/vendor/topthink/think-orm/src/Model.php
index 00016d0..761f780 100644
--- a/vendor/topthink/think-orm/src/Model.php
+++ b/vendor/topthink/think-orm/src/Model.php
@@ -260,11 +260,12 @@ abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonab
/**
* 创建新的模型实例
* @access public
- * @param array $data 数据
- * @param mixed $where 更新条件
+ * @param array $data 数据
+ * @param mixed $where 更新条件
+ * @param array $options 参数
* @return Model
*/
- public function newInstance(array $data = [], $where = null): Model
+ public function newInstance(array $data = [], $where = null, array $options = []): Model
{
$model = new static($data);
@@ -970,21 +971,25 @@ abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonab
}
// ArrayAccess
+ #[\ReturnTypeWillChange]
public function offsetSet($name, $value)
{
$this->setAttr($name, $value);
}
+ #[\ReturnTypeWillChange]
public function offsetExists($name): bool
{
return $this->__isset($name);
}
+ #[\ReturnTypeWillChange]
public function offsetUnset($name)
{
$this->__unset($name);
}
+ #[\ReturnTypeWillChange]
public function offsetGet($name)
{
return $this->getAttr($name);
@@ -1037,10 +1042,6 @@ abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonab
return call_user_func_array(static::$macro[static::class][$method]->bindTo($this, static::class), $args);
}
- if ('withattr' == strtolower($method)) {
- return call_user_func_array([$this, 'withAttribute'], $args);
- }
-
return call_user_func_array([$this->db(), $method], $args);
}
diff --git a/vendor/topthink/think-orm/src/Paginator.php b/vendor/topthink/think-orm/src/Paginator.php
index d8d43d7..2f755ef 100644
--- a/vendor/topthink/think-orm/src/Paginator.php
+++ b/vendor/topthink/think-orm/src/Paginator.php
@@ -410,7 +410,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* @return Traversable An instance of an object implementing Iterator or
* Traversable
*/
- public function getIterator()
+ #[\ReturnTypeWillChange]
+ public function getIterator(): Traversable
{
return new ArrayIterator($this->items->all());
}
@@ -421,7 +422,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* @param mixed $offset
* @return bool
*/
- public function offsetExists($offset)
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset): bool
{
return $this->items->offsetExists($offset);
}
@@ -432,6 +434,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* @param mixed $offset
* @return mixed
*/
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->items->offsetGet($offset);
@@ -443,6 +446,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* @param mixed $offset
* @param mixed $value
*/
+ #[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->items->offsetSet($offset, $value);
@@ -455,6 +459,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
* @return void
* @since 5.0.0
*/
+ #[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
$this->items->offsetUnset($offset);
@@ -498,6 +503,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
/**
* Specify data which should be serialized to JSON
*/
+ #[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->toArray();
diff --git a/vendor/topthink/think-orm/src/db/BaseQuery.php b/vendor/topthink/think-orm/src/db/BaseQuery.php
index a0acdbe..7bb778a 100644
--- a/vendor/topthink/think-orm/src/db/BaseQuery.php
+++ b/vendor/topthink/think-orm/src/db/BaseQuery.php
@@ -137,7 +137,7 @@ abstract class BaseQuery
$query->name($this->name);
}
- if (isset($this->options['json'])) {
+ if (!empty($this->options['json'])) {
$query->json($this->options['json'], $this->options['json_assoc']);
}
@@ -278,7 +278,11 @@ abstract class BaseQuery
public function column($field, string $key = ''): array
{
$result = $this->connection->column($this, $field, $key);
- $this->resultSet($result, false);
+
+ if (count($result) != count($result, 1)) {
+ $this->resultSet($result, false);
+ }
+
return $result;
}
@@ -867,6 +871,7 @@ abstract class BaseQuery
{
$this->options['json'] = $json;
$this->options['json_assoc'] = $assoc;
+
return $this;
}
@@ -1124,7 +1129,7 @@ abstract class BaseQuery
* 查找单条记录
* @access public
* @param mixed $data 查询数据
- * @return array|Model|null|static
+ * @return array|Model|null|static|mixed
* @throws Exception
* @throws ModelNotFoundException
* @throws DataNotFoundException
@@ -1149,7 +1154,7 @@ abstract class BaseQuery
if (!empty($this->model)) {
// 返回模型对象
- $this->resultToModel($result, $this->options);
+ $this->resultToModel($result);
} else {
$this->result($result);
}
@@ -1178,7 +1183,7 @@ abstract class BaseQuery
$this->parseView($options);
}
- foreach (['data', 'order', 'join', 'union'] as $name) {
+ foreach (['data', 'order', 'join', 'union', 'filter', 'json', 'with_attr', 'with_relation_attr'] as $name) {
if (!isset($options[$name])) {
$options[$name] = [];
}
@@ -1188,7 +1193,7 @@ abstract class BaseQuery
$options['strict'] = $this->connection->getConfig('fields_strict');
}
- foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) {
+ foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure', 'with_cache'] as $name) {
if (!isset($options[$name])) {
$options[$name] = false;
}
diff --git a/vendor/topthink/think-orm/src/db/Fetch.php b/vendor/topthink/think-orm/src/db/Fetch.php
index 16caed2..a997a85 100644
--- a/vendor/topthink/think-orm/src/db/Fetch.php
+++ b/vendor/topthink/think-orm/src/db/Fetch.php
@@ -421,10 +421,8 @@ class Fetch
if (!empty($options['group'])) {
// 支持GROUP
- $bind = $this->query->getBind();
- $subSql = $this->query->options($options)->field('count(' . $field . ') AS think_count')->bind($bind)->buildSql();
-
- $query = $this->query->newQuery()->table([$subSql => '_group_count_']);
+ $subSql = $this->query->field('count(' . $field . ') AS think_count')->buildSql();
+ $query = $this->query->newQuery()->table([$subSql => '_group_count_']);
return $query->fetchsql()->aggregate('COUNT', '*');
} else {
diff --git a/vendor/topthink/think-orm/src/db/Mongo.php b/vendor/topthink/think-orm/src/db/Mongo.php
index 5e8a09a..cf6e9c4 100644
--- a/vendor/topthink/think-orm/src/db/Mongo.php
+++ b/vendor/topthink/think-orm/src/db/Mongo.php
@@ -630,7 +630,7 @@ class Mongo extends BaseQuery
$options['table'] = $this->getTable();
}
- foreach (['where', 'data'] as $name) {
+ foreach (['where', 'data', 'projection', 'filter', 'json', 'with_attr', 'with_relation_attr'] as $name) {
if (!isset($options[$name])) {
$options[$name] = [];
}
@@ -649,10 +649,6 @@ class Mongo extends BaseQuery
$options['modifiers'] = $modifiers;
}
- if (!isset($options['projection'])) {
- $options['projection'] = [];
- }
-
if (!isset($options['typeMap'])) {
$options['typeMap'] = $this->getConfig('type_map');
}
diff --git a/vendor/topthink/think-orm/src/db/PDOConnection.php b/vendor/topthink/think-orm/src/db/PDOConnection.php
index 1a3d4a2..40ac99e 100644
--- a/vendor/topthink/think-orm/src/db/PDOConnection.php
+++ b/vendor/topthink/think-orm/src/db/PDOConnection.php
@@ -279,7 +279,7 @@ abstract class PDOConnection extends Connection
*/
protected function getFieldType(string $type): string
{
- if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) {
+ if (0 === stripos($type, 'set') || 0 === stripos($type, 'enum')) {
$result = 'string';
} elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) {
$result = 'float';
@@ -287,11 +287,11 @@ abstract class PDOConnection extends Connection
$result = 'int';
} elseif (preg_match('/bool/is', $type)) {
$result = 'bool';
- } elseif (0 === strpos($type, 'timestamp')) {
+ } elseif (0 === stripos($type, 'timestamp')) {
$result = 'timestamp';
- } elseif (0 === strpos($type, 'datetime')) {
+ } elseif (0 === stripos($type, 'datetime')) {
$result = 'datetime';
- } elseif (0 === strpos($type, 'date')) {
+ } elseif (0 === stripos($type, 'date')) {
$result = 'date';
} else {
$result = 'string';
@@ -1273,7 +1273,7 @@ abstract class PDOConnection extends Connection
$type = is_array($val) ? $val[1] : PDO::PARAM_STR;
if (self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) {
- $value = '\'' . addslashes($value) . '\'';
+ $value = '\'' . addcslashes($value, "'") . '\'';
} elseif (PDO::PARAM_INT == $type && '' === $value) {
$value = '0';
}
diff --git a/vendor/topthink/think-orm/src/db/Raw.php b/vendor/topthink/think-orm/src/db/Raw.php
index 833fbf0..b956ff6 100644
--- a/vendor/topthink/think-orm/src/db/Raw.php
+++ b/vendor/topthink/think-orm/src/db/Raw.php
@@ -64,8 +64,4 @@ class Raw
return $this->bind;
}
- public function __toString()
- {
- return (string) $this->value;
- }
}
diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlite.php b/vendor/topthink/think-orm/src/db/builder/Sqlite.php
index 40cab7f..ff17c5d 100644
--- a/vendor/topthink/think-orm/src/db/builder/Sqlite.php
+++ b/vendor/topthink/think-orm/src/db/builder/Sqlite.php
@@ -24,8 +24,8 @@ class Sqlite extends Builder
/**
* limit
* @access public
- * @param Query $query 查询对象
- * @param mixed $limit
+ * @param Query $query 查询对象
+ * @param mixed $limit
* @return string
*/
public function parseLimit(Query $query, string $limit): string
@@ -47,7 +47,7 @@ class Sqlite extends Builder
/**
* 随机排序
* @access protected
- * @param Query $query 查询对象
+ * @param Query $query 查询对象
* @return string
*/
protected function parseRand(Query $query): string
@@ -58,9 +58,9 @@ class Sqlite extends Builder
/**
* 字段和表名处理
* @access public
- * @param Query $query 查询对象
- * @param mixed $key 字段名
- * @param bool $strict 严格检测
+ * @param Query $query 查询对象
+ * @param mixed $key 字段名
+ * @param bool $strict 严格检测
* @return string
*/
public function parseKey(Query $query, $key, bool $strict = false): string
@@ -73,7 +73,7 @@ class Sqlite extends Builder
$key = trim($key);
- if (strpos($key, '.')) {
+ if (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
[$table, $key] = explode('.', $key, 2);
$alias = $query->getOptions('alias');
@@ -88,10 +88,26 @@ class Sqlite extends Builder
}
}
+ if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
+ $key = '`' . $key . '`';
+ }
+
if (isset($table)) {
- $key = $table . '.' . $key;
+ $key = '`' . $table . '`.' . $key;
}
return $key;
}
+
+ /**
+ * 设置锁机制
+ * @access protected
+ * @param Query $query 查询对象
+ * @param bool|string $lock
+ * @return string
+ */
+ protected function parseLock(Query $query, $lock = false): string
+ {
+ return '';
+ }
}
diff --git a/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php
index ffb72de..0015a24 100644
--- a/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php
+++ b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php
@@ -54,36 +54,39 @@ trait ModelRelationQuery
/**
* 设置需要隐藏的输出属性
* @access public
- * @param array $hidden 需要隐藏的字段名
+ * @param array $hidden 属性列表
* @return $this
*/
- public function hidden(array $hidden)
+ public function hidden(array $hidden = [])
{
$this->options['hidden'] = $hidden;
+
return $this;
}
/**
* 设置需要输出的属性
* @access public
- * @param array $visible 需要输出的属性
+ * @param array $visible
* @return $this
*/
- public function visible(array $visible)
+ public function visible(array $visible = [])
{
$this->options['visible'] = $visible;
+
return $this;
}
/**
- * 设置需要追加输出的属性
+ * 设置需要附加的输出属性
* @access public
- * @param array $append 需要追加的属性
+ * @param array $append 属性列表
* @return $this
*/
- public function append(array $append)
+ public function append(array $append = [])
{
$this->options['append'] = $append;
+
return $this;
}
@@ -130,10 +133,11 @@ trait ModelRelationQuery
*/
public function relation(array $relation)
{
- if (!empty($relation)) {
- $this->options['relation'] = $relation;
+ if (empty($this->model) || empty($relation)) {
+ return $this;
}
+ $this->options['relation'] = $relation;
return $this;
}
@@ -175,16 +179,30 @@ trait ModelRelationQuery
/**
* 设置数据字段获取器
* @access public
- * @param string|array $name 字段名
- * @param callable $callback 闭包获取器
+ * @param string|array $name 字段名
+ * @param callable $callback 闭包获取器
* @return $this
*/
public function withAttr($name, callable $callback = null)
{
if (is_array($name)) {
- $this->options['with_attr'] = $name;
- } else {
- $this->options['with_attr'][$name] = $callback;
+ foreach ($name as $key => $val) {
+ $this->withAttr($key, $val);
+ }
+ return $this;
+ }
+
+ $this->options['with_attr'][$name] = $callback;
+
+ if (strpos($name, '.')) {
+ [$relation, $field] = explode('.', $name);
+
+ if (!empty($this->options['json']) && in_array($relation, $this->options['json'])) {
+
+ } else {
+ $this->options['with_relation_attr'][$relation][$field] = $callback;
+ unset($this->options['with_attr'][$name]);
+ }
}
return $this;
@@ -198,10 +216,11 @@ trait ModelRelationQuery
*/
public function with($with)
{
- if (!empty($with)) {
- $this->options['with'] = (array) $with;
+ if (empty($this->model) || empty($with)) {
+ return $this;
}
+ $this->options['with'] = (array) $with;
return $this;
}
@@ -214,7 +233,7 @@ trait ModelRelationQuery
*/
public function withJoin($with, string $joinType = '')
{
- if (empty($with)) {
+ if (empty($this->model) || empty($with)) {
return $this;
}
@@ -246,7 +265,6 @@ trait ModelRelationQuery
}
$this->via();
-
$this->options['with_join'] = $with;
return $this;
@@ -263,16 +281,20 @@ trait ModelRelationQuery
*/
protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true)
{
+ if (empty($this->model)) {
+ return $this;
+ }
+
if (!$subQuery) {
- $this->options['with_count'][] = [$relations, $aggregate, $field];
- } else {
- if (!isset($this->options['field'])) {
- $this->field('*');
- }
+ $this->options['with_aggregate'][] = [(array) $relations, $aggregate, $field];
+ return $this;
+ }
- $this->model->relationCount($this, (array) $relations, $aggregate, $field, true);
+ if (!isset($this->options['field'])) {
+ $this->field('*');
}
+ $this->model->relationCount($this, (array) $relations, $aggregate, $field, true);
return $this;
}
@@ -287,6 +309,10 @@ trait ModelRelationQuery
*/
public function withCache($relation = true, $key = true, $expire = null, string $tag = null)
{
+ if (empty($this->model)) {
+ return $this;
+ }
+
if (false === $relation || false === $key || !$this->getConnection()->getCache()) {
return $this;
}
@@ -406,6 +432,32 @@ trait ModelRelationQuery
return $this->model->hasWhere($relation, $where, $fields, $joinType, $this);
}
+ /**
+ * JSON字段数据转换
+ * @access protected
+ * @param array $result 查询数据
+ * @return void
+ */
+ protected function jsonModelResult(array &$result): void
+ {
+ $withAttr = $this->options['with_attr'];
+ foreach ($this->options['json'] as $name) {
+ if (!isset($result[$name])) {
+ continue;
+ }
+
+ $jsonData = json_decode($result[$name], true);
+
+ if (isset($withAttr[$name])) {
+ foreach ($withAttr[$name] as $key => $closure) {
+ $jsonData[$key] = $closure($jsonData[$key] ?? null, $jsonData);
+ }
+ }
+
+ $result[$name] = !$this->options['json_assoc'] ? (object) $jsonData : $jsonData;
+ }
+ }
+
/**
* 查询数据转换为模型数据集对象
* @access protected
@@ -418,33 +470,24 @@ trait ModelRelationQuery
return $this->model->toCollection();
}
- // 检查动态获取器
- if (!empty($this->options['with_attr'])) {
- foreach ($this->options['with_attr'] as $name => $val) {
- if (strpos($name, '.')) {
- [$relation, $field] = explode('.', $name);
-
- $withRelationAttr[$relation][$field] = $val;
- unset($this->options['with_attr'][$name]);
- }
- }
- }
-
- $withRelationAttr = $withRelationAttr ?? [];
+ $this->options['is_resultSet'] = true;
foreach ($resultSet as $key => &$result) {
// 数据转换为模型对象
- $this->resultToModel($result, $this->options, true, $withRelationAttr);
- }
-
- if (!empty($this->options['with'])) {
- // 预载入
- $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr, false, $this->options['with_cache'] ?? false);
+ $this->resultToModel($result);
}
- if (!empty($this->options['with_join'])) {
- // 预载入
- $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true, $this->options['with_cache'] ?? false);
+ foreach (['with', 'with_join'] as $with) {
+ // 关联预载入
+ if (!empty($this->options[$with])) {
+ $result->eagerlyResultSet(
+ $resultSet,
+ $this->options[$with],
+ $this->options['with_relation_attr'],
+ 'with_join' == $with ? true : false,
+ $this->options['with_cache'] ?? false
+ );
+ }
}
// 模型数据集转换
@@ -455,70 +498,84 @@ trait ModelRelationQuery
* 查询数据转换为模型对象
* @access protected
* @param array $result 查询数据
- * @param array $options 查询参数
- * @param bool $resultSet 是否为数据集查询
- * @param array $withRelationAttr 关联字段获取器
* @return void
*/
- protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void
+ protected function resultToModel(array &$result): void
{
- // 动态获取器
- if (!empty($options['with_attr']) && empty($withRelationAttr)) {
- foreach ($options['with_attr'] as $name => $val) {
- if (strpos($name, '.')) {
- [$relation, $field] = explode('.', $name);
-
- $withRelationAttr[$relation][$field] = $val;
- unset($options['with_attr'][$name]);
- }
- }
- }
-
- // JSON 数据处理
- if (!empty($options['json'])) {
- $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr);
+ // JSON数据处理
+ if (!empty($this->options['json'])) {
+ $this->jsonModelResult($result);
}
- $result = $this->model
- ->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options));
+ $result = $this->model->newInstance(
+ $result,
+ !empty($this->options['is_resultSet']) ? null : $this->getModelUpdateCondition($this->options),
+ $this->options
+ );
- // 动态获取器
- if (!empty($options['with_attr'])) {
- $result->withAttribute($options['with_attr']);
- }
-
- // 输出属性控制
- if (!empty($options['visible'])) {
- $result->visible($options['visible']);
- } elseif (!empty($options['hidden'])) {
- $result->hidden($options['hidden']);
+ // 模型数据处理
+ foreach ($this->options['filter'] as $filter) {
+ call_user_func_array($filter, [$result, $this->options]);
}
- if (!empty($options['append'])) {
- $result->append($options['append']);
+ // 关联查询
+ if (!empty($this->options['relation'])) {
+ $result->relationQuery($this->options['relation'], $this->options['with_relation_attr']);
}
- // 关联查询
- if (!empty($options['relation'])) {
- $result->relationQuery($options['relation'], $withRelationAttr);
+ // 关联预载入查询
+ if (empty($this->options['is_resultSet'])) {
+ foreach (['with', 'with_join'] as $with) {
+ if (!empty($this->options[$with])) {
+ $result->eagerlyResult(
+ $this->options[$with],
+ $this->options['with_relation_attr'],
+ 'with_join' == $with ? true : false,
+ $this->options['with_cache'] ?? false
+ );
+ }
+ }
}
- // 预载入查询
- if (!$resultSet && !empty($options['with'])) {
- $result->eagerlyResult($result, $options['with'], $withRelationAttr, false, $options['with_cache'] ?? false);
+ // 关联统计查询
+ if (!empty($this->options['with_aggregate'])) {
+ foreach ($this->options['with_aggregate'] as $val) {
+ $result->relationCount($this, $val[0], $val[1], $val[2], false);
+ }
}
- // JOIN预载入查询
- if (!$resultSet && !empty($options['with_join'])) {
- $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true, $options['with_cache'] ?? false);
+ // 动态获取器
+ if (!empty($this->options['with_attr'])) {
+ $result->withAttr($this->options['with_attr']);
}
- // 关联统计
- if (!empty($options['with_count'])) {
- foreach ($options['with_count'] as $val) {
- $result->relationCount($this, (array) $val[0], $val[1], $val[2], false);
+ foreach (['hidden', 'visible', 'append'] as $name) {
+ if (!empty($this->options[$name])) {
+ $result->$name($this->options[$name]);
}
}
+
+ // 刷新原始数据
+ $result->refreshOrigin();
+ }
+
+ /**
+ * 查询软删除数据
+ * @access public
+ * @return Query
+ */
+ public function withTrashed()
+ {
+ return $this->model ? $this->model->queryWithTrashed() : $this;
}
+ /**
+ * 只查询软删除数据
+ * @access public
+ * @return Query
+ */
+ public function onlyTrashed()
+ {
+ return $this->model ? $this->model->queryOnlyTrashed() : $this;
+ }
}
diff --git a/vendor/topthink/think-orm/src/db/concern/ResultOperation.php b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php
index 77409d1..ea26916 100644
--- a/vendor/topthink/think-orm/src/db/concern/ResultOperation.php
+++ b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php
@@ -26,6 +26,23 @@ use think\Model;
*/
trait ResultOperation
{
+ /**
+ * 设置数据处理(支持模型)
+ * @access public
+ * @param callable $filter 数据处理Callable
+ * @param string $index 索引(唯一)
+ * @return $this
+ */
+ public function filter(callable $filter, string $index = null)
+ {
+ if ($index) {
+ $this->options['filter'][$index] = $filter;
+ } else {
+ $this->options['filter'][] = $filter;
+ }
+ return $this;
+ }
+
/**
* 是否允许返回空数据(或空模型)
* @access public
@@ -58,15 +75,20 @@ trait ResultOperation
*/
protected function result(array &$result): void
{
+ // JSON数据处理
if (!empty($this->options['json'])) {
- $this->jsonResult($result, $this->options['json'], true);
+ $this->jsonResult($result);
}
+ // 查询数据处理
+ foreach ($this->options['filter'] as $filter) {
+ $result = call_user_func_array($filter, [$result, $this->options]);
+ }
+
+ // 获取器
if (!empty($this->options['with_attr'])) {
$this->getResultAttr($result, $this->options['with_attr']);
}
-
- $this->filterResult($result);
}
/**
@@ -78,22 +100,8 @@ trait ResultOperation
*/
protected function resultSet(array &$resultSet, bool $toCollection = true): void
{
- if (!empty($this->options['json'])) {
- foreach ($resultSet as &$result) {
- $this->jsonResult($result, $this->options['json'], true);
- }
- }
-
- if (!empty($this->options['with_attr'])) {
- foreach ($resultSet as &$result) {
- $this->getResultAttr($result, $this->options['with_attr']);
- }
- }
-
- if (!empty($this->options['visible']) || !empty($this->options['hidden'])) {
- foreach ($resultSet as &$result) {
- $this->filterResult($result);
- }
+ foreach ($resultSet as &$result) {
+ $this->result($result);
}
// 返回Collection对象
@@ -102,28 +110,6 @@ trait ResultOperation
}
}
- /**
- * 处理数据的可见和隐藏
- * @access protected
- * @param array $result 查询数据
- * @return void
- */
- protected function filterResult(&$result): void
- {
- $array = [];
- if (!empty($this->options['visible'])) {
- foreach ($this->options['visible'] as $key) {
- $array[] = $key;
- }
- $result = array_intersect_key($result, array_flip($array));
- } elseif (!empty($this->options['hidden'])) {
- foreach ($this->options['hidden'] as $key) {
- $array[] = $key;
- }
- $result = array_diff_key($result, array_flip($array));
- }
- }
-
/**
* 使用获取器处理数据
* @access protected
@@ -170,7 +156,7 @@ trait ResultOperation
* 查找单条记录 不存在返回空数据(或者空模型)
* @access public
* @param mixed $data 数据
- * @return array|Model|static
+ * @return array|Model|static|mixed
*/
public function findOrEmpty($data = null)
{
@@ -180,30 +166,17 @@ trait ResultOperation
/**
* JSON字段数据转换
* @access protected
- * @param array $result 查询数据
- * @param array $json JSON字段
- * @param bool $assoc 是否转换为数组
- * @param array $withRelationAttr 关联获取器
+ * @param array $result 查询数据
* @return void
*/
- protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void
+ protected function jsonResult(array &$result): void
{
- foreach ($json as $name) {
+ foreach ($this->options['json'] as $name) {
if (!isset($result[$name])) {
continue;
}
$result[$name] = json_decode($result[$name], true);
-
- if (isset($withRelationAttr[$name])) {
- foreach ($withRelationAttr[$name] as $key => $closure) {
- $result[$name][$key] = $closure($result[$name][$key] ?? null, $result[$name]);
- }
- }
-
- if (!$assoc) {
- $result[$name] = (object) $result[$name];
- }
}
}
@@ -242,7 +215,7 @@ trait ResultOperation
* 查找单条记录 如果不存在则抛出异常
* @access public
* @param array|string|Query|Closure $data 数据
- * @return array|Model|static
+ * @return array|Model|static|mixed
* @throws ModelNotFoundException
* @throws DataNotFoundException
*/
diff --git a/vendor/topthink/think-orm/src/db/concern/WhereQuery.php b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php
index d2deb03..ef845f5 100644
--- a/vendor/topthink/think-orm/src/db/concern/WhereQuery.php
+++ b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php
@@ -361,9 +361,7 @@ trait WhereQuery
$field = $this->options['via'] . '.' . $field;
}
- if ($field instanceof Raw) {
- return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
- } elseif ($strict) {
+ if ($strict) {
// 使用严格模式查询
if ('=' == $op) {
$where = $this->whereEq($field, $condition);
diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlite.php b/vendor/topthink/think-orm/src/db/connector/Sqlite.php
index c664f20..3e42a90 100644
--- a/vendor/topthink/think-orm/src/db/connector/Sqlite.php
+++ b/vendor/topthink/think-orm/src/db/connector/Sqlite.php
@@ -42,7 +42,7 @@ class Sqlite extends PDOConnection
public function getFields(string $tableName): array
{
[$tableName] = explode(' ', $tableName);
- $sql = 'PRAGMA table_info( ' . $tableName . ' )';
+ $sql = 'PRAGMA table_info( \'' . $tableName . '\' )';
$pdo = $this->getPDOStatement($sql);
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
diff --git a/vendor/topthink/think-orm/src/model/Collection.php b/vendor/topthink/think-orm/src/model/Collection.php
index f017e32..c8ff385 100644
--- a/vendor/topthink/think-orm/src/model/Collection.php
+++ b/vendor/topthink/think-orm/src/model/Collection.php
@@ -157,7 +157,7 @@ class Collection extends BaseCollection
public function withAttr($name, $callback = null)
{
$this->each(function (Model $model) use ($name, $callback) {
- $model->withAttribute($name, $callback);
+ $model->withAttr($name, $callback);
});
return $this;
diff --git a/vendor/topthink/think-orm/src/model/concern/Attribute.php b/vendor/topthink/think-orm/src/model/concern/Attribute.php
index 34cb59a..e4aa07b 100644
--- a/vendor/topthink/think-orm/src/model/concern/Attribute.php
+++ b/vendor/topthink/think-orm/src/model/concern/Attribute.php
@@ -250,6 +250,17 @@ trait Attribute
return $this;
}
+ /**
+ * 刷新对象原始数据(为当前数据)
+ * @access public
+ * @return $this
+ */
+ public function refreshOrigin()
+ {
+ $this->origin = $this->data;
+ return $this;
+ }
+
/**
* 获取对象原始数据 如果不存在指定字段返回null
* @access public
@@ -371,6 +382,9 @@ trait Attribute
} elseif (isset($this->type[$name])) {
// 类型转换
$value = $this->writeTransform($value, $this->type[$name]);
+ } elseif (is_object($value) && method_exists($value, '__toString')) {
+ // 对象类型
+ $value = $value->__toString();
}
// 设置数据对象属性
@@ -531,6 +545,10 @@ trait Attribute
*/
protected function getJsonValue($name, $value)
{
+ if (is_null($value)) {
+ return $value;
+ }
+
foreach ($this->withAttr[$name] as $key => $closure) {
if ($this->jsonAssoc) {
$value[$key] = $closure($value[$key], $value);
@@ -633,11 +651,11 @@ trait Attribute
* @param callable $callback 闭包获取器
* @return $this
*/
- public function withAttribute($name, callable $callback = null)
+ public function withAttr($name, callable $callback = null)
{
if (is_array($name)) {
foreach ($name as $key => $val) {
- $this->withAttribute($key, $val);
+ $this->withAttr($key, $val);
}
} else {
$name = $this->getRealFieldName($name);
diff --git a/vendor/topthink/think-orm/src/model/concern/Conversion.php b/vendor/topthink/think-orm/src/model/concern/Conversion.php
index 35d96d0..22d6256 100644
--- a/vendor/topthink/think-orm/src/model/concern/Conversion.php
+++ b/vendor/topthink/think-orm/src/model/concern/Conversion.php
@@ -330,6 +330,7 @@ trait Conversion
}
// JsonSerializable
+ #[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->toArray();
diff --git a/vendor/topthink/think-orm/src/model/concern/RelationShip.php b/vendor/topthink/think-orm/src/model/concern/RelationShip.php
index 33c1b89..8faadf4 100644
--- a/vendor/topthink/think-orm/src/model/concern/RelationShip.php
+++ b/vendor/topthink/think-orm/src/model/concern/RelationShip.php
@@ -291,14 +291,13 @@ trait RelationShip
/**
* 预载入关联查询 返回模型对象
* @access public
- * @param Model $result 数据对象
* @param array $relations 关联
* @param array $withRelationAttr 关联获取器
* @param bool $join 是否为JOIN方式
* @param mixed $cache 关联缓存
* @return void
*/
- public function eagerlyResult(Model $result, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void
+ public function eagerlyResult(array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void
{
foreach ($relations as $key => $relation) {
$subRelation = [];
@@ -333,7 +332,7 @@ trait RelationShip
$relationCache = $cache[$relationName] ?? [];
}
- $relationResult->eagerlyResult($result, $relationName, $subRelation, $closure, $relationCache, $join);
+ $relationResult->eagerlyResult($this, $relationName, $subRelation, $closure, $relationCache, $join);
}
}
diff --git a/vendor/topthink/think-orm/src/model/concern/SoftDelete.php b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php
index 8d76bb0..117f1ef 100644
--- a/vendor/topthink/think-orm/src/model/concern/SoftDelete.php
+++ b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php
@@ -55,6 +55,16 @@ trait SoftDelete
return $model->withTrashedData(true)->db();
}
+ /**
+ * 查询软删除数据
+ * @access public
+ * @return Query
+ */
+ public function queryWithTrashed(): Query
+ {
+ return $this->withTrashedData(true)->db();
+ }
+
/**
* 是否包含软删除数据
* @access protected
@@ -86,6 +96,23 @@ trait SoftDelete
return $model->db();
}
+ /**
+ * 只查询软删除数据
+ * @access public
+ * @return Query
+ */
+ public function queryOnlyTrashed(): Query
+ {
+ $field = $this->getDeleteTimeField(true);
+
+ if ($field) {
+ return $this->db()
+ ->useSoftDelete($field, $this->getWithTrashedExp());
+ }
+
+ return $this->db();
+ }
+
/**
* 获取软删除数据的查询条件
* @access protected
@@ -152,12 +179,12 @@ trait SoftDelete
public static function destroy($data, bool $force = false): bool
{
// 传入空值(包括空字符串和空数组)的时候不会做任何的数据删除操作,但传入0则是有效的
- if(empty($data) && $data !== 0){
+ if (empty($data) && 0 !== $data) {
return false;
}
// 仅当强制删除时包含软删除数据
$model = (new static());
- if($force){
+ if ($force) {
$model->withTrashedData(true);
}
$query = $model->db(false);
diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsTo.php b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php
index 941e1d4..0802b11 100644
--- a/vendor/topthink/think-orm/src/model/relation/BelongsTo.php
+++ b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php
@@ -236,12 +236,11 @@ class BelongsTo extends OneToOne
$relationModel->exists(true);
}
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
if (!empty($this->bindAttr)) {
// 绑定关联属性
$this->bindAttr($result, $relationModel);
- } else {
- // 设置关联属性
- $result->setRelation($relation, $relationModel);
}
}
}
@@ -277,12 +276,12 @@ class BelongsTo extends OneToOne
$relationModel->exists(true);
}
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
+
if (!empty($this->bindAttr)) {
// 绑定关联属性
$this->bindAttr($result, $relationModel);
- } else {
- // 设置关联属性
- $result->setRelation($relation, $relationModel);
}
}
diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php
index 5f177c1..9890906 100644
--- a/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php
+++ b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php
@@ -19,7 +19,6 @@ use think\db\Raw;
use think\Model;
use think\model\Pivot;
use think\model\Relation;
-use think\Paginator;
/**
* 多对多关联类
@@ -120,31 +119,6 @@ class BelongsToMany extends Relation
}
}
- /**
- * 合成中间表模型
- * @access protected
- * @param array|Collection|Paginator $models
- */
- protected function hydratePivot(iterable $models)
- {
- foreach ($models as $model) {
- $pivot = [];
-
- foreach ($model->getData() as $key => $val) {
- if (strpos($key, '__')) {
- [$name, $attr] = explode('__', $key, 2);
-
- if ('pivot' == $name) {
- $pivot[$attr] = $val;
- unset($model->$key);
- }
- }
- }
-
- $model->setRelation($this->pivotDataName, $this->newPivot($pivot));
- }
- }
-
/**
* 延迟获取关联数据
* @access public
@@ -158,62 +132,33 @@ class BelongsToMany extends Relation
$closure($this->getClosureType($closure));
}
- $result = $this->relation($subRelation)
+ return $this->relation($subRelation)
->select()
->setParent(clone $this->parent);
-
- $this->hydratePivot($result);
-
- return $result;
- }
-
- /**
- * 重载select方法
- * @access public
- * @param mixed $data
- * @return Collection
- */
- public function select($data = null): Collection
- {
- $this->baseQuery();
- $result = $this->query->select($data);
- $this->hydratePivot($result);
-
- return $result;
- }
-
- /**
- * 重载paginate方法
- * @access public
- * @param int|array $listRows
- * @param int|bool $simple
- * @return Paginator
- */
- public function paginate($listRows = null, $simple = false): Paginator
- {
- $this->baseQuery();
- $result = $this->query->paginate($listRows, $simple);
- $this->hydratePivot($result);
-
- return $result;
}
/**
- * 重载find方法
+ * 组装Pivot模型
* @access public
- * @param mixed $data
- * @return Model
+ * @param Model $result 模型对象
+ * @return array
*/
- public function find($data = null)
+ protected function matchPivot(Model $result): array
{
- $this->baseQuery();
- $result = $this->query->find($data);
-
- if ($result && !$result->isEmpty()) {
- $this->hydratePivot([$result]);
+ $pivot = [];
+ foreach ($result->getData() as $key => $val) {
+ if (strpos($key, '__')) {
+ [$name, $attr] = explode('__', $key, 2);
+
+ if ('pivot' == $name) {
+ $pivot[$attr] = $val;
+ unset($result->$key);
+ }
+ }
}
- return $result;
+ $result->setRelation($this->pivotDataName, $this->newPivot($pivot));
+ return $pivot;
}
/**
@@ -405,24 +350,13 @@ class BelongsToMany extends Relation
// 组装模型数据
$data = [];
foreach ($list as $set) {
- $pivot = [];
- foreach ($set->getData() as $key => $val) {
- if (strpos($key, '__')) {
- [$name, $attr] = explode('__', $key, 2);
- if ('pivot' == $name) {
- $pivot[$attr] = $val;
- unset($set->$key);
- }
- }
- }
- $key = $pivot[$this->localKey];
+ $pivot = $this->matchPivot($set);
+ $key = $pivot[$this->localKey];
if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) {
continue;
}
- $set->setRelation($this->pivotDataName, $this->newPivot($pivot));
-
$data[$key][] = $set;
}
@@ -673,6 +607,10 @@ class BelongsToMany extends Relation
$foreignKey = $this->foreignKey;
$localKey = $this->localKey;
+ $this->query->filter(function ($result, $options) {
+ $this->matchPivot($result);
+ });
+
// 关联查询
if (null === $this->parent->getKey()) {
$condition = ['pivot.' . $localKey, 'exp', new Raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk())];
diff --git a/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php
index d4b7d91..fc62026 100644
--- a/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php
+++ b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php
@@ -258,8 +258,14 @@ class HasManyThrough extends Relation
$closure($this->getClosureType($closure));
}
+ $throughKey = $this->throughKey;
+
+ if ($this->baseQuery) {
+ $throughKey = Str::snake(class_basename($this->model)) . "." . $this->throughKey;
+ }
+
$list = $this->query
- ->where($this->throughKey, 'in', $keys)
+ ->where($throughKey, 'in', $keys)
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
->select();
diff --git a/vendor/topthink/think-orm/src/model/relation/HasOne.php b/vendor/topthink/think-orm/src/model/relation/HasOne.php
index be4927b..269f0d7 100644
--- a/vendor/topthink/think-orm/src/model/relation/HasOne.php
+++ b/vendor/topthink/think-orm/src/model/relation/HasOne.php
@@ -234,13 +234,12 @@ class HasOne extends OneToOne
$relationModel->setParent(clone $result);
$relationModel->exists(true);
}
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
if (!empty($this->bindAttr)) {
// 绑定关联属性
$this->bindAttr($result, $relationModel);
- } else {
- // 设置关联属性
- $result->setRelation($relation, $relationModel);
}
}
}
@@ -276,11 +275,12 @@ class HasOne extends OneToOne
$relationModel->exists(true);
}
+ // 设置关联属性
+ $result->setRelation($relation, $relationModel);
+
if (!empty($this->bindAttr)) {
// 绑定关联属性
$this->bindAttr($result, $relationModel);
- } else {
- $result->setRelation($relation, $relationModel);
}
}