diff --git a/thinkphp/base.php b/thinkphp/base.php index 67e42559..86c585bc 100644 --- a/thinkphp/base.php +++ b/thinkphp/base.php @@ -9,7 +9,7 @@ // | Author: liu21st // +---------------------------------------------------------------------- -define('THINK_VERSION', '5.0.21'); +define('THINK_VERSION', '5.0.23'); define('THINK_START_TIME', microtime(true)); define('THINK_START_MEM', memory_get_usage()); define('EXT', '.php'); diff --git a/thinkphp/library/think/App.php b/thinkphp/library/think/App.php index 37c50aa9..f572b907 100644 --- a/thinkphp/library/think/App.php +++ b/thinkphp/library/think/App.php @@ -551,12 +551,13 @@ class App // 获取控制器名 $controller = strip_tags($result[1] ?: $config['default_controller']); - $controller = $convert ? strtolower($controller) : $controller; if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) { throw new HttpException(404, 'controller not exists:' . $controller); } + $controller = $convert ? strtolower($controller) : $controller; + // 获取操作名 $actionName = strip_tags($result[2] ?: $config['default_action']); if (!empty($config['action_convert'])) { diff --git a/thinkphp/library/think/Log.php b/thinkphp/library/think/Log.php index bf6c04f3..c064306c 100644 --- a/thinkphp/library/think/Log.php +++ b/thinkphp/library/think/Log.php @@ -176,7 +176,7 @@ class Log } } - if ($result = self::$driver->save($log)) { + if ($result = self::$driver->save($log, true)) { self::$log = []; } @@ -211,7 +211,7 @@ class Log is_null(self::$driver) && self::init(Config::get('log')); // 写入日志 - if ($result = self::$driver->save($log)) { + if ($result = self::$driver->save($log, false)) { self::$log = []; } diff --git a/thinkphp/library/think/Model.php b/thinkphp/library/think/Model.php index 386660c4..2190e341 100644 --- a/thinkphp/library/think/Model.php +++ b/thinkphp/library/think/Model.php @@ -94,6 +94,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected $type = []; // 是否为更新数据 protected $isUpdate = false; + // 是否使用Replace + protected $replace = false; // 是否强制更新所有数据 protected $force = false; // 更新条件 @@ -1013,6 +1015,18 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return false; } + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace($replace = true) + { + $this->replace = $replace; + return $this; + } + /** * 保存当前数据对象 * @access public @@ -1028,19 +1042,19 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $data = []; } - if (!empty($data)) { - // 数据自动验证 - if (!$this->validateData($data)) { - return false; - } - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - if (!empty($where)) { - $this->isUpdate = true; - $this->updateWhere = $where; - } + // 数据自动验证 + if (!$this->validateData($data)) { + return false; + } + + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + + if (!empty($where)) { + $this->isUpdate = true; + $this->updateWhere = $where; } // 自动关联写入 @@ -1163,9 +1177,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 检测字段 $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert)); if (!empty($allowFields)) { - $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, false, false, $sequence); + $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, $this->replace, false, $sequence); } else { - $result = $this->getQuery()->insert($this->data, false, false, $sequence); + $result = $this->getQuery()->insert($this->data, $this->replace, false, $sequence); } // 获取自动增长主键 diff --git a/thinkphp/library/think/Request.php b/thinkphp/library/think/Request.php index 02e05735..f6ac1ae6 100644 --- a/thinkphp/library/think/Request.php +++ b/thinkphp/library/think/Request.php @@ -160,8 +160,8 @@ class Request /** * Hook 方法注入 * @access public - * @param string|array $method 方法名 - * @param mixed $callback callable + * @param string|array $method 方法名 + * @param mixed $callback callable * @return void */ public static function hook($method, $callback = null) @@ -202,13 +202,13 @@ class Request /** * 创建一个URL请求 * @access public - * @param string $uri URL地址 - * @param string $method 请求类型 - * @param array $params 请求参数 - * @param array $cookie - * @param array $files - * @param array $server - * @param string $content + * @param string $uri URL地址 + * @param string $method 请求类型 + * @param array $params 请求参数 + * @param array $cookie + * @param array $files + * @param array $server + * @param string $content * @return \think\Request */ public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) @@ -415,7 +415,7 @@ class Request foreach (Config::get('pathinfo_fetch') as $type) { if (!empty($_SERVER[$type])) { $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? - substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; + substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; break; } } @@ -496,8 +496,8 @@ class Request /** * 设置资源类型 * @access public - * @param string|array $type 资源类型名 - * @param string $val 资源类型 + * @param string|array $type 资源类型名 + * @param string $val 资源类型 * @return void */ public function mimeType($type, $val = '') @@ -512,7 +512,7 @@ class Request /** * 当前的请求类型 * @access public - * @param bool $method true 获取原始请求类型 + * @param bool $method true 获取原始请求类型 * @return string */ public function method($method = false) @@ -626,9 +626,9 @@ class Request /** * 获取当前请求的参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function param($name = '', $default = null, $filter = '') @@ -664,15 +664,16 @@ class Request /** * 设置获取路由参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function route($name = '', $default = null, $filter = '') { if (is_array($name)) { - $this->param = []; + $this->param = []; + $this->mergeParam = false; return $this->route = array_merge($this->route, $name); } return $this->input($this->route, $name, $default, $filter); @@ -681,9 +682,9 @@ class Request /** * 设置获取GET参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function get($name = '', $default = null, $filter = '') @@ -693,6 +694,7 @@ class Request } if (is_array($name)) { $this->param = []; + $this->mergeParam = false; return $this->get = array_merge($this->get, $name); } return $this->input($this->get, $name, $default, $filter); @@ -701,9 +703,9 @@ class Request /** * 设置获取POST参数 * @access public - * @param string $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function post($name = '', $default = null, $filter = '') @@ -717,7 +719,8 @@ class Request } } if (is_array($name)) { - $this->param = []; + $this->param = []; + $this->mergeParam = false; return $this->post = array_merge($this->post, $name); } return $this->input($this->post, $name, $default, $filter); @@ -726,9 +729,9 @@ class Request /** * 设置获取PUT参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function put($name = '', $default = null, $filter = '') @@ -743,6 +746,7 @@ class Request } if (is_array($name)) { $this->param = []; + $this->mergeParam = false; return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); } @@ -752,9 +756,9 @@ class Request /** * 设置获取DELETE参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function delete($name = '', $default = null, $filter = '') @@ -765,9 +769,9 @@ class Request /** * 设置获取PATCH参数 * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function patch($name = '', $default = null, $filter = '') @@ -777,9 +781,9 @@ class Request /** * 获取request变量 - * @param string $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param string $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function request($name = '', $default = null, $filter = '') @@ -788,7 +792,8 @@ class Request $this->request = $_REQUEST; } if (is_array($name)) { - $this->param = []; + $this->param = []; + $this->mergeParam = false; return $this->request = array_merge($this->request, $name); } return $this->input($this->request, $name, $default, $filter); @@ -797,9 +802,9 @@ class Request /** * 获取session数据 * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function session($name = '', $default = null, $filter = '') @@ -816,9 +821,9 @@ class Request /** * 获取cookie参数 * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function cookie($name = '', $default = null, $filter = '') @@ -849,9 +854,9 @@ class Request /** * 获取server参数 * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function server($name = '', $default = null, $filter = '') @@ -927,9 +932,9 @@ class Request /** * 获取环境变量 - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 * @return mixed */ public function env($name = '', $default = null, $filter = '') @@ -946,8 +951,8 @@ class Request /** * 设置或者获取当前的Header * @access public - * @param string|array $name header名称 - * @param string $default 默认值 + * @param string|array $name header名称 + * @param string $default 默认值 * @return string */ public function header($name = '', $default = null) @@ -985,10 +990,10 @@ class Request /** * 获取变量 支持过滤和默认值 - * @param array $data 数据源 - * @param string|false $name 字段名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤函数 + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 * @return mixed */ public function input($data = [], $name = '', $default = null, $filter = '') @@ -1069,9 +1074,9 @@ class Request /** * 递归过滤给定的值 - * @param mixed $value 键值 - * @param mixed $key 键名 - * @param array $filters 过滤方法+默认值 + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 * @return mixed */ private function filterValue(&$value, $key, $filters) @@ -1156,9 +1161,9 @@ class Request /** * 是否存在某个请求参数 * @access public - * @param string $name 变量名 - * @param string $type 变量类型 - * @param bool $checkEmpty 是否检测空值 + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 * @return mixed */ public function has($name, $type = 'param', $checkEmpty = false) @@ -1182,8 +1187,8 @@ class Request /** * 获取指定的参数 * @access public - * @param string|array $name 变量名 - * @param string $type 变量类型 + * @param string|array $name 变量名 + * @param string $type 变量类型 * @return mixed */ public function only($name, $type = 'param') @@ -1204,8 +1209,8 @@ class Request /** * 排除指定参数获取 * @access public - * @param string|array $name 变量名 - * @param string $type 变量类型 + * @param string|array $name 变量名 + * @param string $type 变量类型 * @return mixed */ public function except($name, $type = 'param') @@ -1247,7 +1252,7 @@ class Request /** * 当前是否Ajax请求 * @access public - * @param bool $ajax true 获取原始ajax请求 + * @param bool $ajax true 获取原始ajax请求 * @return bool */ public function isAjax($ajax = false) @@ -1257,7 +1262,7 @@ class Request if (true === $ajax) { return $result; } else { - $result = $this->param(Config::get('var_ajax')) ? true : $result; + $result = $this->param(Config::get('var_ajax')) ? true : $result; $this->mergeParam = false; return $result; } @@ -1266,7 +1271,7 @@ class Request /** * 当前是否Pjax请求 * @access public - * @param bool $pjax true 获取原始pjax请求 + * @param bool $pjax true 获取原始pjax请求 * @return bool */ public function isPjax($pjax = false) @@ -1275,7 +1280,7 @@ class Request if (true === $pjax) { return $result; } else { - $result = $this->param(Config::get('var_pjax')) ? true : $result; + $result = $this->param(Config::get('var_pjax')) ? true : $result; $this->mergeParam = false; return $result; } @@ -1283,13 +1288,13 @@ class Request /** * 获取客户端IP地址 - * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 - * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) * @return mixed */ public function ip($type = 0, $adv = true) { - $type = $type ? 1 : 0; + $type = $type ? 1 : 0; static $ip = null; if (null !== $ip) { return $ip[$type]; @@ -1364,7 +1369,7 @@ class Request /** * 当前请求的host * @access public - * @param bool $strict true 仅仅获取HOST + * @param bool $strict true 仅仅获取HOST * @return string */ public function host($strict = false) @@ -1445,7 +1450,7 @@ class Request /** * 设置或者获取当前请求的调度信息 * @access public - * @param array $dispatch 调度信息 + * @param array $dispatch 调度信息 * @return array */ public function dispatch($dispatch = null) @@ -1565,7 +1570,7 @@ class Request /** * 设置当前地址的请求缓存 * @access public - * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id * @param mixed $expire 缓存有效期 * @param array $except 缓存排除 * @param string $tag 缓存标签 @@ -1628,7 +1633,7 @@ class Request throw new \think\exception\HttpResponseException($response); } elseif (Cache::has($key)) { list($content, $header) = Cache::get($key); - $response = Response::create($content)->header($header); + $response = Response::create($content)->header($header); throw new \think\exception\HttpResponseException($response); } else { $this->cache = [$key, $expire, $tag]; @@ -1650,7 +1655,7 @@ class Request * 设置当前请求绑定的对象实例 * @access public * @param string|array $name 绑定的对象标识 - * @param mixed $obj 绑定的对象实例 + * @param mixed $obj 绑定的对象实例 * @return mixed */ public function bind($name, $obj = null) diff --git a/thinkphp/library/think/Validate.php b/thinkphp/library/think/Validate.php index 6226892c..df6c6e75 100644 --- a/thinkphp/library/think/Validate.php +++ b/thinkphp/library/think/Validate.php @@ -67,6 +67,8 @@ class Validate 'min' => 'min size of :attribute must be :rule', 'after' => ':attribute cannot be less than :rule', 'before' => ':attribute cannot exceed :rule', + 'afterWith' => ':attribute cannot be less than :rule', + 'beforeWith' => ':attribute cannot exceed :rule', 'expire' => ':attribute not within :rule', 'allowIp' => 'access IP is not allowed', 'denyIp' => 'access IP denied', @@ -1113,9 +1115,10 @@ class Validate * @access protected * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function after($value, $rule) + protected function after($value, $rule, $data) { return strtotime($value) >= strtotime($rule); } @@ -1125,13 +1128,42 @@ class Validate * @access protected * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function before($value, $rule) + protected function before($value, $rule, $data) { return strtotime($value) <= strtotime($rule); } + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function afterWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function beforeWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) <= strtotime($rule); + } + /** * 验证有效期 * @access protected diff --git a/thinkphp/library/think/db/Builder.php b/thinkphp/library/think/db/Builder.php index 1a865a9d..58b45aa8 100644 --- a/thinkphp/library/think/db/Builder.php +++ b/thinkphp/library/think/db/Builder.php @@ -98,6 +98,10 @@ abstract class Builder $result = []; foreach ($data as $key => $val) { + if ('*' != $options['field'] && !in_array($key, $fields, true)) { + continue; + } + $item = $this->parseKey($key, $options, true); if ($val instanceof Expression) { $result[$item] = $val->getValue(); @@ -115,18 +119,10 @@ abstract class Builder } elseif (is_array($val) && !empty($val)) { switch (strtolower($val[0])) { case 'inc': -// $result[$item] = $item . '+' . floatval($val[1]); - if ($key == $val[1]) { - $result[$item] = $this->parseKey($val[1]) . '+' . floatval($val[2]); - } - + $result[$item] = $item . '+' . floatval($val[1]); break; case 'dec': -// $result[$item] = $item . '-' . floatval($val[1]); - if ($key == $val[1]) { - $result[$item] = $this->parseKey($val[1]) . '-' . floatval($val[2]); - } - + $result[$item] = $item . '-' . floatval($val[1]); break; case 'exp': throw new Exception('not support data:[' . $val[0] . ']'); diff --git a/thinkphp/library/think/db/Connection.php b/thinkphp/library/think/db/Connection.php index 7720282d..578cc8f9 100644 --- a/thinkphp/library/think/db/Connection.php +++ b/thinkphp/library/think/db/Connection.php @@ -361,14 +361,9 @@ abstract class Connection // 调试开始 $this->debug(true); - // 释放前次的查询结果 - if (!empty($this->PDOStatement)) { - $this->free(); - } // 预处理 - if (empty($this->PDOStatement)) { - $this->PDOStatement = $this->linkID->prepare($sql); - } + $this->PDOStatement = $this->linkID->prepare($sql); + // 是否为存储过程调用 $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); // 参数绑定 @@ -429,14 +424,9 @@ abstract class Connection // 调试开始 $this->debug(true); - //释放前次的查询结果 - if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) { - $this->free(); - } // 预处理 - if (empty($this->PDOStatement)) { - $this->PDOStatement = $this->linkID->prepare($sql); - } + $this->PDOStatement = $this->linkID->prepare($sql); + // 是否为存储过程调用 $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); // 参数绑定 @@ -659,18 +649,15 @@ abstract class Connection ); } - } catch (\PDOException $e) { - if ($this->isBreak($e)) { - return $this->close()->startTrans(); - } - throw $e; } catch (\Exception $e) { if ($this->isBreak($e)) { + --$this->transTimes; return $this->close()->startTrans(); } throw $e; } catch (\Error $e) { if ($this->isBreak($e)) { + --$this->transTimes; return $this->close()->startTrans(); } throw $e; @@ -804,6 +791,8 @@ abstract class Connection $this->linkWrite = null; $this->linkRead = null; $this->links = []; + // 释放查询 + $this->free(); return $this; } diff --git a/thinkphp/library/think/db/Query.php b/thinkphp/library/think/db/Query.php index b63c38e5..75086bc6 100644 --- a/thinkphp/library/think/db/Query.php +++ b/thinkphp/library/think/db/Query.php @@ -92,6 +92,13 @@ class Query $name = Loader::parseName(substr($method, 10)); $where[$name] = $args[0]; return $this->where($where)->value($args[1]); + } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $this); + + call_user_func_array([$this->model, $method], $args); + return $this; } else { throw new Exception('method not exist:' . __CLASS__ . '->' . $method); } @@ -436,9 +443,10 @@ class Query // 返回SQL语句 return $pdo; } + $result = $pdo->fetchColumn(); if ($force) { - $result += 0; + $result = (float) $result; } if (isset($cache) && false !== $result) { @@ -531,13 +539,43 @@ class Query public function count($field = '*') { if (isset($this->options['group'])) { + if (!preg_match('/^[\w\.\*]+$/', $field)) { + throw new Exception('not support data:' . $field); + } // 支持GROUP $options = $this->getOptions(); $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); - return $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + + $count = $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + } else { + $count = $this->aggregate('COUNT', $field, true); } - return $this->value('COUNT(' . $field . ') AS tp_count', 0, true); + return is_string($count) ? $count : (int) $count; + + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate($aggregate, $field, $force = false) + { + if (0 === stripos($field, 'DISTINCT ')) { + list($distinct, $field) = explode(' ', $field); + } + + if (!preg_match('/^[\w\.\+\-\*]+$/', $field)) { + throw new Exception('not support data:' . $field); + } + + $result = $this->value($aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $field . ') AS tp_' . strtolower($aggregate), 0, $force); + + return $result; } /** @@ -548,7 +586,7 @@ class Query */ public function sum($field) { - return $this->value('SUM(' . $field . ') AS tp_sum', 0, true); + return $this->aggregate('SUM', $field, true); } /** @@ -560,7 +598,7 @@ class Query */ public function min($field, $force = true) { - return $this->value('MIN(' . $field . ') AS tp_min', 0, $force); + return $this->aggregate('MIN', $field, $force); } /** @@ -572,7 +610,7 @@ class Query */ public function max($field, $force = true) { - return $this->value('MAX(' . $field . ') AS tp_max', 0, $force); + return $this->aggregate('MAX', $field, $force); } /** @@ -583,7 +621,7 @@ class Query */ public function avg($field) { - return $this->value('AVG(' . $field . ') AS tp_avg', 0, true); + return $this->aggregate('AVG', $field, true); } /** @@ -2092,14 +2130,23 @@ class Query $this->field('*'); } foreach ($relations as $key => $relation) { - $closure = false; + $closure = $name = null; if ($relation instanceof \Closure) { $closure = $relation; $relation = $key; + } elseif (!is_int($key)) { + $name = $relation; + $relation = $key; } $relation = Loader::parseName($relation, 1, false); - $count = '(' . $this->model->$relation()->getRelationCountQuery($closure) . ')'; - $this->field([$count => Loader::parseName($relation) . '_count']); + + $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $name) . ')'; + + if (empty($name)) { + $name = Loader::parseName($relation) . '_count'; + } + + $this->field([$count => $name]); } } return $this; diff --git a/thinkphp/library/think/db/builder/Mysql.php b/thinkphp/library/think/db/builder/Mysql.php index 8eee746f..be2af714 100644 --- a/thinkphp/library/think/db/builder/Mysql.php +++ b/thinkphp/library/think/db/builder/Mysql.php @@ -109,6 +109,9 @@ class Mysql extends Builder } } + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } if ('*' != $key && ($strict || !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 f79ae030..dc425d9e 100644 --- a/thinkphp/library/think/db/builder/Sqlsrv.php +++ b/thinkphp/library/think/db/builder/Sqlsrv.php @@ -94,6 +94,10 @@ class Sqlsrv extends Builder $table = $options['alias'][$table]; } } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) { $key = '[' . $key . ']'; } diff --git a/thinkphp/library/think/db/connector/Sqlsrv.php b/thinkphp/library/think/db/connector/Sqlsrv.php index 08ad45e2..35c66005 100644 --- a/thinkphp/library/think/db/connector/Sqlsrv.php +++ b/thinkphp/library/think/db/connector/Sqlsrv.php @@ -50,7 +50,10 @@ class Sqlsrv extends Connection public function getFields($tableName) { list($tableName) = explode(' ', $tableName); - $sql = "SELECT column_name, data_type, column_default, is_nullable + $tableNames = explode('.', $tableName); + $tableName = isset($tableNames[1]) ? $tableNames[1] : $tableNames[0]; + + $sql = "SELECT column_name, data_type, column_default, is_nullable FROM information_schema.tables AS t JOIN information_schema.columns AS c ON t.table_catalog = c.table_catalog diff --git a/thinkphp/library/think/log/driver/File.php b/thinkphp/library/think/log/driver/File.php index fa84ac1a..f2296cfa 100644 --- a/thinkphp/library/think/log/driver/File.php +++ b/thinkphp/library/think/log/driver/File.php @@ -26,10 +26,9 @@ class File 'path' => LOG_PATH, 'apart_level' => [], 'max_files' => 0, + 'json' => false, ]; - protected $writed = []; - // 实例化并传入参数 public function __construct($config = []) { @@ -41,106 +40,231 @@ class File /** * 日志写入接口 * @access public - * @param array $log 日志信息 + * @param array $log 日志信息 + * @param bool $append 是否追加请求信息 * @return bool */ - public function save(array $log = []) + public function save(array $log = [], $append = false) + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + foreach ($log as $type => $val) { + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg; + } + + if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + + $this->write($info[$type], $filename, true, $append); + unset($info[$type]); + } + } + + if ($info) { + return $this->write($info, $destination, false, $append); + } + + return true; + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile() { if ($this->config['single']) { - $destination = $this->config['path'] . 'single.log'; + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $destination = $this->config['path'] . $name . '.log'; } else { - $cli = IS_CLI ? '_cli' : ''; + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; if ($this->config['max_files']) { $filename = date('Ymd') . $cli . '.log'; $files = glob($this->config['path'] . '*.log'); - if (count($files) > $this->config['max_files']) { - unlink($files[0]); + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { } } else { - $filename = date('Ym') . '/' . date('d') . $cli . '.log'; + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; } $destination = $this->config['path'] . $filename; } - $path = dirname($destination); - !is_dir($path) && mkdir($path, 0755, true); - - $info = ''; - foreach ($log as $type => $val) { - $level = ''; - foreach ($val as $msg) { - if (!is_string($msg)) { - $msg = var_export($msg, true); - } - $level .= '[ ' . $type . ' ] ' . $msg . "\r\n"; - } - if (in_array($type, $this->config['apart_level'])) { - // 独立记录的日志级别 - if ($this->config['single']) { - $filename = $path . DS . $type . '.log'; - } elseif ($this->config['max_files']) { - $filename = $path . DS . date('Ymd') . '_' . $type . $cli . '.log'; - } else { - $filename = $path . DS . date('d') . '_' . $type . $cli . '.log'; - } - $this->write($level, $filename, true); - } else { - $info .= $level; - } - } - if ($info) { - return $this->write($info, $destination); - } - return true; + return $destination; } - protected function write($message, $destination, $apart = false) + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile($path, $type) { - //检测日志文件大小,超过配置大小则备份日志文件重新生成 - if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { - try { - rename($destination, dirname($destination) . DS . time() . '-' . basename($destination)); - } catch (\Exception $e) { - } - $this->writed[$destination] = false; + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + 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; + } else { + $name = date('d') . '_' . $type . $cli; } - if (empty($this->writed[$destination]) && !IS_CLI) { - if (App::$debug && !$apart) { - // 获取基本信息 - if (isset($_SERVER['HTTP_HOST'])) { - $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $current_uri = "cmd:" . implode(' ', $_SERVER['argv']); - } + return $path . DIRECTORY_SEPARATOR . $name . '.log'; + } - $runtime = round(microtime(true) - THINK_START_TIME, 10); - $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; - $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); - $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @param bool $append 是否追加请求信息 + * @return bool + */ + protected function write($message, $destination, $apart = false, $append = false) + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); - $message = '[ info ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n" . $message; - } - $now = date($this->config['time_format']); - $ip = Request::instance()->ip(); - $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; - $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; - $message = "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}\r\n" . $message; + // 日志信息封装 + $info['timestamp'] = date($this->config['time_format']); - $this->writed[$destination] = true; + foreach ($message as $type => $msg) { + $info[$type] = is_array($msg) ? implode("\r\n", $msg) : $msg; } - if (IS_CLI) { - $now = date($this->config['time_format']); - $message = "[{$now}]" . $message; + if (PHP_SAPI == 'cli') { + $message = $this->parseCliLog($info); + } else { + // 添加调试日志 + $this->getDebugLog($info, $append, $apart); + + $message = $this->parseLog($info); } return error_log($message, 3, $destination); } + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize($destination) + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + } + } + } + + /** + * CLI日志解析 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseCliLog($info) + { + if ($this->config['json']) { + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } else { + $now = $info['timestamp']; + unset($info['timestamp']); + + $message = implode("\r\n", $info); + + $message = "[{$now}]" . $message . "\r\n"; + } + + return $message; + } + + /** + * 解析日志 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseLog($info) + { + $request = Request::instance(); + $requestInfo = [ + 'ip' => $request->ip(), + 'method' => $request->method(), + 'host' => $request->host(), + 'uri' => $request->url(), + ]; + + if ($this->config['json']) { + $info = $requestInfo + $info; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } + + array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + unset($info['timestamp']); + + return implode("\r\n", $info) . "\r\n"; + } + + protected function getDebugLog(&$info, $append, $apart) + { + if (App::$debug && $append) { + + if ($this->config['json']) { + // 获取基本信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $info = [ + 'runtime' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ] + $info; + + } elseif (!$apart) { + // 增加额外的调试信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + array_unshift($info, $time_str . $memory_str . $file_load); + } + } + } } diff --git a/thinkphp/library/think/log/driver/Socket.php b/thinkphp/library/think/log/driver/Socket.php index d30bba30..4f62915b 100644 --- a/thinkphp/library/think/log/driver/Socket.php +++ b/thinkphp/library/think/log/driver/Socket.php @@ -60,7 +60,7 @@ class Socket * @param array $log 日志信息 * @return bool */ - public function save(array $log = []) + public function save(array $log = [], $append = false) { if (!$this->check()) { return false; diff --git a/thinkphp/library/think/model/relation/BelongsToMany.php b/thinkphp/library/think/model/relation/BelongsToMany.php index 787edc07..a41c45ce 100644 --- a/thinkphp/library/think/model/relation/BelongsToMany.php +++ b/thinkphp/library/think/model/relation/BelongsToMany.php @@ -29,6 +29,8 @@ class BelongsToMany extends Relation protected $pivotName; // 中间表模型对象 protected $pivot; + // 中间表数据名称 + protected $pivotDataName = 'pivot'; /** * 构造函数 @@ -70,6 +72,18 @@ class BelongsToMany extends Relation return $this; } + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function pivotDataName($name) + { + $this->pivotDataName = $name; + return $this; + } + /** * 获取中间表更新条件 * @param $data @@ -118,7 +132,7 @@ class BelongsToMany extends Relation } } } - $model->setRelation('pivot', $this->newPivot($pivot, true)); + $model->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); } } @@ -346,10 +360,18 @@ class BelongsToMany extends Relation * 获取关联统计子查询 * @access public * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 * @return string */ - public function getRelationCountQuery($closure) + public function getRelationCountQuery($closure, &$name = null) { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ 'pivot.' . $this->localKey => [ 'exp', @@ -384,7 +406,7 @@ class BelongsToMany extends Relation } } } - $set->setRelation('pivot', $this->newPivot($pivot, true)); + $set->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); $data[$pivot[$this->localKey]][] = $set; } return $data; @@ -499,6 +521,29 @@ class BelongsToMany extends Relation } } + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot + * @throws Exception + */ + public function attached($data) + { + if ($data instanceof Model) { + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } else { + $id = $data; + } + + $pk = $this->parent->getPk(); + + $pivot = $this->pivot->where($this->localKey, $this->parent->$pk)->where($this->foreignKey, $id)->find(); + + return $pivot ?: false; + } + /** * 解除关联的一个中间表数据 * @access public diff --git a/thinkphp/library/think/model/relation/HasMany.php b/thinkphp/library/think/model/relation/HasMany.php index cfcf2a98..c4b31af9 100644 --- a/thinkphp/library/think/model/relation/HasMany.php +++ b/thinkphp/library/think/model/relation/HasMany.php @@ -152,12 +152,16 @@ class HasMany extends Relation * 创建关联统计子查询 * @access public * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 * @return string */ - public function getRelationCountQuery($closure) + public function getRelationCountQuery($closure, &$name = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } } $localKey = $this->localKey ?: $this->parent->getPk(); return $this->query->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $localKey)->fetchSql()->count(); @@ -197,14 +201,26 @@ class HasMany extends Relation * @return Model|false */ public function save($data) + { + $model = $this->make($data); + return $model->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) { if ($data instanceof Model) { $data = $data->getData(); } + // 保存关联表数据 - $model = new $this->model; $data[$this->foreignKey] = $this->parent->{$this->localKey}; - return $model->save($data) ? $model : false; + + return new $this->model($data); } /** diff --git a/thinkphp/library/think/model/relation/HasManyThrough.php b/thinkphp/library/think/model/relation/HasManyThrough.php index b7b3333d..3a9a5482 100644 --- a/thinkphp/library/think/model/relation/HasManyThrough.php +++ b/thinkphp/library/think/model/relation/HasManyThrough.php @@ -120,6 +120,18 @@ class HasManyThrough extends Relation public function relationCount($result, $closure) {} + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } + /** * 执行基础查询(进执行一次) * @access protected diff --git a/thinkphp/library/think/model/relation/MorphMany.php b/thinkphp/library/think/model/relation/MorphMany.php index 8945042f..941ebe8b 100644 --- a/thinkphp/library/think/model/relation/MorphMany.php +++ b/thinkphp/library/think/model/relation/MorphMany.php @@ -188,15 +188,19 @@ class MorphMany extends Relation } /** - * 获取关联统计子查询 + * 创建关联统计子查询 * @access public * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 * @return string */ - public function getRelationCountQuery($closure) + public function getRelationCountQuery($closure, &$name = null) { if ($closure) { - call_user_func_array($closure, [ & $this->query]); + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } } return $this->query->where([ @@ -240,17 +244,30 @@ class MorphMany extends Relation * @return Model|false */ public function save($data) + { + $model = $this->make($data); + + return $model->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) { if ($data instanceof Model) { $data = $data->getData(); } + // 保存关联表数据 $pk = $this->parent->getPk(); - $model = new $this->model; $data[$this->morphKey] = $this->parent->$pk; $data[$this->morphType] = $this->type; - return $model->save($data) ? $model : false; + + return new $this->model($data); } /** diff --git a/thinkphp/library/think/model/relation/MorphOne.php b/thinkphp/library/think/model/relation/MorphOne.php index 2337599b..1f4bffd7 100644 --- a/thinkphp/library/think/model/relation/MorphOne.php +++ b/thinkphp/library/think/model/relation/MorphOne.php @@ -81,8 +81,8 @@ class MorphOne extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 * @return Query */ public function hasWhere($where = [], $fields = null) @@ -198,6 +198,17 @@ class MorphOne extends Relation * @return Model|false */ public function save($data) + { + $model = $this->make($data); + return $model->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) { if ($data instanceof Model) { $data = $data->getData(); @@ -205,10 +216,10 @@ class MorphOne extends Relation // 保存关联表数据 $pk = $this->parent->getPk(); - $model = new $this->model; $data[$this->morphKey] = $this->parent->$pk; $data[$this->morphType] = $this->type; - return $model->save($data) ? $model : false; + + return new $this->model($data); } /** @@ -227,4 +238,15 @@ class MorphOne extends Relation } } + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } } diff --git a/thinkphp/library/think/model/relation/MorphTo.php b/thinkphp/library/think/model/relation/MorphTo.php index a3a02934..7d452651 100644 --- a/thinkphp/library/think/model/relation/MorphTo.php +++ b/thinkphp/library/think/model/relation/MorphTo.php @@ -285,4 +285,15 @@ class MorphTo extends Relation return $this->parent->setRelation($this->relation, null); } + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } } diff --git a/thinkphp/library/think/model/relation/OneToOne.php b/thinkphp/library/think/model/relation/OneToOne.php index dd8595c9..353ce21b 100644 --- a/thinkphp/library/think/model/relation/OneToOne.php +++ b/thinkphp/library/think/model/relation/OneToOne.php @@ -323,4 +323,15 @@ abstract class OneToOne extends Relation return $data; } + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } } diff --git a/thinkphp/library/traits/model/SoftDelete.php b/thinkphp/library/traits/model/SoftDelete.php index 4c8a6b82..70f31ba2 100644 --- a/thinkphp/library/traits/model/SoftDelete.php +++ b/thinkphp/library/traits/model/SoftDelete.php @@ -2,6 +2,7 @@ namespace traits\model; +use think\Collection; use think\db\Query; use think\Model; @@ -111,7 +112,7 @@ trait SoftDelete } // 包含软删除数据 - $query = self::withTrashed(); + $query = (new static())->db(false); if (is_array($data) && key($data) !== 0) { $query->where($data); $data = null; diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index add6e245..f3605061 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -894,17 +894,17 @@ }, { "name": "topthink/framework", - "version": "v5.0.21", - "version_normalized": "5.0.21.0", + "version": "v5.0.23", + "version_normalized": "5.0.23.0", "source": { "type": "git", "url": "https://github.com/top-think/framework.git", - "reference": "ab826da071a7a47116a7f1d01f72228d6bcf212a" + "reference": "4cbc0b5e93314446243ebc7d5f005f9c32864737" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/top-think/framework/zipball/ab826da071a7a47116a7f1d01f72228d6bcf212a", - "reference": "ab826da071a7a47116a7f1d01f72228d6bcf212a", + "url": "https://api.github.com/repos/top-think/framework/zipball/4cbc0b5e93314446243ebc7d5f005f9c32864737", + "reference": "4cbc0b5e93314446243ebc7d5f005f9c32864737", "shasum": "" }, "require": { @@ -919,7 +919,7 @@ "phpunit/phpunit": "4.8.*", "sebastian/phpcpd": "2.*" }, - "time": "2018-09-04T09:18:48+00:00", + "time": "2018-12-09T12:40:40+00:00", "type": "think-framework", "installation-source": "dist", "autoload": {