From 820030d987639fbc4574ecd6489fec6fd1d8d8bd Mon Sep 17 00:00:00 2001 From: devil_gong Date: Mon, 11 Nov 2019 14:37:00 +0800 Subject: [PATCH] =?UTF-8?q?thinkphp=E6=A1=86=E6=9E=B6=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- thinkphp/composer.json | 2 +- thinkphp/helper.php | 14 +- thinkphp/library/think/App.php | 12 +- thinkphp/library/think/Collection.php | 2 +- thinkphp/library/think/Console.php | 13 +- thinkphp/library/think/Controller.php | 4 +- thinkphp/library/think/File.php | 2 +- thinkphp/library/think/Log.php | 6 +- thinkphp/library/think/Model.php | 54 +++- thinkphp/library/think/Request.php | 33 ++- thinkphp/library/think/Route.php | 2 +- thinkphp/library/think/Template.php | 2 +- thinkphp/library/think/Url.php | 9 +- thinkphp/library/think/Validate.php | 46 ++- thinkphp/library/think/View.php | 5 +- thinkphp/library/think/cache/Driver.php | 7 +- thinkphp/library/think/cache/driver/File.php | 6 +- .../library/think/cache/driver/Memcached.php | 6 +- thinkphp/library/think/db/Builder.php | 25 +- thinkphp/library/think/db/Connection.php | 56 ++-- thinkphp/library/think/db/Query.php | 102 ++++--- thinkphp/library/think/db/builder/Mysql.php | 16 +- thinkphp/library/think/db/builder/Sqlsrv.php | 2 +- thinkphp/library/think/db/connector/Mysql.php | 22 +- thinkphp/library/think/db/connector/pgsql.sql | 48 +++- .../library/think/exception/DbException.php | 2 +- .../think/exception/ValidateException.php | 2 +- thinkphp/library/think/facade/Config.php | 9 +- thinkphp/library/think/facade/Log.php | 1 + thinkphp/library/think/log/driver/File.php | 36 +-- thinkphp/library/think/model/Collection.php | 6 +- thinkphp/library/think/model/Relation.php | 21 ++ .../library/think/model/concern/Attribute.php | 23 +- .../think/model/concern/Conversion.php | 107 +++---- .../think/model/concern/RelationShip.php | 6 +- .../think/model/concern/SoftDelete.php | 3 +- .../think/model/relation/BelongsTo.php | 19 +- .../think/model/relation/BelongsToMany.php | 10 +- .../library/think/model/relation/HasMany.php | 33 ++- .../think/model/relation/HasManyThrough.php | 269 ++++++++++++++++-- .../library/think/model/relation/HasOne.php | 19 +- .../think/model/relation/MorphMany.php | 9 +- .../library/think/model/relation/MorphOne.php | 5 +- .../library/think/model/relation/MorphTo.php | 1 + .../library/think/model/relation/OneToOne.php | 5 +- thinkphp/library/think/response/Download.php | 10 +- thinkphp/library/think/response/Redirect.php | 5 +- thinkphp/library/think/response/View.php | 27 +- thinkphp/library/think/route/Dispatch.php | 9 +- thinkphp/library/think/route/Rule.php | 19 +- thinkphp/library/think/route/RuleGroup.php | 6 +- thinkphp/library/think/route/RuleItem.php | 14 +- .../library/think/route/dispatch/Module.php | 1 - .../library/think/session/driver/Redis.php | 2 +- thinkphp/tpl/think_exception.tpl | 10 +- 55 files changed, 844 insertions(+), 341 deletions(-) diff --git a/thinkphp/composer.json b/thinkphp/composer.json index cc4fca912..33477b1d7 100755 --- a/thinkphp/composer.json +++ b/thinkphp/composer.json @@ -26,7 +26,7 @@ "require-dev": { "phpunit/phpunit": "^5.0|^6.0", "johnkary/phpunit-speedtrap": "^1.0", - "mikey179/vfsStream": "~1.6", + "mikey179/vfsstream": "~1.6", "phploc/phploc": "2.*", "sebastian/phpcpd": "2.*", "squizlabs/php_codesniffer": "2.*", diff --git a/thinkphp/helper.php b/thinkphp/helper.php index 9c4653350..72b9e9fde 100755 --- a/thinkphp/helper.php +++ b/thinkphp/helper.php @@ -313,9 +313,9 @@ if (!function_exists('download')) { * @param integer $expire 有效期(秒) * @return \think\response\Download */ - function download($filename, $name = '', $content = false, $expire = 360, $openinBrower = false) + function download($filename, $name = '', $content = false, $expire = 360, $openinBrowser = false) { - return Response::create($filename, 'download')->name($name)->isContent($content)->expire($expire)->openinBrower($openinBrower); + return Response::create($filename, 'download')->name($name)->isContent($content)->expire($expire)->openinBrowser($openinBrowser); } } @@ -533,7 +533,7 @@ if (!function_exists('response')) { * @param string $type * @return Response */ - function response($data = [], $code = 200, $header = [], $type = 'html') + function response($data = '', $code = 200, $header = [], $type = 'html') { return Response::create($data, $type, $code, $header); } @@ -686,7 +686,13 @@ if (!function_exists('widget')) { */ function widget($name, $data = []) { - return app()->action($name, $data, 'widget'); + $result = app()->action($name, $data, 'widget'); + + if (is_object($result)) { + $result = $result->getContent(); + } + + return $result; } } diff --git a/thinkphp/library/think/App.php b/thinkphp/library/think/App.php index ac8b0e91b..7766555eb 100755 --- a/thinkphp/library/think/App.php +++ b/thinkphp/library/think/App.php @@ -20,7 +20,7 @@ use think\route\Dispatch; */ class App extends Container { - const VERSION = '5.1.32 LTS'; + const VERSION = '5.1.38 LTS'; /** * 当前模块路径 @@ -179,6 +179,11 @@ class App extends Container $this->instance('app', $this); + // 加载环境变量配置文件 + if (is_file($this->rootPath . '.env')) { + $this->env->load($this->rootPath . '.env'); + } + $this->configExt = $this->env->get('config_ext', '.php'); // 加载惯例配置文件 @@ -196,11 +201,6 @@ class App extends Container 'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR, ]); - // 加载环境变量配置文件 - if (is_file($this->rootPath . '.env')) { - $this->env->load($this->rootPath . '.env'); - } - $this->namespace = $this->env->get('app_namespace', $this->namespace); $this->env->set('app_namespace', $this->namespace); diff --git a/thinkphp/library/think/Collection.php b/thinkphp/library/think/Collection.php index d58c8999b..8251fc8f1 100755 --- a/thinkphp/library/think/Collection.php +++ b/thinkphp/library/think/Collection.php @@ -353,7 +353,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria $result = isset($data[$field]) ? $data[$field] : null; } - switch ($operator) { + switch (strtolower($operator)) { case '===': return $result === $value; case '!==': diff --git a/thinkphp/library/think/Console.php b/thinkphp/library/think/Console.php index d12129072..22f3e2c5f 100755 --- a/thinkphp/library/think/Console.php +++ b/thinkphp/library/think/Console.php @@ -487,12 +487,17 @@ class Console public function getNamespaces() { $namespaces = []; - foreach ($this->commands as $command) { - $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + foreach ($this->commands as $name => $command) { + if (is_string($command)) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($name)); + } else { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); - foreach ($command->getAliases() as $alias) { - $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } } + } return array_values(array_unique(array_filter($namespaces))); diff --git a/thinkphp/library/think/Controller.php b/thinkphp/library/think/Controller.php index d16a1ed50..966eaaa83 100755 --- a/thinkphp/library/think/Controller.php +++ b/thinkphp/library/think/Controller.php @@ -158,7 +158,7 @@ class Controller */ protected function fetch($template = '', $vars = [], $config = []) { - return $this->view->fetch($template, $vars, $config); + return Response::create($template, 'view')->assign($vars)->config($config); } /** @@ -171,7 +171,7 @@ class Controller */ protected function display($content = '', $vars = [], $config = []) { - return $this->view->display($content, $vars, $config); + return Response::create($content, 'view')->assign($vars)->config($config)->isContent(true); } /** diff --git a/thinkphp/library/think/File.php b/thinkphp/library/think/File.php index b29060972..b24b77708 100755 --- a/thinkphp/library/think/File.php +++ b/thinkphp/library/think/File.php @@ -300,7 +300,7 @@ class File extends SplFileObject */ public function checkSize($size) { - if ($this->getSize() > $size) { + if ($this->getSize() > (int) $size) { $this->error = 'filesize not match'; return false; } diff --git a/thinkphp/library/think/Log.php b/thinkphp/library/think/Log.php index 1a3749626..8902e9767 100755 --- a/thinkphp/library/think/Log.php +++ b/thinkphp/library/think/Log.php @@ -127,8 +127,10 @@ class Log implements LoggerInterface } if (PHP_SAPI == 'cli') { - // 命令行日志实时写入 - $this->write($msg, $type, true); + if (empty($this->config['level']) || in_array($type, $this->config['level'])) { + // 命令行日志实时写入 + $this->write($msg, $type, true); + } } else { $this->log[$type][] = $msg; } diff --git a/thinkphp/library/think/Model.php b/thinkphp/library/think/Model.php index 9896f9ff5..93be3dc01 100755 --- a/thinkphp/library/think/Model.php +++ b/thinkphp/library/think/Model.php @@ -18,6 +18,33 @@ use think\db\Query; * Class Model * @package think * @mixin Query + * @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 + * @method Query whereRaw(string $where, array $bind = []) static 表达式查询 + * @method Query whereExp(string $field, string $condition, array $bind = []) static 字段表达式查询 + * @method Query when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询 + * @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 + * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 + * @method Query with(mixed $with) static 关联预载入 + * @method Query count(string $field) static Count统计查询 + * @method Query min(string $field) static Min统计查询 + * @method Query max(string $field) static Max统计查询 + * @method Query sum(string $field) static SUM统计查询 + * @method Query avg(string $field) static Avg统计查询 + * @method Query field(mixed $field, boolean $except = false) static 指定查询字段 + * @method Query fieldRaw(string $field, array $bind = []) static 指定查询字段 + * @method Query union(mixed $union, boolean $all = false) static UNION查询 + * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT + * @method Query order(mixed $field, string $order = null) static 查询ORDER + * @method Query orderRaw(string $field, array $bind = []) static 查询ORDER + * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 + * @method mixed value(string $field) static 获取某个字段的值 + * @method array column(string $field, string $key = '') static 获取某个列的值 + * @method mixed find(mixed $data = null) static 查询单个记录 + * @method mixed select(mixed $data = null) static 查询多个记录 + * @method mixed get(mixed $data = null,mixed $with =[],bool $cache= false) static 查询单个记录 支持关联预载入 + * @method mixed getOrFail(mixed $data = null,mixed $with =[],bool $cache= false) static 查询单个记录 不存在则抛出异常 + * @method mixed findOrEmpty(mixed $data = null,mixed $with =[],bool $cache= false) static 查询单个记录 不存在则返回空模型 + * @method mixed all(mixed $data = null,mixed $with =[],bool $cache= false) static 查询多个记录 支持关联预载入 * @method \think\Model withAttr(array $name,\Closure $closure) 动态定义获取器 */ abstract class Model implements \JsonSerializable, \ArrayAccess @@ -410,6 +437,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $this->exists; } + /** + * 判断模型是否为空 + * @access public + * @return bool + */ + public function isEmpty() + { + return empty($this->data); + } + /** * 保存当前数据对象 * @access public @@ -534,7 +571,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->autoRelationUpdate(); } - return false; + return true; } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { // 自动写入更新时间 $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); @@ -743,12 +780,19 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 删除条件 $pk = $this->getPk(); + $where = []; if (is_string($pk) && isset($this->data[$pk])) { $where[] = [$pk, '=', $this->data[$pk]]; - } elseif (!empty($this->updateWhere)) { - $where = $this->updateWhere; - } else { - $where = null; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($this->data[$field])) { + $where[] = [$field, '=', $this->data[$field]]; + } + } + } + + if (empty($where)) { + $where = empty($this->updateWhere) ? null : $this->updateWhere; } return $where; diff --git a/thinkphp/library/think/Request.php b/thinkphp/library/think/Request.php index 6ba6e7d77..92a401d73 100755 --- a/thinkphp/library/think/Request.php +++ b/thinkphp/library/think/Request.php @@ -682,6 +682,7 @@ class Request // 判断URL里面是否有兼容模式参数 $pathinfo = $_GET[$this->config['var_pathinfo']]; unset($_GET[$this->config['var_pathinfo']]); + unset($this->get[$this->config['var_pathinfo']]); } elseif ($this->isCli()) { // CLI模式下 index.php module/controller/action/params/... $pathinfo = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; @@ -702,6 +703,10 @@ class Request } } + if (!empty($pathinfo)) { + unset($this->get[$pathinfo], $this->request[$pathinfo]); + } + $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/'); } @@ -809,9 +814,14 @@ class Request return $this->server('REQUEST_METHOD') ?: 'GET'; } elseif (!$this->method) { if (isset($_POST[$this->config['var_method']])) { - $this->method = strtoupper($_POST[$this->config['var_method']]); - $method = strtolower($this->method); - $this->{$method} = $_POST; + $method = strtolower($_POST[$this->config['var_method']]); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) { + $this->method = strtoupper($method); + $this->{$method} = $_POST; + } else { + $this->method = 'POST'; + } + unset($_POST[$this->config['var_method']]); } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')); } else { @@ -1034,7 +1044,7 @@ class Request protected function getInputData($content) { - if (false !== strpos($this->contentType(), 'application/json') || 0 === strpos($content, '{"')) { + if (false !== strpos($this->contentType(), 'json')) { return (array) json_decode($content, true); } elseif (strpos($content, '=')) { parse_str($content, $data); @@ -1320,7 +1330,8 @@ class Request * @param array $data 数据源 * @return void */ - public function arrayReset(array &$data) { + public function arrayReset(array &$data) + { foreach ($data as &$value) { if (is_array($value)) { $this->arrayReset($value); @@ -1523,7 +1534,7 @@ class Request */ public function has($name, $type = 'param', $checkEmpty = false) { - if (!in_array($type, ['param', 'get', 'post', 'request', 'put', 'file', 'session', 'cookie', 'env', 'header', 'route'])) { + if (!in_array($type, ['param', 'get', 'post', 'request', 'put', 'patch', 'file', 'session', 'cookie', 'env', 'header', 'route'])) { return false; } @@ -1625,6 +1636,16 @@ class Request return false; } + /** + * 当前是否JSON请求 + * @access public + * @return bool + */ + public function isJson() + { + return false !== strpos($this->type(), 'json'); + } + /** * 当前是否Ajax请求 * @access public diff --git a/thinkphp/library/think/Route.php b/thinkphp/library/think/Route.php index 7bd468ba6..44af9871b 100755 --- a/thinkphp/library/think/Route.php +++ b/thinkphp/library/think/Route.php @@ -408,7 +408,7 @@ class Route $result = $this->bind[$domain]; } elseif (isset($name) && isset($this->bind[$name])) { $result = $this->bind[$name]; - } elseif (isset($this->bind['*'])) { + } elseif (!empty($subDomain) && isset($this->bind['*'])) { $result = $this->bind['*']; } else { $result = null; diff --git a/thinkphp/library/think/Template.php b/thinkphp/library/think/Template.php index dc1acfa51..2855cbcb2 100755 --- a/thinkphp/library/think/Template.php +++ b/thinkphp/library/think/Template.php @@ -1311,7 +1311,7 @@ class Template public function __debugInfo() { $data = get_object_vars($this); - unset($data['app'], $data['storege']); + unset($data['app'], $data['storage']); return $data; } diff --git a/thinkphp/library/think/Url.php b/thinkphp/library/think/Url.php index a339f8de2..b1ab6b411 100755 --- a/thinkphp/library/think/Url.php +++ b/thinkphp/library/think/Url.php @@ -130,7 +130,9 @@ class Url // 匹配路由命名标识 $url = $match[0]; - $domain = $match[1]; + if ($domain) { + $domain = $match[1]; + } if (!is_null($match[2])) { $suffix = $match[2]; @@ -347,6 +349,7 @@ class Url // 匹配路由地址 public function getRuleUrl($rule, &$vars = [], $allowDomain = '') { + $port = $this->app['request']->port(); foreach ($rule as $item) { list($url, $pattern, $domain, $suffix, $method) = $item; @@ -354,8 +357,8 @@ class Url continue; } - if (!in_array($this->app['request']->port(), [80, 443])) { - $domain .= ':' . $this->app['request']->port(); + if ($port && !in_array($port, [80, 443])) { + $domain .= ':' . $port; } if (empty($pattern)) { diff --git a/thinkphp/library/think/Validate.php b/thinkphp/library/think/Validate.php index 54c3a7370..f58d7a88d 100755 --- a/thinkphp/library/think/Validate.php +++ b/thinkphp/library/think/Validate.php @@ -131,7 +131,7 @@ class Validate * 内置正则验证规则 * @var array */ - protected $regex = [ + protected $defaultRegex = [ 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', 'chs' => '/^[\x{4e00}-\x{9fa5}]+$/u', 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', @@ -178,6 +178,12 @@ class Validate */ protected $append = []; + /** + * 验证正则定义 + * @var array + */ + protected $regex = []; + /** * 架构函数 * @access public @@ -513,7 +519,7 @@ class Validate if (isset($this->append[$field])) { // 追加额外的验证规则 - $rules = array_merge($rules, $this->append[$field]); + $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR); } $i = 0; @@ -561,10 +567,11 @@ class Validate } elseif (true !== $result) { // 返回自定义错误信息 if (is_string($result) && false !== strpos($result, ':')) { - $result = str_replace( - [':attribute', ':rule'], - [$title, (string) $rule], - $result); + $result = str_replace(':attribute', $title, $result); + + if (strpos($result, ':rule') && is_scalar($rule)) { + $result = str_replace(':rule', (string) $rule, $result); + } } return $result; @@ -927,8 +934,8 @@ class Validate if (isset($rule[2])) { $imageType = strtolower($rule[2]); - if ('jpeg' == $imageType) { - $imageType = 'jpg'; + if ('jpg' == $imageType) { + $imageType = 'jpeg'; } if (image_type_to_extension($type, false) != $imageType) { @@ -1006,6 +1013,8 @@ class Validate $map[] = [$key, '=', $data[$key]]; } } + } elseif (strpos($key, '=')) { + parse_str($key, $map); } elseif (isset($data[$field])) { $map[] = [$key, '=', $data[$field]]; } else { @@ -1360,6 +1369,8 @@ class Validate { if (isset($this->regex[$rule])) { $rule = $this->regex[$rule]; + } elseif (isset($this->defaultRegex[$rule])) { + $rule = $this->defaultRegex[$rule]; } if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { @@ -1461,13 +1472,17 @@ class Validate $msg = $title . $lang->get('not conform to the rules'); } - if (is_string($msg) && 0 === strpos($msg, '{%')) { + if (!is_string($msg)) { + return $msg; + } + + if (0 === strpos($msg, '{%')) { $msg = $lang->get(substr($msg, 2, -1)); } elseif ($lang->has($msg)) { $msg = $lang->get($msg); } - if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { + if (is_scalar($rule) && false !== strpos($msg, ':')) { // 变量替换 if (is_string($rule) && strpos($rule, ',')) { $array = array_pad(explode(',', $rule), 3, ''); @@ -1475,9 +1490,12 @@ class Validate $array = array_pad([], 3, ''); } $msg = str_replace( - [':attribute', ':rule', ':1', ':2', ':3'], - [$title, (string) $rule, $array[0], $array[1], $array[2]], + [':attribute', ':1', ':2', ':3'], + [$title, $array[0], $array[1], $array[2]], $msg); + if (strpos($msg, ':rule')) { + $msg = str_replace(':rule', (string) $rule, $msg); + } } return $msg; @@ -1496,12 +1514,12 @@ class Validate $scene = $this->currentScene; } + $this->only = $this->append = $this->remove = []; + if (empty($scene)) { return; } - $this->only = $this->append = $this->remove = []; - if (method_exists($this, 'scene' . $scene)) { call_user_func([$this, 'scene' . $scene]); } elseif (isset($this->scene[$scene])) { diff --git a/thinkphp/library/think/View.php b/thinkphp/library/think/View.php index 17860a6ba..284dd41a1 100755 --- a/thinkphp/library/think/View.php +++ b/thinkphp/library/think/View.php @@ -160,7 +160,10 @@ class View */ public function filter($filter) { - $this->filter = $filter; + if ($filter) { + $this->filter = $filter; + } + return $this; } diff --git a/thinkphp/library/think/cache/Driver.php b/thinkphp/library/think/cache/Driver.php index f4c5dcbf7..642168107 100755 --- a/thinkphp/library/think/cache/Driver.php +++ b/thinkphp/library/think/cache/Driver.php @@ -249,7 +249,6 @@ abstract class Driver { if ($this->tag) { $key = $this->getTagkey($this->tag); - $prev = $this->tag; $this->tag = null; if ($this->has($key)) { @@ -266,7 +265,6 @@ abstract class Driver } $this->set($key, $value, 0); - $this->tag = $prev; } } @@ -360,4 +358,9 @@ abstract class Driver { return $this->writeTimes; } + + public function __call($method, $args) + { + return call_user_func_array([$this->handler, $method], $args); + } } diff --git a/thinkphp/library/think/cache/driver/File.php b/thinkphp/library/think/cache/driver/File.php index 93d321f23..60be08db4 100755 --- a/thinkphp/library/think/cache/driver/File.php +++ b/thinkphp/library/think/cache/driver/File.php @@ -278,11 +278,13 @@ class File extends Driver if (is_dir($path)) { $matches = glob($path . DIRECTORY_SEPARATOR . '*.php'); if (is_array($matches)) { - array_map('unlink', $matches); + array_map(function ($v) { + $this->unlink($v); + }, $matches); } rmdir($path); } else { - unlink($path); + $this->unlink($path); } } diff --git a/thinkphp/library/think/cache/driver/Memcached.php b/thinkphp/library/think/cache/driver/Memcached.php index 6af60d19b..4533e78ac 100755 --- a/thinkphp/library/think/cache/driver/Memcached.php +++ b/thinkphp/library/think/cache/driver/Memcached.php @@ -67,7 +67,7 @@ class Memcached extends Driver } $this->handler->addServers($servers); - + $this->handler->setOption(\Memcached::OPT_COMPRESSION, false); if ('' != $this->options['username']) { $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); $this->handler->setSaslAuthData($this->options['username'], $this->options['password']); @@ -232,7 +232,7 @@ class Memcached extends Driver $this->handler->delete($tagName); } - if (!$this->handler->has($tagName)) { + if (!$this->has($tagName)) { $this->handler->set($tagName, ''); } @@ -255,7 +255,7 @@ class Memcached extends Driver if ($this->tag) { $tagName = $this->getTagKey($this->tag); - if ($this->handler->has($tagName)) { + if ($this->has($tagName)) { $this->handler->append($tagName, ',' . $name); } else { $this->handler->set($tagName, $name); diff --git a/thinkphp/library/think/db/Builder.php b/thinkphp/library/think/db/Builder.php index 3dbbf7683..a0faada87 100755 --- a/thinkphp/library/think/db/Builder.php +++ b/thinkphp/library/think/db/Builder.php @@ -313,9 +313,10 @@ abstract class Builder // 使用闭包查询 $newQuery = $query->newQuery()->setConnection($this->connection); $value($newQuery); - $whereClause = $this->buildWhere($query, $newQuery->getOptions('where')); + $whereClause = $this->buildWhere($newQuery, $newQuery->getOptions('where')); if (!empty($whereClause)) { + $query->bind($newQuery->getBind(false)); $str[] = ' ' . $logic . ' ( ' . $whereClause . ' )'; } } elseif (is_array($field)) { @@ -407,7 +408,7 @@ abstract class Builder $jsonType = $query->getJsonFieldType($field); $bindType = $this->connection->getFieldBindType($jsonType); } else { - $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; + $bindType = isset($binds[$field]) && 'LIKE' != $exp ? $binds[$field] : PDO::PARAM_STR; } if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { @@ -450,11 +451,11 @@ abstract class Builder // 模糊匹配 if (is_array($value)) { foreach ($value as $item) { - $name = $query->bind($item, $bindType); + $name = $query->bind($item, PDO::PARAM_STR); $array[] = $key . ' ' . $exp . ' :' . $name; } - $whereStr = '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; + $whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')'; } else { $whereStr = $key . ' ' . $exp . ' ' . $value; } @@ -604,6 +605,10 @@ abstract class Builder $value = $this->parseClosure($query, $value); } + if ('=' == $exp && is_null($value)) { + return $key . ' IS NULL'; + } + return $key . ' ' . $exp . ' ' . $value; } @@ -647,9 +652,10 @@ abstract class Builder // IN 查询 if ($value instanceof \Closure) { $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Expression) { + $value = $value->getValue(); } else { $value = array_unique(is_array($value) ? $value : explode(',', $value)); - $array = []; foreach ($value as $k => $v) { @@ -657,9 +663,12 @@ abstract class Builder $array[] = ':' . $name; } - $zone = implode(',', $array); - - $value = empty($zone) ? "''" : $zone; + if (count($array) == 1) { + return $key . ('IN' == $exp ? ' = ' : ' <> ') . $array[0]; + } else { + $zone = implode(',', $array); + $value = empty($zone) ? "''" : $zone; + } } return $key . ' ' . $exp . ' (' . $value . ')'; diff --git a/thinkphp/library/think/db/Connection.php b/thinkphp/library/think/db/Connection.php index af27fd629..18b4885a6 100755 --- a/thinkphp/library/think/db/Connection.php +++ b/thinkphp/library/think/db/Connection.php @@ -1246,22 +1246,14 @@ abstract class Connection * @access public * @param Query $query 查询对象 * @param string $field 字段名 - * @param bool $default 默认值 + * @param mixed $default 默认值 + * @param bool $one 是否返回一个值 * @return mixed */ - public function value(Query $query, $field, $default = null) + public function value(Query $query, $field, $default = null, $one = true) { $options = $query->getOptions(); - if (empty($options['fetch_sql']) && !empty($options['cache'])) { - $cache = $options['cache']; - $result = $this->getCacheData($query, $cache, null, $key); - - if (false !== $result) { - return $result; - } - } - if (isset($options['field'])) { $query->removeOption('field'); } @@ -1271,7 +1263,19 @@ abstract class Connection } $query->setOption('field', $field); - $query->setOption('limit', 1); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + $cache = $options['cache']; + $result = $this->getCacheData($query, $cache, null, $key); + + if (false !== $result) { + return $result; + } + } + + if ($one) { + $query->setOption('limit', 1); + } // 生成查询SQL $sql = $this->builder->select($query); @@ -1320,7 +1324,7 @@ abstract class Connection $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS tp_' . strtolower($aggregate); - return $this->value($query, $field, 0); + return $this->value($query, $field, 0, false); } /** @@ -1335,16 +1339,6 @@ abstract class Connection { $options = $query->getOptions(); - if (empty($options['fetch_sql']) && !empty($options['cache'])) { - // 判断查询缓存 - $cache = $options['cache']; - $result = $this->getCacheData($query, $cache, null, $guid); - - if (false !== $result) { - return $result; - } - } - if (isset($options['field'])) { $query->removeOption('field'); } @@ -1362,6 +1356,16 @@ abstract class Connection $query->setOption('field', $field); + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + $result = $this->getCacheData($query, $cache, null, $guid); + + if (false !== $result) { + return $result; + } + } + // 生成查询SQL $sql = $this->builder->select($query); @@ -1463,9 +1467,7 @@ abstract class Connection $value = is_array($val) ? $val[0] : $val; $type = is_array($val) ? $val[1] : PDO::PARAM_STR; - if (self::PARAM_FLOAT == $type) { - $value = (float) $value; - } elseif (PDO::PARAM_STR == $type) { + if ((self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) && is_string($value)) { $value = '\'' . addslashes($value) . '\''; } elseif (PDO::PARAM_INT == $type && '' === $value) { $value = 0; @@ -1499,7 +1501,7 @@ abstract class Connection if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { $val[0] = 0; } elseif (self::PARAM_FLOAT == $val[1]) { - $val[0] = (float) $val[0]; + $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0]; $val[1] = PDO::PARAM_STR; } diff --git a/thinkphp/library/think/db/Query.php b/thinkphp/library/think/db/Query.php index 2704c8df7..310ac659f 100755 --- a/thinkphp/library/think/db/Query.php +++ b/thinkphp/library/think/db/Query.php @@ -95,14 +95,14 @@ class Query * @var array */ protected $timeRule = [ - 'today' => ['today', 'tomorrow'], - 'yesterday' => ['yesterday', 'today'], - 'week' => ['this week 00:00:00', 'next week 00:00:00'], - 'last week' => ['last week 00:00:00', 'this week 00:00:00'], - 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'], - 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'], - 'year' => ['this year 1/1', 'next year 1/1'], - 'last year' => ['last year 1/1', 'this year 1/1'], + 'today' => ['today', 'tomorrow -1second'], + 'yesterday' => ['yesterday', 'today -1second'], + 'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'], + 'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'], + 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'], + 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'], + 'year' => ['this year 1/1', 'next year 1/1 -1second'], + 'last year' => ['last year 1/1', 'this year 1/1 -1second'], ]; /** @@ -133,7 +133,27 @@ class Query */ public function newQuery() { - return new static($this->connection); + $query = new static($this->connection); + + if ($this->model) { + $query->model($this->model); + } + + if (isset($this->options['table'])) { + $query->table($this->options['table']); + } else { + $query->name($this->name); + } + + if (isset($this->options['json'])) { + $query->json($this->options['json'], $this->options['json_assoc']); + } + + if (isset($this->options['field_type'])) { + $query->setJsonFieldType($this->options['field_type']); + } + + return $query; } /** @@ -611,9 +631,9 @@ class Query /** * 聚合查询 * @access public - * @param string $aggregate 聚合方法 - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 + * @param string $aggregate 聚合方法 + * @param string|Expression $field 字段名 + * @param bool $force 强制转为数字类型 * @return mixed */ public function aggregate($aggregate, $field, $force = false) @@ -634,7 +654,7 @@ class Query /** * COUNT查询 * @access public - * @param string $field 字段名 + * @param string|Expression $field 字段名 * @return float|string */ public function count($field = '*') @@ -664,7 +684,7 @@ class Query /** * SUM查询 * @access public - * @param string $field 字段名 + * @param string|Expression $field 字段名 * @return float */ public function sum($field) @@ -675,8 +695,8 @@ class Query /** * MIN查询 * @access public - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 + * @param string|Expression $field 字段名 + * @param bool $force 强制转为数字类型 * @return mixed */ public function min($field, $force = true) @@ -687,8 +707,8 @@ class Query /** * MAX查询 * @access public - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 + * @param string|Expression $field 字段名 + * @param bool $force 强制转为数字类型 * @return mixed */ public function max($field, $force = true) @@ -699,7 +719,7 @@ class Query /** * AVG查询 * @access public - * @param string $field 字段名 + * @param string|Expression $field 字段名 * @return float */ public function avg($field) @@ -834,9 +854,10 @@ class Query * @param mixed $join 关联的表名 * @param mixed $condition 条件 * @param string $type JOIN类型 + * @param array $bind 参数绑定 * @return $this */ - public function join($join, $condition = null, $type = 'INNER') + public function join($join, $condition = null, $type = 'INNER', $bind = []) { if (empty($condition)) { // 如果为组数,则循环调用join @@ -847,7 +868,9 @@ class Query } } else { $table = $this->getJoinTable($join); - + if ($bind) { + $this->bindParams($condition, $bind); + } $this->options['join'][] = [$table, strtoupper($type), $condition]; } @@ -859,9 +882,10 @@ class Query * @access public * @param mixed $join 关联的表名 * @param mixed $condition 条件 + * @param array $bind 参数绑定 * @return $this */ - public function leftJoin($join, $condition = null) + public function leftJoin($join, $condition = null, $bind = []) { return $this->join($join, $condition, 'LEFT'); } @@ -871,9 +895,10 @@ class Query * @access public * @param mixed $join 关联的表名 * @param mixed $condition 条件 + * @param array $bind 参数绑定 * @return $this */ - public function rightJoin($join, $condition = null) + public function rightJoin($join, $condition = null, $bind = []) { return $this->join($join, $condition, 'RIGHT'); } @@ -883,9 +908,10 @@ class Query * @access public * @param mixed $join 关联的表名 * @param mixed $condition 条件 + * @param array $bind 参数绑定 * @return $this */ - public function fullJoin($join, $condition = null) + public function fullJoin($join, $condition = null, $bind = []) { return $this->join($join, $condition, 'FULL'); } @@ -943,6 +969,10 @@ class Query */ public function union($union, $all = false) { + if (empty($union)) { + return $this; + } + $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION'; if (is_array($union)) { @@ -1502,6 +1532,7 @@ class Query { if ($field instanceof $this) { $this->options['where'] = $field->getOptions('where'); + $this->bind($field->getBind(false)); return $this; } @@ -1517,10 +1548,10 @@ class Query } if ($field instanceof Expression) { - return $this->whereRaw($field, is_array($op) ? $op : []); + return $this->whereRaw($field, is_array($op) ? $op : [], $logic); } elseif ($strict) { // 使用严格模式查询 - $where = [$field, $op, $condition]; + $where = [$field, $op, $condition, $logic]; } elseif (is_array($field)) { // 解析数组批量查询 return $this->parseArrayWhereItems($field, $logic); @@ -1528,7 +1559,7 @@ class Query $where = $field; } elseif (is_string($field)) { if (preg_match('/[,=\<\'\"\(\s]/', $field)) { - return $this->whereRaw($field, $op); + return $this->whereRaw($field, $op, $logic); } elseif (is_string($op) && strtolower($op) == 'exp') { $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : null; return $this->whereExp($field, $condition, $bind, $logic); @@ -1572,7 +1603,7 @@ class Query // 字段相等查询 $where = [$field, '=', $op]; } - } elseif (in_array(strtoupper($op), ['REGEXP', 'NOT REGEXP', 'EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { + } elseif (in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { $where = [$field, $op, is_string($condition) ? $this->raw($condition) : $condition]; } else { $where = $field ? [$field, $op, $condition, isset($param[2]) ? $param[2] : null] : null; @@ -1645,6 +1676,7 @@ class Query { if (true === $option) { $this->options = []; + $this->bind = []; } elseif (is_string($option) && isset($this->options[$option])) { unset($this->options[$option]); } @@ -2186,12 +2218,12 @@ class Query } /** - * 设置需要追加输出的属性 + * 设置需要附加的输出属性 * @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; @@ -2458,7 +2490,7 @@ class Query if (is_array($value)) { $this->bind = array_merge($this->bind, $value); } else { - $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_'; + $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_'; $this->bind[$name] = [$value, $type]; return $name; @@ -3344,13 +3376,13 @@ class Query // 输出属性控制 if (!empty($options['visible'])) { - $result->visible($options['visible']); + $result->visible($options['visible'], true); } elseif (!empty($options['hidden'])) { - $result->hidden($options['hidden']); + $result->hidden($options['hidden'], true); } if (!empty($options['append'])) { - $result->append($options['append']); + $result->append($options['append'], true); } // 关联查询 diff --git a/thinkphp/library/think/db/builder/Mysql.php b/thinkphp/library/think/db/builder/Mysql.php index 7ec3bf850..af364dff0 100755 --- a/thinkphp/library/think/db/builder/Mysql.php +++ b/thinkphp/library/think/db/builder/Mysql.php @@ -62,7 +62,7 @@ class Mysql extends Builder $bind = $this->connection->getFieldsBind($options['table']); foreach ($dataSet as $k => $data) { - $data = $this->parseData($query, $data, $allowFields, $bind, '_' . $k); + $data = $this->parseData($query, $data, $allowFields, $bind); $values[] = '( ' . implode(',', array_values($data)) . ' )'; @@ -94,13 +94,17 @@ class Mysql extends Builder * @param Query $query 查询对象 * @param string $key * @param string $exp - * @param Expression $value + * @param mixed $value * @param string $field * @return string */ - protected function parseRegexp(Query $query, $key, $exp, Expression $value, $field) + protected function parseRegexp(Query $query, $key, $exp, $value, $field) { - return $key . ' ' . $exp . ' ' . $value->getValue(); + if ($value instanceof Expression) { + $value = $value->getValue(); + } + + return $key . ' ' . $exp . ' ' . $value; } /** @@ -125,7 +129,7 @@ class Mysql extends Builder // JSON字段支持 list($field, $name) = explode('->', $key, 2); - return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$.' . str_replace('->', '.', $name) . '\')'; + return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->', '.', $name) . '\')'; } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { list($table, $key) = explode('.', $key, 2); @@ -145,7 +149,7 @@ class Mysql extends Builder throw new Exception('not support data:' . $key); } - if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) { + if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { $key = '`' . $key . '`'; } diff --git a/thinkphp/library/think/db/builder/Sqlsrv.php b/thinkphp/library/think/db/builder/Sqlsrv.php index e24f7d25a..ef27aafa3 100755 --- a/thinkphp/library/think/db/builder/Sqlsrv.php +++ b/thinkphp/library/think/db/builder/Sqlsrv.php @@ -114,7 +114,7 @@ class Sqlsrv extends Builder throw new Exception('not support data:' . $key); } - if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) { + if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { $key = '[' . $key . ']'; } diff --git a/thinkphp/library/think/db/connector/Mysql.php b/thinkphp/library/think/db/connector/Mysql.php index 93b8a1825..1713b99b9 100755 --- a/thinkphp/library/think/db/connector/Mysql.php +++ b/thinkphp/library/think/db/connector/Mysql.php @@ -136,7 +136,27 @@ class Mysql extends Connection */ protected function getExplain($sql) { - $pdo = $this->linkID->query("EXPLAIN " . $sql); + $pdo = $this->linkID->prepare("EXPLAIN " . $this->queryStr); + + foreach ($this->bind as $key => $val) { + // 占位符 + $param = is_int($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } elseif (self::PARAM_FLOAT == $val[1]) { + $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0]; + $val[1] = PDO::PARAM_STR; + } + + $result = $pdo->bindValue($param, $val[0], $val[1]); + } else { + $result = $pdo->bindValue($param, $val); + } + } + + $pdo->execute(); $result = $pdo->fetch(PDO::FETCH_ASSOC); $result = array_change_key_case($result); diff --git a/thinkphp/library/think/db/connector/pgsql.sql b/thinkphp/library/think/db/connector/pgsql.sql index e1a09a30c..5a4442d02 100755 --- a/thinkphp/library/think/db/connector/pgsql.sql +++ b/thinkphp/library/think/db/connector/pgsql.sql @@ -37,6 +37,10 @@ DECLARE v_sql varchar; v_rec RECORD; v_key varchar; + v_conkey smallint[]; + v_pk varchar[]; + v_len smallint; + v_pos smallint := 1; BEGIN SELECT pg_class.oid INTO v_oid @@ -49,6 +53,31 @@ BEGIN RETURN; END IF; + SELECT + pg_constraint.conkey INTO v_conkey + FROM + pg_constraint + INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid + INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid + INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid + WHERE + pg_class.relname = a_table_name + AND pg_constraint.contype = 'p'; + + v_len := array_length(v_conkey,1) + 1; + WHILE v_pos < v_len LOOP + SELECT + pg_attribute.attname INTO v_key + FROM pg_constraint + INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid + INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid AND pg_attribute.attnum = pg_constraint.conkey [ v_conkey[v_pos] ] + INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid + WHERE pg_class.relname = a_table_name AND pg_constraint.contype = 'p'; + v_pk := array_append(v_pk,v_key); + + v_pos := v_pos + 1; + END LOOP; + v_sql=' SELECT pg_attribute.attname AS fields_name, @@ -83,12 +112,19 @@ BEGIN v_ret.fields_not_null=v_rec.fields_not_null; v_ret.fields_default=v_rec.fields_default; v_ret.fields_comment=v_rec.fields_comment; - SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name; - IF FOUND THEN - v_ret.fields_key_name=v_key; - ELSE - v_ret.fields_key_name=''; - END IF; + + v_ret.fields_key_name=''; + + v_len := array_length(v_pk,1) + 1; + v_pos := 1; + WHILE v_pos < v_len LOOP + IF v_rec.fields_name = v_pk[v_pos] THEN + v_ret.fields_key_name=v_pk[v_pos]; + EXIT; + END IF; + v_pos := v_pos + 1; + END LOOP; + RETURN NEXT v_ret; END LOOP; RETURN ; diff --git a/thinkphp/library/think/exception/DbException.php b/thinkphp/library/think/exception/DbException.php index 0f504257c..6baafb513 100755 --- a/thinkphp/library/think/exception/DbException.php +++ b/thinkphp/library/think/exception/DbException.php @@ -26,7 +26,7 @@ class DbException extends Exception * @param string $sql * @param int $code */ - public function __construct($message, array $config, $sql, $code = 10500) + public function __construct($message, array $config = [], $sql = '', $code = 10500) { $this->message = $message; $this->code = $code; diff --git a/thinkphp/library/think/exception/ValidateException.php b/thinkphp/library/think/exception/ValidateException.php index e3f843745..81ddfe211 100755 --- a/thinkphp/library/think/exception/ValidateException.php +++ b/thinkphp/library/think/exception/ValidateException.php @@ -18,7 +18,7 @@ class ValidateException extends \RuntimeException public function __construct($error, $code = 0) { $this->error = $error; - $this->message = is_array($error) ? implode("\n\r", $error) : $error; + $this->message = is_array($error) ? implode(PHP_EOL, $error) : $error; $this->code = $code; } diff --git a/thinkphp/library/think/facade/Config.php b/thinkphp/library/think/facade/Config.php index 8646d1271..824d2b6af 100755 --- a/thinkphp/library/think/facade/Config.php +++ b/thinkphp/library/think/facade/Config.php @@ -16,11 +16,14 @@ use think\Facade; /** * @see \think\Config * @mixin \think\Config + * @method array load(string $file, string $name = '') static 加载配置文件 * @method bool has(string $name) static 检测配置是否存在 - * @method array pull(string $name) static 获取一级配置 + * @method array pull(string $name) static 获取一级配置参数 * @method mixed get(string $name,mixed $default = null) static 获取配置参数 - * @method mixed set(string $name, mixed $value = null) static 设置配置参数 - * @method array reset(string $prefix ='') static 重置配置参数 + * @method array set(mixed $name, mixed $value = null) static 设置配置参数 + * @method array reset(string $name ='') static 重置配置参数 + * @method void remove(string $name = '') static 移除配置 + * @method void setYaconf(mixed $yaconf) static 设置开启Yaconf 或者指定配置文件名 */ class Config extends Facade { diff --git a/thinkphp/library/think/facade/Log.php b/thinkphp/library/think/facade/Log.php index ddf851ea4..ae627a5cf 100755 --- a/thinkphp/library/think/facade/Log.php +++ b/thinkphp/library/think/facade/Log.php @@ -21,6 +21,7 @@ use think\Facade; * @method \think\Log record(mixed $msg, string $type = 'info', array $context = []) static 记录日志信息 * @method \think\Log clear() static 清空日志信息 * @method \think\Log key(string $key) static 当前日志记录的授权key + * @method \think\Log close() static 关闭本次请求日志写入 * @method bool check(array $config) static 检查日志写入权限 * @method bool save() static 保存调试信息 * @method void write(mixed $msg, string $type = 'info', bool $force = false) static 实时写入日志信息 diff --git a/thinkphp/library/think/log/driver/File.php b/thinkphp/library/think/log/driver/File.php index 10f745d26..3f6522d1a 100755 --- a/thinkphp/library/think/log/driver/File.php +++ b/thinkphp/library/think/log/driver/File.php @@ -19,7 +19,7 @@ use think\App; class File { protected $config = [ - 'time_format' => ' c ', + 'time_format' => 'c', 'single' => false, 'file_size' => 2097152, 'path' => '', @@ -107,7 +107,13 @@ class File $info['timestamp'] = date($this->config['time_format']); foreach ($message as $type => $msg) { - $info[$type] = is_array($msg) ? implode("\r\n", $msg) : $msg; + $msg = is_array($msg) ? implode(PHP_EOL, $msg) : $msg; + if (PHP_SAPI == 'cli') { + $info['msg'] = $msg; + $info['type'] = $type; + } else { + $info[$type] = $msg; + } } if (PHP_SAPI == 'cli') { @@ -140,13 +146,13 @@ class File } } + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + if ($this->config['single']) { $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; - $destination = $this->config['path'] . $name . '.log'; + $destination = $this->config['path'] . $name . $cli . '.log'; } else { - $cli = PHP_SAPI == 'cli' ? '_cli' : ''; - if ($this->config['max_files']) { $filename = date('Ymd') . $cli . '.log'; } else { @@ -172,15 +178,13 @@ class File if ($this->config['single']) { $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; - - $name .= '_' . $type; } elseif ($this->config['max_files']) { - $name = date('Ymd') . '_' . $type . $cli; + $name = date('Ymd'); } else { - $name = date('d') . '_' . $type . $cli; + $name = date('d'); } - return $path . DIRECTORY_SEPARATOR . $name . '.log'; + return $path . DIRECTORY_SEPARATOR . $name . '_' . $type . $cli . '.log'; } /** @@ -208,14 +212,14 @@ class File protected function parseCliLog($info) { if ($this->config['json']) { - $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; } else { $now = $info['timestamp']; unset($info['timestamp']); - $message = implode("\r\n", $info); + $message = implode(PHP_EOL, $info); - $message = "[{$now}]" . $message . "\r\n"; + $message = "[{$now}]" . $message . PHP_EOL; } return $message; @@ -238,13 +242,13 @@ class File if ($this->config['json']) { $info = $requestInfo + $info; - return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; } - array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + array_unshift($info, "---------------------------------------------------------------" . PHP_EOL . "\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); unset($info['timestamp']); - return implode("\r\n", $info) . "\r\n"; + return implode(PHP_EOL, $info) . PHP_EOL; } protected function getDebugLog(&$info, $append, $apart) diff --git a/thinkphp/library/think/model/Collection.php b/thinkphp/library/think/model/Collection.php index 09b61a803..34ec067e7 100755 --- a/thinkphp/library/think/model/Collection.php +++ b/thinkphp/library/think/model/Collection.php @@ -24,8 +24,10 @@ class Collection extends BaseCollection */ public function load($relation) { - $item = current($this->items); - $item->eagerlyResultSet($this->items, $relation); + if (!$this->isEmpty()) { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + } return $this; } diff --git a/thinkphp/library/think/model/Relation.php b/thinkphp/library/think/model/Relation.php index c2b9adc7b..ac6dd4cf9 100755 --- a/thinkphp/library/think/model/Relation.php +++ b/thinkphp/library/think/model/Relation.php @@ -58,6 +58,16 @@ abstract class Relation return $this->query->getModel(); } + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + /** * 设置当前关联为自关联 * @access public @@ -129,6 +139,17 @@ abstract class Relation } } + /** + * 更新数据 + * @access public + * @param array $data 更新数据 + * @return integer|string + */ + public function update(array $data = []) + { + return $this->query->update($data); + } + /** * 删除记录 * @access public diff --git a/thinkphp/library/think/model/concern/Attribute.php b/thinkphp/library/think/model/concern/Attribute.php index b7a80fd3b..66627b383 100755 --- a/thinkphp/library/think/model/concern/Attribute.php +++ b/thinkphp/library/think/model/concern/Attribute.php @@ -12,6 +12,7 @@ namespace think\model\concern; use InvalidArgumentException; +use think\db\Expression; use think\Exception; use think\Loader; use think\model\Relation; @@ -326,9 +327,13 @@ trait Attribute $method = 'set' . Loader::parseName($name, 1) . 'Attr'; if (method_exists($this, $method)) { - $value = $this->$method($value, array_merge($this->data, $data)); + $origin = $this->data; + $value = $this->$method($value, array_merge($this->data, $data)); $this->set[$name] = true; + if (is_null($value) && $origin !== $this->data) { + return; + } } elseif (isset($this->type[$name])) { // 类型转换 $value = $this->writeTransform($value, $this->type[$name]); @@ -370,9 +375,7 @@ trait Attribute switch ($type) { case 'datetime': case 'date': - $format = !empty($param) ? $param : $this->dateFormat; - $format .= strpos($format, 'u') || false !== strpos($format, '\\') ? '' : '.u'; - $value = $this->formatDateTime($format); + $value = $this->formatDateTime('Y-m-d H:i:s.u'); break; case 'timestamp': case 'integer': @@ -385,8 +388,7 @@ trait Attribute 'date', 'timestamp', ])) { - $format = strpos($this->dateFormat, 'u') || false !== strpos($this->dateFormat, '\\') ? '' : '.u'; - $value = $this->formatDateTime($this->dateFormat . $format); + $value = $this->formatDateTime('Y-m-d H:i:s.u'); } else { $value = time(); } @@ -407,6 +409,10 @@ trait Attribute return; } + if ($value instanceof Expression) { + return $value; + } + if (is_array($type)) { list($type, $param) = $type; } elseif (strpos($type, ':')) { @@ -433,9 +439,8 @@ trait Attribute } break; case 'datetime': - $format = !empty($param) ? $param : $this->dateFormat; - $value = is_numeric($value) ? $value : strtotime($value); - $value = $this->formatDateTime($format, $value); + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime('Y-m-d H:i:s.u', $value); break; case 'object': if (is_object($value)) { diff --git a/thinkphp/library/think/model/concern/Conversion.php b/thinkphp/library/think/model/concern/Conversion.php index b88528adb..28a6f9991 100755 --- a/thinkphp/library/think/model/concern/Conversion.php +++ b/thinkphp/library/think/model/concern/Conversion.php @@ -130,34 +130,52 @@ trait Conversion */ public function toArray() { - $item = []; - $visible = []; - $hidden = []; + $item = []; + $hasVisible = false; + + foreach ($this->visible as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + list($relation, $name) = explode('.', $val); + $this->visible[$relation][] = $name; + } else { + $this->visible[$val] = true; + $hasVisible = true; + } + unset($this->visible[$key]); + } + } + + foreach ($this->hidden as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + list($relation, $name) = explode('.', $val); + $this->hidden[$relation][] = $name; + } else { + $this->hidden[$val] = true; + } + unset($this->hidden[$key]); + } + } // 合并关联数据 $data = array_merge($this->data, $this->relation); - // 过滤属性 - if (!empty($this->visible)) { - $array = $this->parseAttr($this->visible, $visible); - $data = array_intersect_key($data, array_flip($array)); - } elseif (!empty($this->hidden)) { - $array = $this->parseAttr($this->hidden, $hidden, false); - $data = array_diff_key($data, array_flip($array)); - } - foreach ($data as $key => $val) { if ($val instanceof Model || $val instanceof ModelCollection) { // 关联模型对象 - if (isset($visible[$key])) { - $val->visible($visible[$key]); - } elseif (isset($hidden[$key])) { - $val->hidden($hidden[$key]); + if (isset($this->visible[$key]) && is_array($this->visible[$key])) { + $val->visible($this->visible[$key]); + } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) { + $val->hidden($this->hidden[$key]); } // 关联模型对象 - $item[$key] = $val->toArray(); - } else { - // 模型属性 + if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) { + $item[$key] = $val->toArray(); + } + } elseif (isset($this->visible[$key])) { + $item[$key] = $this->getAttr($key); + } elseif (!isset($this->hidden[$key]) && !$hasVisible) { $item[$key] = $this->getAttr($key); } } @@ -171,10 +189,12 @@ trait Conversion if (!$relation) { $relation = $this->getAttr($key); - $relation->visible($name); + if ($relation) { + $relation->visible($name); + } } - $item[$key] = $relation->append($name)->toArray(); + $item[$key] = $relation ? $relation->append($name)->toArray() : []; } elseif (strpos($name, '.')) { list($key, $attr) = explode('.', $name); // 追加关联对象属性 @@ -182,15 +202,14 @@ trait Conversion if (!$relation) { $relation = $this->getAttr($key); - $relation->visible([$attr]); + if ($relation) { + $relation->visible([$attr]); + } } - $item[$key] = $relation->append([$attr])->toArray(); + $item[$key] = $relation ? $relation->append([$attr])->toArray() : []; } else { - $value = $this->getAttr($name, $item); - if (false !== $value) { - $item[$name] = $value; - } + $item[$name] = $this->getAttr($name, $item); } } } @@ -251,38 +270,4 @@ trait Conversion return $collection; } - /** - * 解析隐藏及显示属性 - * @access protected - * @param array $attrs 属性 - * @param array $result 结果集 - * @param bool $visible - * @return array - */ - protected function parseAttr($attrs, &$result, $visible = true) - { - $array = []; - - foreach ($attrs as $key => $val) { - if (is_array($val)) { - if ($visible) { - $array[] = $key; - } - - $result[$key] = $val; - } elseif (strpos($val, '.')) { - list($key, $name) = explode('.', $val); - - if ($visible) { - $array[] = $key; - } - - $result[$key][] = $name; - } else { - $array[] = $val; - } - } - - return $array; - } } diff --git a/thinkphp/library/think/model/concern/RelationShip.php b/thinkphp/library/think/model/concern/RelationShip.php index 38ad5d20f..9ca709a5e 100755 --- a/thinkphp/library/think/model/concern/RelationShip.php +++ b/thinkphp/library/think/model/concern/RelationShip.php @@ -204,7 +204,7 @@ trait RelationShip $relationResult = $this->$method(); if (isset($withRelationAttr[$relationName])) { - $relationResult->withAttr($withRelationAttr[$relationName]); + $relationResult->getQuery()->withAttr($withRelationAttr[$relationName]); } $this->relation[$relation] = $relationResult->getRelation($subRelation, $closure); @@ -248,7 +248,7 @@ trait RelationShip $relationResult = $this->$relation(); if (isset($withRelationAttr[$relationName])) { - $relationResult->withAttr($withRelationAttr[$relationName]); + $relationResult->getQuery()->withAttr($withRelationAttr[$relationName]); } $relationResult->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $join); @@ -290,7 +290,7 @@ trait RelationShip $relationResult = $this->$relation(); if (isset($withRelationAttr[$relationName])) { - $relationResult->withAttr($withRelationAttr[$relationName]); + $relationResult->getQuery()->withAttr($withRelationAttr[$relationName]); } $relationResult->eagerlyResult($result, $relation, $subRelation, $closure, $join); diff --git a/thinkphp/library/think/model/concern/SoftDelete.php b/thinkphp/library/think/model/concern/SoftDelete.php index 7dc96e126..679aa34c4 100755 --- a/thinkphp/library/think/model/concern/SoftDelete.php +++ b/thinkphp/library/think/model/concern/SoftDelete.php @@ -235,7 +235,8 @@ trait SoftDelete $field = $this->getDeleteTimeField(true); if ($field) { - $query->useSoftDelete($field, $this->defaultSoftDelete); + $condition = is_null($this->defaultSoftDelete) ? ['null', ''] : ['=', $this->defaultSoftDelete]; + $query->useSoftDelete($field, $condition); } } } diff --git a/thinkphp/library/think/model/relation/BelongsTo.php b/thinkphp/library/think/model/relation/BelongsTo.php index 98d176e8f..056c7d766 100755 --- a/thinkphp/library/think/model/relation/BelongsTo.php +++ b/thinkphp/library/think/model/relation/BelongsTo.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\Loader; use think\Model; @@ -49,7 +50,7 @@ class BelongsTo extends OneToOne */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } @@ -79,7 +80,7 @@ class BelongsTo extends OneToOne */ public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') { - if ($closure) { + if ($closure instanceof Closure) { $return = $closure($this->query); if ($return && is_string($return)) { @@ -111,7 +112,7 @@ class BelongsTo extends OneToOne return 0; } - if ($closure) { + if ($closure instanceof Closure) { $return = $closure($this->query); if ($return && is_string($return)) { @@ -140,13 +141,17 @@ class BelongsTo extends OneToOne $relation = basename(str_replace('\\', '/', $this->model)); $localKey = $this->localKey; $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); return $this->parent->db() ->alias($model) ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { $query->table([$table => $relation]) ->field($relation . '.' . $localKey) - ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey); + ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); }); } @@ -167,12 +172,16 @@ class BelongsTo extends OneToOne $this->getQueryWhere($where, $relation); } - $fields = $this->getRelationQueryFields($fields, $model); + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); return $this->parent->db() ->alias($model) ->field($fields) ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) ->where($where); } diff --git a/thinkphp/library/think/model/relation/BelongsToMany.php b/thinkphp/library/think/model/relation/BelongsToMany.php index 747482b6e..2d64f683c 100755 --- a/thinkphp/library/think/model/relation/BelongsToMany.php +++ b/thinkphp/library/think/model/relation/BelongsToMany.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\Collection; use think\db\Query; use think\Exception; @@ -166,7 +167,7 @@ class BelongsToMany extends Relation */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } @@ -377,7 +378,7 @@ class BelongsToMany extends Relation $pk = $result->$pk; - if ($closure) { + if ($closure instanceof Closure) { $return = $closure($this->query); if ($return && is_string($return)) { @@ -401,7 +402,7 @@ class BelongsToMany extends Relation */ public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') { - if ($closure) { + if ($closure instanceof Closure) { $return = $closure($this->query); if ($return && is_string($return)) { @@ -428,7 +429,7 @@ class BelongsToMany extends Relation protected function eagerlyManyToMany($where, $relation, $subRelation = '', $closure = null) { // 预载入关联查询 支持嵌套预载入 - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } @@ -561,6 +562,7 @@ class BelongsToMany extends Relation $pivot[$this->foreignKey] = $id; $this->pivot->replace() ->exists(false) + ->data([]) ->save($pivot); $result[] = $this->newPivot($pivot, true); } diff --git a/thinkphp/library/think/model/relation/HasMany.php b/thinkphp/library/think/model/relation/HasMany.php index dbb8fa083..e4df5c4b4 100755 --- a/thinkphp/library/think/model/relation/HasMany.php +++ b/thinkphp/library/think/model/relation/HasMany.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\db\Query; use think\Loader; use think\Model; @@ -48,7 +49,7 @@ class HasMany extends Relation */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } @@ -163,7 +164,7 @@ class HasMany extends Relation return 0; } - if ($closure) { + if ($closure instanceof Closure) { $return = $closure($this->query); if ($return && is_string($return)) { $name = $return; @@ -186,7 +187,7 @@ class HasMany extends Relation */ public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') { - if ($closure) { + if ($closure instanceof Closure) { $return = $closure($this->query); if ($return && is_string($return)) { @@ -194,8 +195,8 @@ class HasMany extends Relation } } - return $this->query - ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + return $this->query->alias($aggregate . '_table') + ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) ->fetchSql() ->$aggregate($field); } @@ -216,7 +217,7 @@ class HasMany extends Relation $this->query->removeWhereField($this->foreignKey); // 预载入关联查询 支持嵌套预载入 - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } @@ -266,11 +267,11 @@ class HasMany extends Relation /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 + * @param array|\think\Collection $dataSet 数据集 * @param boolean $replace 是否自动识别更新和写入 * @return array|false */ - public function saveAll(array $dataSet, $replace = true) + public function saveAll($dataSet, $replace = true) { $result = []; @@ -292,14 +293,18 @@ class HasMany extends Relation */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') { - $table = $this->query->getTable(); - $model = basename(str_replace('\\', '/', get_class($this->parent))); - $relation = basename(str_replace('\\', '/', $this->model)); + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $softDelete = $this->query->getOptions('soft_delete'); return $this->parent->db() ->alias($model) ->field($model . '.*') ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) ->group($relation . '.' . $this->foreignKey) ->having('count(' . $id . ')' . $operator . $count); } @@ -321,13 +326,17 @@ class HasMany extends Relation $this->getQueryWhere($where, $relation); } - $fields = $this->getRelationQueryFields($fields, $model); + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); return $this->parent->db() ->alias($model) ->group($model . '.' . $this->localKey) ->field($fields) ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) ->where($where); } diff --git a/thinkphp/library/think/model/relation/HasManyThrough.php b/thinkphp/library/think/model/relation/HasManyThrough.php index 7c7acaa07..6e39f2ad5 100755 --- a/thinkphp/library/think/model/relation/HasManyThrough.php +++ b/thinkphp/library/think/model/relation/HasManyThrough.php @@ -11,8 +11,8 @@ namespace think\model\relation; +use Closure; use think\db\Query; -use think\Exception; use think\Loader; use think\Model; use think\model\Relation; @@ -24,6 +24,12 @@ class HasManyThrough extends Relation // 中间表模型 protected $through; + /** + * 中间主键 + * @var string + */ + protected $throughPk; + /** * 架构函数 * @access public @@ -38,9 +44,10 @@ class HasManyThrough extends Relation { $this->parent = $parent; $this->model = $model; - $this->through = $through; + $this->through = (new $through)->db(); $this->foreignKey = $foreignKey; $this->throughKey = $throughKey; + $this->throughPk = $this->through->getPk(); $this->localKey = $localKey; $this->query = (new $model)->db(); } @@ -54,7 +61,7 @@ class HasManyThrough extends Relation */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } @@ -74,7 +81,28 @@ class HasManyThrough extends Relation */ public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') { - return $this->parent; + $model = App::parseName(App::classBaseName($this->parent)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $relation = (new $this->model)->db(); + $relationTable = $relation->getTable(); + $softDelete = $this->query->getOptions('soft_delete'); + + if ('*' != $id) { + $id = $relationTable . '.' . $relation->getPk(); + } + + return $this->parent->db() + ->alias($model) + ->field($model . '.*') + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $relationTable) { + $query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relationTable . '.' . $this->throughKey) + ->having('count(' . $id . ')' . $operator . $count); } /** @@ -86,45 +114,225 @@ class HasManyThrough extends Relation */ public function hasWhere($where = [], $fields = null) { - throw new Exception('relation not support: hasWhere'); + $model = App::parseName(App::classBaseName($this->parent)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = (new $this->model)->db()->getTable(); + + if (is_array($where)) { + $this->getQueryWhere($where, $modelTable); + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + + return $this->parent->db() + ->alias($model) + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $modelTable) { + $query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($modelTable . '.' . $this->throughKey) + ->where($where) + ->field($fields); } /** - * 预载入关联查询 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param mixed $subRelation 子关联名 + * @param Closure $closure 闭包 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) - {} + public function eagerlyResultSet(array &$resultSet, $relation, $subRelation = '', $closure = null) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$this->foreignKey, 'in', $range], + ], $foreignKey, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = App::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + foreach ($data[$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + // 设置关联属性 + $result->setRelation($attr, $this->resultSetBuild($data[$pk])); + } + } + } /** - * 预载入关联查询 返回模型对象 - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param mixed $subRelation 子关联名 + * @param Closure $closure 闭包 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) - {} + public function eagerlyResult($result, $relation, $subRelation = '', $closure = null) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $pk = $result->$localKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $pk], + ], $foreignKey, $relation, $subRelation, $closure); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + foreach ($data[$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(App::parseName($relation), $this->resultSetBuild($data[$pk])); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param mixed $subRelation 子关联 + * @param Closure $closure + * @return array + */ + protected function eagerlyWhere(array $where, $key, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + $throughList = $this->through->where($where)->select(); + $keys = $throughList->column($this->throughPk, $this->throughPk); + + if ($closure instanceof Closure) { + $closure($this->query); + } + + $list = $this->query->where($this->throughKey, 'in', $keys)->select(); + + // 组装模型数据 + $data = []; + $keys = $throughList->column($this->foreignKey, $this->throughPk); + + foreach ($list as $set) { + $data[$keys[$set->{$this->throughKey}]][] = $set; + } + + return $data; + } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 - * @param string $aggregate 聚合查询方法 - * @param string $field 字段 - * @param string $name 统计字段别名 + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 * @return integer */ - public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') - {} + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + $alias = App::parseName(App::classBaseName($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery($closure = null, $aggregate = 'count', $field = '*', &$name = null) + { + if ($closure instanceof Closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + $alias = App::parseName(App::classBaseName($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } /** * 执行基础查询(仅执行一次) @@ -134,10 +342,9 @@ class HasManyThrough extends Relation protected function baseQuery() { if (empty($this->baseQuery) && $this->parent->getData()) { - $through = $this->through; $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); - $throughTable = $through::getTable(); - $pk = (new $through)->getPk(); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; $throughKey = $this->throughKey; $modelTable = $this->parent->getTable(); $fields = $this->getQueryFields($alias); diff --git a/thinkphp/library/think/model/relation/HasOne.php b/thinkphp/library/think/model/relation/HasOne.php index d8e3ec798..e036bba4e 100755 --- a/thinkphp/library/think/model/relation/HasOne.php +++ b/thinkphp/library/think/model/relation/HasOne.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\db\Query; use think\Loader; use think\Model; @@ -50,7 +51,7 @@ class HasOne extends OneToOne { $localKey = $this->localKey; - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } @@ -79,7 +80,7 @@ class HasOne extends OneToOne */ public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') { - if ($closure) { + if ($closure instanceof Closure) { $return = $closure($this->query); if ($return && is_string($return)) { @@ -111,7 +112,7 @@ class HasOne extends OneToOne return 0; } - if ($closure) { + if ($closure instanceof Closure) { $return = $closure($this->query); if ($return && is_string($return)) { $name = $return; @@ -139,13 +140,17 @@ class HasOne extends OneToOne $relation = basename(str_replace('\\', '/', $this->model)); $localKey = $this->localKey; $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); return $this->parent->db() ->alias($model) ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { $query->table([$table => $relation]) ->field($relation . '.' . $foreignKey) - ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); + ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); }); } @@ -166,12 +171,16 @@ class HasOne extends OneToOne $this->getQueryWhere($where, $relation); } - $fields = $this->getRelationQueryFields($fields, $model); + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); return $this->parent->db() ->alias($model) ->field($fields) ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) ->where($where); } diff --git a/thinkphp/library/think/model/relation/MorphMany.php b/thinkphp/library/think/model/relation/MorphMany.php index a1f54889b..d2af66e9b 100755 --- a/thinkphp/library/think/model/relation/MorphMany.php +++ b/thinkphp/library/think/model/relation/MorphMany.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\db\Query; use think\Exception; use think\Loader; @@ -53,7 +54,7 @@ class MorphMany extends Relation */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } @@ -197,7 +198,7 @@ class MorphMany extends Relation return 0; } - if ($closure) { + if ($closure instanceof Closure) { $return = $closure($this->query); if ($return && is_string($return)) { @@ -224,7 +225,7 @@ class MorphMany extends Relation */ public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') { - if ($closure) { + if ($closure instanceof Closure) { $return = $closure($this->query); if ($return && is_string($return)) { @@ -253,7 +254,7 @@ class MorphMany extends Relation // 预载入关联查询 支持嵌套预载入 $this->query->removeOption('where'); - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } diff --git a/thinkphp/library/think/model/relation/MorphOne.php b/thinkphp/library/think/model/relation/MorphOne.php index 775b2dfd7..6bc205c5b 100755 --- a/thinkphp/library/think/model/relation/MorphOne.php +++ b/thinkphp/library/think/model/relation/MorphOne.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\db\Query; use think\Exception; use think\Loader; @@ -53,7 +54,7 @@ class MorphOne extends Relation */ public function getRelation($subRelation = '', $closure = null) { - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } @@ -186,7 +187,7 @@ class MorphOne extends Relation protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = null) { // 预载入关联查询 支持嵌套预载入 - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); } diff --git a/thinkphp/library/think/model/relation/MorphTo.php b/thinkphp/library/think/model/relation/MorphTo.php index 6da6a0252..17771ce86 100755 --- a/thinkphp/library/think/model/relation/MorphTo.php +++ b/thinkphp/library/think/model/relation/MorphTo.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\Exception; use think\Loader; use think\Model; diff --git a/thinkphp/library/think/model/relation/OneToOne.php b/thinkphp/library/think/model/relation/OneToOne.php index 59fce77fb..a0333c863 100755 --- a/thinkphp/library/think/model/relation/OneToOne.php +++ b/thinkphp/library/think/model/relation/OneToOne.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use Closure; use think\db\Query; use think\Exception; use think\Loader; @@ -87,7 +88,7 @@ abstract class OneToOne extends Relation $joinOn = $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey; } - if ($closure) { + if ($closure instanceof Closure) { // 执行闭包查询 $closure($query); // 使用withField指定获取关联的字段,如 @@ -311,7 +312,7 @@ abstract class OneToOne extends Relation protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = null) { // 预载入关联查询 支持嵌套预载入 - if ($closure) { + if ($closure instanceof Closure) { $closure($this->query); if ($field = $this->query->getOptions('with_field')) { diff --git a/thinkphp/library/think/response/Download.php b/thinkphp/library/think/response/Download.php index d5fcb444c..5595f9ab4 100755 --- a/thinkphp/library/think/response/Download.php +++ b/thinkphp/library/think/response/Download.php @@ -20,7 +20,7 @@ class Download extends Response protected $name; protected $mimeType; protected $isContent = false; - protected $openinBrower = false; + protected $openinBrowser = false; /** * 处理数据 * @access protected @@ -53,7 +53,7 @@ class Download extends Response $this->header['Pragma'] = 'public'; $this->header['Content-Type'] = $mimeType ?: 'application/octet-stream'; $this->header['Cache-control'] = 'max-age=' . $this->expire; - $this->header['Content-Disposition'] = $this->openinBrower ? 'inline' : 'attachment; filename="' . $name . '"'; + $this->header['Content-Disposition'] = $this->openinBrowser ? 'inline' : 'attachment; filename="' . $name . '"'; $this->header['Content-Length'] = $size; $this->header['Content-Transfer-Encoding'] = 'binary'; $this->header['Expires'] = gmdate("D, d M Y H:i:s", time() + $this->expire) . ' GMT'; @@ -138,11 +138,11 @@ class Download extends Response /** * 设置是否在浏览器中显示文件 * @access public - * @param bool $openinBrower 是否在浏览器中显示文件 + * @param bool $openinBrowser 是否在浏览器中显示文件 * @return $this */ - public function openinBrower($openinBrower) { - $this->openinBrower = $openinBrower; + public function openinBrowser($openinBrowser) { + $this->openinBrowser = $openinBrowser; return $this; } } diff --git a/thinkphp/library/think/response/Redirect.php b/thinkphp/library/think/response/Redirect.php index 73729ce88..6b4f118ac 100755 --- a/thinkphp/library/think/response/Redirect.php +++ b/thinkphp/library/think/response/Redirect.php @@ -87,11 +87,12 @@ class Redirect extends Response /** * 记住当前url后跳转 * @access public + * @param string $url 指定记住的url * @return $this */ - public function remember() + public function remember($url = null) { - $this->app['session']->set('redirect_url', $this->app['request']->url()); + $this->app['session']->set('redirect_url', $url ?: $this->app['request']->url()); return $this; } diff --git a/thinkphp/library/think/response/View.php b/thinkphp/library/think/response/View.php index c836ccb5d..3d54c7352 100755 --- a/thinkphp/library/think/response/View.php +++ b/thinkphp/library/think/response/View.php @@ -18,9 +18,16 @@ class View extends Response // 输出参数 protected $options = []; protected $vars = []; + protected $config = []; protected $filter; protected $contentType = 'text/html'; + /** + * 是否内容渲染 + * @var bool + */ + protected $isContent = false; + /** * 处理数据 * @access protected @@ -32,7 +39,19 @@ class View extends Response // 渲染模板输出 return $this->app['view'] ->filter($this->filter) - ->fetch($data, $this->vars); + ->fetch($data, $this->vars, $this->config, $this->isContent); + } + + /** + * 设置是否为内容渲染 + * @access public + * @param bool $content + * @return $this + */ + public function isContent($content = true) + { + $this->isContent = $content; + return $this; } /** @@ -68,6 +87,12 @@ class View extends Response return $this; } + public function config($config) + { + $this->config = $config; + return $this; + } + /** * 视图内容过滤 * @access public diff --git a/thinkphp/library/think/route/Dispatch.php b/thinkphp/library/think/route/Dispatch.php index 93afe73b6..7323c98db 100755 --- a/thinkphp/library/think/route/Dispatch.php +++ b/thinkphp/library/think/route/Dispatch.php @@ -11,9 +11,9 @@ namespace think\route; +use think\App; use think\Container; use think\exception\ValidateException; -use think\App; use think\Request; use think\Response; @@ -181,9 +181,10 @@ abstract class Dispatch $response = Response::create($data, $type); } else { - $data = ob_get_clean(); - $content = false === $data ? '' : $data; - $status = '' === $content && $this->request->isAjax() ? 204 : 200; + $data = ob_get_clean(); + $content = false === $data ? '' : $data; + $status = '' === $content && $this->request->isJson() ? 204 : 200; + $response = Response::create($content, '', $status); } diff --git a/thinkphp/library/think/route/Rule.php b/thinkphp/library/think/route/Rule.php index fdfc29d1d..996305f74 100755 --- a/thinkphp/library/think/route/Rule.php +++ b/thinkphp/library/think/route/Rule.php @@ -642,17 +642,26 @@ abstract class Rule protected function checkCrossDomain($request) { if (!empty($this->option['cross_domain'])) { - $header = [ - 'Access-Control-Allow-Origin' => '*', - 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE', - 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With', + 'Access-Control-Allow-Credentials' => 'true', + 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE', + 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With', ]; if (!empty($this->option['header'])) { $header = array_merge($header, $this->option['header']); } + if (!isset($header['Access-Control-Allow-Origin'])) { + $httpOrigin = $request->header('origin'); + + if ($httpOrigin && strpos(config('cookie.domain'), $httpOrigin)) { + $header['Access-Control-Allow-Origin'] = $httpOrigin; + } else { + $header['Access-Control-Allow-Origin'] = '*'; + } + } + $this->option['header'] = $header; if ($request->method(true) == 'OPTIONS') { @@ -1001,7 +1010,7 @@ abstract class Rule } } - $regex = str_replace($match, $replace, $rule); + $regex = str_replace(array_unique($match), array_unique($replace), $rule); $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex); if (isset($hasSlash)) { diff --git a/thinkphp/library/think/route/RuleGroup.php b/thinkphp/library/think/route/RuleGroup.php index 36be2f444..5781d8cf2 100755 --- a/thinkphp/library/think/route/RuleGroup.php +++ b/thinkphp/library/think/route/RuleGroup.php @@ -118,8 +118,8 @@ class RuleGroup extends Rule */ public function check($request, $url, $completeMatch = false) { + // 跨域OPTIONS请求 if ($dispatch = $this->checkCrossDomain($request)) { - // 跨域OPTIONS请求 return $dispatch; } @@ -151,10 +151,6 @@ class RuleGroup extends Rule $method = strtolower($request->method()); $rules = $this->getMethodRules($method); - if (count($rules) == 0) { - return false; - } - if ($this->parent) { // 合并分组参数 $this->mergeGroupOptions(); diff --git a/thinkphp/library/think/route/RuleItem.php b/thinkphp/library/think/route/RuleItem.php index e4bddd90c..a05d2deb0 100755 --- a/thinkphp/library/think/route/RuleItem.php +++ b/thinkphp/library/think/route/RuleItem.php @@ -149,11 +149,6 @@ class RuleItem extends Rule */ public function checkRule($request, $url, $match = null, $completeMatch = false) { - if ($dispatch = $this->checkCrossDomain($request)) { - // 允许跨域 - return $dispatch; - } - // 检查参数有效性 if (!$this->checkOption($this->option, $request)) { return false; @@ -169,6 +164,15 @@ class RuleItem extends Rule } if (false !== $match) { + if (!empty($option['cross_domain'])) { + if ($dispatch = $this->checkCrossDomain($request)) { + // 允许跨域 + return $dispatch; + } + + $option['header'] = $this->option['header']; + } + // 检查前置行为 if (isset($option['before']) && false === $this->checkBefore($option['before'])) { return false; diff --git a/thinkphp/library/think/route/dispatch/Module.php b/thinkphp/library/think/route/dispatch/Module.php index e8842cd3a..40bd77596 100755 --- a/thinkphp/library/think/route/dispatch/Module.php +++ b/thinkphp/library/think/route/dispatch/Module.php @@ -12,7 +12,6 @@ namespace think\route\dispatch; use ReflectionMethod; -use think\Controller; use think\exception\ClassNotFoundException; use think\exception\HttpException; use think\Loader; diff --git a/thinkphp/library/think/session/driver/Redis.php b/thinkphp/library/think/session/driver/Redis.php index 646700bd6..5a0e7bc73 100755 --- a/thinkphp/library/think/session/driver/Redis.php +++ b/thinkphp/library/think/session/driver/Redis.php @@ -124,7 +124,7 @@ class Redis implements SessionHandlerInterface */ public function destroy($sessID) { - return $this->handler->delete($this->config['session_name'] . $sessID) > 0; + return $this->handler->del($this->config['session_name'] . $sessID) > 0; } /** diff --git a/thinkphp/tpl/think_exception.tpl b/thinkphp/tpl/think_exception.tpl index 6a700abb1..19ecbdc1b 100755 --- a/thinkphp/tpl/think_exception.tpl +++ b/thinkphp/tpl/think_exception.tpl @@ -280,9 +280,6 @@ pre.prettyprint .atv { color: #080 } /* a markup attribute value */ pre.prettyprint .dec, pre.prettyprint .var { color: #606 } /* a declaration; a variable name */ pre.prettyprint .fun { color: red } /* a function name */ - - /* bbs */ - .bbs-ask { margin-left: 15px; } @@ -413,10 +410,9 @@