diff --git a/application/admin/config.php b/application/admin/config.php index 46f13453..37e8ce8d 100644 --- a/application/admin/config.php +++ b/application/admin/config.php @@ -29,5 +29,5 @@ return array( 'prefix' => 'admin', 'type' => '', 'auto_start' => true, - ), + ) ); \ No newline at end of file diff --git a/core/base.php b/core/base.php index 63fc16c3..9520a92f 100644 --- a/core/base.php +++ b/core/base.php @@ -9,7 +9,7 @@ // | Author: liu21st // +---------------------------------------------------------------------- -define('THINK_VERSION', '5.0.6'); +define('THINK_VERSION', '5.0.7'); define('THINK_START_TIME', microtime(true)); define('THINK_START_MEM', memory_get_usage()); define('EXT', '.php'); diff --git a/core/library/think/App.php b/core/library/think/App.php index 963d8b35..531bc7c4 100644 --- a/core/library/think/App.php +++ b/core/library/think/App.php @@ -85,14 +85,14 @@ class App $request->filter($config['default_filter']); + // 默认语言 + Lang::range($config['default_lang']); if ($config['lang_switch_on']) { // 开启多语言机制 检测当前语言 Lang::detect(); - } else { - // 读取默认语言 - Lang::range($config['default_lang']); } $request->langset(Lang::range()); + // 加载系统语言包 Lang::load([ THINK_PATH . 'lang' . DS . $request->langset() . EXT, @@ -120,35 +120,7 @@ class App // 请求缓存检查 $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']); - switch ($dispatch['type']) { - case 'redirect': - // 执行重定向跳转 - $data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']); - break; - case 'module': - // 模块/控制器/操作 - $data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null); - break; - case 'controller': - // 执行控制器操作 - $vars = array_merge(Request::instance()->param(), $dispatch['var']); - $data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']); - break; - case 'method': - // 执行回调方法 - $vars = array_merge(Request::instance()->param(), $dispatch['var']); - $data = self::invokeMethod($dispatch['method'], $vars); - break; - case 'function': - // 执行闭包 - $data = self::invokeFunction($dispatch['function']); - break; - case 'response': - $data = $dispatch['response']; - break; - default: - throw new \InvalidArgumentException('dispatch type not support'); - } + $data = self::exec($dispatch, $config); } catch (HttpResponseException $exception) { $data = $exception->getResponse(); } @@ -245,7 +217,7 @@ class App /** * 绑定参数 - * @access public + * @access private * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 * @param array $vars 变量 * @return array @@ -261,43 +233,90 @@ class App } } $args = []; - // 判断数组类型 数字数组时按顺序绑定参数 - reset($vars); - $type = key($vars) === 0 ? 1 : 0; if ($reflect->getNumberOfParameters() > 0) { + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; $params = $reflect->getParameters(); foreach ($params as $param) { - $name = $param->getName(); - $class = $param->getClass(); - if ($class) { - $className = $class->getName(); - $bind = Request::instance()->$name; - if ($bind instanceof $className) { - $args[] = $bind; - } else { - if (method_exists($className, 'invoke')) { - $method = new \ReflectionMethod($className, 'invoke'); - if ($method->isPublic() && $method->isStatic()) { - $args[] = $className::invoke(Request::instance()); - continue; - } - } - $args[] = method_exists($className, 'instance') ? $className::instance() : new $className; - } - } elseif (1 == $type && !empty($vars)) { - $args[] = array_shift($vars); - } elseif (0 == $type && isset($vars[$name])) { - $args[] = $vars[$name]; - } elseif ($param->isDefaultValueAvailable()) { - $args[] = $param->getDefaultValue(); - } else { - throw new \InvalidArgumentException('method param miss:' . $name); - } + $args[] = self::getParamValue($param, $vars, $type); } } return $args; } + /** + * 获取参数值 + * @access private + * @param \ReflectionParameter $param + * @param array $vars 变量 + * @param string $type + * @return array + */ + private static function getParamValue($param, &$vars, $type) + { + $name = $param->getName(); + $class = $param->getClass(); + if ($class) { + $className = $class->getName(); + $bind = Request::instance()->$name; + if ($bind instanceof $className) { + $result = $bind; + } else { + if (method_exists($className, 'invoke')) { + $method = new \ReflectionMethod($className, 'invoke'); + if ($method->isPublic() && $method->isStatic()) { + return $className::invoke(Request::instance()); + } + } + $result = method_exists($className, 'instance') ? $className::instance() : new $className; + } + } elseif (1 == $type && !empty($vars)) { + $result = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $result = $vars[$name]; + } elseif ($param->isDefaultValueAvailable()) { + $result = $param->getDefaultValue(); + } else { + throw new \InvalidArgumentException('method param miss:' . $name); + } + return $result; + } + + protected static function exec($dispatch, $config) + { + switch ($dispatch['type']) { + case 'redirect': + // 执行重定向跳转 + $data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']); + break; + case 'module': + // 模块/控制器/操作 + $data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null); + break; + case 'controller': + // 执行控制器操作 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']); + break; + case 'method': + // 执行回调方法 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = self::invokeMethod($dispatch['method'], $vars); + break; + case 'function': + // 执行闭包 + $data = self::invokeFunction($dispatch['function']); + break; + case 'response': + $data = $dispatch['response']; + break; + default: + throw new \InvalidArgumentException('dispatch type not support'); + } + return $data; + } + /** * 执行模块 * @access public diff --git a/core/library/think/File.php b/core/library/think/File.php index ba59273f..636a3430 100644 --- a/core/library/think/File.php +++ b/core/library/think/File.php @@ -11,7 +11,6 @@ namespace think; -use SplFileInfo; use SplFileObject; class File extends SplFileObject @@ -281,7 +280,7 @@ class File extends SplFileObject * @param string $path 保存路径 * @param string|bool $savename 保存的文件名 默认自动生成 * @param boolean $replace 同名文件是否覆盖 - * @return false|SplFileInfo false-失败 否则返回SplFileInfo实例 + * @return false|File false-失败 否则返回File实例 */ public function move($path, $savename = true, $replace = true) { diff --git a/core/library/think/Loader.php b/core/library/think/Loader.php index 70de3175..a0727fef 100644 --- a/core/library/think/Loader.php +++ b/core/library/think/Loader.php @@ -369,8 +369,9 @@ class Loader if (isset(self::$instance[$guid])) { return self::$instance[$guid]; } - if (strpos($name, '\\')) { - $class = $name; + if (false !== strpos($name, '\\')) { + $class = $name; + $module = Request::instance()->module(); } else { if (strpos($name, '/')) { list($module, $name) = explode('/', $name, 2); @@ -404,8 +405,9 @@ class Loader */ public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') { - if (strpos($name, '\\')) { - $class = $name; + if (false !== strpos($name, '\\')) { + $class = $name; + $module = Request::instance()->module(); } else { if (strpos($name, '/')) { list($module, $name) = explode('/', $name); @@ -440,8 +442,9 @@ class Loader if (isset(self::$instance[$guid])) { return self::$instance[$guid]; } - if (strpos($name, '\\')) { - $class = $name; + if (false !== strpos($name, '\\')) { + $class = $name; + $module = Request::instance()->module(); } else { if (strpos($name, '/')) { list($module, $name) = explode('/', $name); diff --git a/core/library/think/Log.php b/core/library/think/Log.php index 4a8b0b40..5a658880 100644 --- a/core/library/think/Log.php +++ b/core/library/think/Log.php @@ -84,7 +84,7 @@ class Log public static function record($msg, $type = 'log') { self::$log[$type][] = $msg; - if (IS_CLI && count(self::$log[$type]) > 100) { + if (IS_CLI) { // 命令行下面日志写入改进 self::save(); } @@ -101,7 +101,7 @@ class Log /** * 当前日志记录的授权key - * @param string $key 授权key + * @param string $key 授权key * @return void */ public static function key($key) @@ -111,7 +111,7 @@ class Log /** * 检查日志写入权限 - * @param array $config 当前日志配置参数 + * @param array $config 当前日志配置参数 * @return bool */ public static function check($config) @@ -166,13 +166,14 @@ class Log /** * 实时写入日志信息 并支持行为 - * @param mixed $msg 调试信息 - * @param string $type 信息类型 + * @param mixed $msg 调试信息 + * @param string $type 信息类型 * @param bool $force 是否强制写入 * @return bool */ public static function write($msg, $type = 'log', $force = false) { + $log = self::$log; // 封装日志信息 if (true === $force || empty(self::$config['level'])) { $log[$type][] = $msg; @@ -188,7 +189,11 @@ class Log self::init(Config::get('log')); } // 写入日志 - return self::$driver->save($log, false); + $result = self::$driver->save($log); + if ($result) { + self::$log = []; + } + return $result; } /** diff --git a/core/library/think/Model.php b/core/library/think/Model.php index ebeffbb4..5d050c51 100644 --- a/core/library/think/Model.php +++ b/core/library/think/Model.php @@ -290,9 +290,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } // 标记字段更改 - if (isset($this->data[$name]) && is_scalar($this->data[$name]) && is_scalar($value) && 0 !== strcmp($this->data[$name], $value)) { + if (!isset($this->data[$name])) { $this->change[] = $name; - } elseif (!isset($this->data[$name]) || $value != $this->data[$name]) { + } elseif (is_scalar($value) && is_scalar($this->data[$name]) && 0 !== strcmp($this->data[$name], $value)) { + $this->change[] = $name; + } elseif (!is_object($value) && $value != $this->data[$name]) { $this->change[] = $name; } // 设置数据对象属性 @@ -378,7 +380,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (empty($param)) { $value = (float) $value; } else { - $value = (float) number_format($value, $param); + $value = (float) number_format($value, $param, '.', ''); } break; case 'boolean': @@ -486,7 +488,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (empty($param)) { $value = (float) $value; } else { - $value = (float) number_format($value, $param); + $value = (float) number_format($value, $param, '.', ''); } break; case 'boolean': @@ -508,7 +510,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $value = json_decode($value, true); break; case 'array': - $value = is_null($value) ? [] : json_decode($value, true); + $value = empty($value) ? [] : json_decode($value, true); break; case 'object': $value = empty($value) ? new \stdClass() : json_decode($value); @@ -837,11 +839,6 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 数据自动完成 $this->autoCompleteData($this->auto); - // 自动写入更新时间 - if ($this->autoWriteTimestamp && $this->updateTime && (empty($this->change) || !in_array($this->updateTime, $this->change))) { - $this->setAttr($this->updateTime, null); - } - // 事件回调 if (false === $this->trigger('before_write', $this)) { return false; @@ -873,6 +870,14 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } } + if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) { + // 没有更新 + return 0; + } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + } + if (empty($where) && !empty($this->updateWhere)) { $where = $this->updateWhere; } @@ -919,10 +924,14 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } else { // 自动写入 $this->autoCompleteData($this->insert); - - // 自动写入创建时间 - if ($this->autoWriteTimestamp && $this->createTime && (empty($this->change) || !in_array($this->createTime, $this->change))) { - $this->setAttr($this->createTime, null); + // 自动写入创建时间和更新时间 + if ($this->autoWriteTimestamp) { + if ($this->createTime && !isset($this->data[$this->createTime])) { + $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); + } + if ($this->updateTime && !isset($this->data[$this->updateTime])) { + $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + } } if (false === $this->trigger('before_insert', $this)) { diff --git a/core/library/think/Paginator.php b/core/library/think/Paginator.php index 0c1bea8a..d3547f8b 100644 --- a/core/library/think/Paginator.php +++ b/core/library/think/Paginator.php @@ -341,7 +341,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J { try { $total = $this->total(); - } catch (Exception $e) { + } catch (\DomainException $e) { $total = null; } @@ -349,7 +349,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J 'total' => $total, 'per_page' => $this->listRows(), 'current_page' => $this->currentPage(), - 'data' => $this->items->toArray() + 'data' => $this->items->toArray(), ]; } diff --git a/core/library/think/Request.php b/core/library/think/Request.php index f32637d5..d4f96a75 100644 --- a/core/library/think/Request.php +++ b/core/library/think/Request.php @@ -633,7 +633,7 @@ class Request if (true === $name) { // 获取包含文件上传信息的数组 $file = $this->file(); - $data = array_merge($this->param, $file); + $data = is_array($file) ? array_merge($this->param, $file) : $this->param; return $this->input($data, '', $default, $filter); } return $this->input($this->param, $name, $default, $filter); @@ -688,7 +688,7 @@ class Request { if (empty($this->post)) { $content = $this->input; - if (empty($_POST) && 'application/json' == $this->contentType()) { + if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) { $this->post = (array) json_decode($content, true); } else { $this->post = $_POST; @@ -713,7 +713,7 @@ class Request { if (is_null($this->put)) { $content = $this->input; - if ('application/json' == $this->contentType()) { + if (false !== strpos($this->contentType(), 'application/json')) { $this->put = (array) json_decode($content, true); } else { parse_str($content, $this->put); @@ -1357,7 +1357,11 @@ class Request { $contentType = $this->server('CONTENT_TYPE'); if ($contentType) { - list($type) = explode(';', $contentType); + if (strpos($contentType, ';')) { + list($type) = explode(';', $contentType); + } else { + $type = $contentType; + } return trim($type); } return ''; diff --git a/core/library/think/Route.php b/core/library/think/Route.php index cf640a4a..0fa95d97 100644 --- a/core/library/think/Route.php +++ b/core/library/think/Route.php @@ -833,7 +833,7 @@ class Route // 分隔符替换 确保路由定义使用统一的分隔符 $url = str_replace($depr, '|', $url); - if (strpos($url, '|') && isset(self::$rules['alias'][strstr($url, '|', true)])) { + if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) { // 检测路由别名 $result = self::checkRouteAlias($request, $url, $depr); if (false !== $result) { @@ -1057,7 +1057,7 @@ class Route if (!empty($array[1])) { self::parseUrlParams($array[1]); } - return ['type' => 'method', 'method' => [$class, $action]]; + return ['type' => 'method', 'method' => [$class, $action], 'var' => []]; } /** @@ -1077,7 +1077,7 @@ class Route if (!empty($array[2])) { self::parseUrlParams($array[2]); } - return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method]]; + return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []]; } /** @@ -1096,7 +1096,7 @@ class Route if (!empty($array[1])) { self::parseUrlParams($array[1]); } - return ['type' => 'controller', 'controller' => $controller . '/' . $action]; + return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []]; } /** @@ -1281,9 +1281,6 @@ class Route } elseif (strpos($url, '/')) { // [模块/控制器/操作] $path = explode('/', $url); - } elseif (false !== strpos($url, '=')) { - // 参数1=值1&参数2=值2... - parse_str($url, $var); } else { $path = [$url]; } diff --git a/core/library/think/Template.php b/core/library/think/Template.php index 7e20407c..4abf21ce 100644 --- a/core/library/think/Template.php +++ b/core/library/think/Template.php @@ -927,7 +927,7 @@ class Template if (false === strpos($name, '(')) { $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; } else { - $name = '(' . $name . ' !== \'\'?' . $name . ':' . $args[1] . ')'; + $name = '(' . $name . ' ?: ' . $args[1] . ')'; } break; default: // 通用模板函数 diff --git a/core/library/think/Url.php b/core/library/think/Url.php index de3cd912..d6e57037 100644 --- a/core/library/think/Url.php +++ b/core/library/think/Url.php @@ -268,7 +268,7 @@ class Url $host = $request->host(); $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; } - if (!strpos($domain, $rootDomain)) { + if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) { $domain .= '.' . $rootDomain; } } diff --git a/core/library/think/db/Builder.php b/core/library/think/db/Builder.php index 56665527..414672d1 100644 --- a/core/library/think/db/Builder.php +++ b/core/library/think/db/Builder.php @@ -98,14 +98,18 @@ abstract class Builder $result = []; foreach ($data as $key => $val) { $item = $this->parseKey($key, $options); + if (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $val = $val->__toString(); + } if (false === strpos($key, '.') && !in_array($key, $fields, true)) { if ($options['strict']) { throw new Exception('fields not exists:[' . $key . ']'); } - } elseif (isset($val[0]) && 'exp' == $val[0]) { - $result[$item] = $val[1]; } elseif (is_null($val)) { $result[$item] = 'NULL'; + } elseif (isset($val[0]) && 'exp' == $val[0]) { + $result[$item] = $val[1]; } elseif (is_scalar($val)) { // 过滤非标量数据 if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { @@ -115,9 +119,6 @@ abstract class Builder $this->query->bind('__data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); $result[$item] = ':__data__' . $key; } - } elseif (is_object($val) && method_exists($val, '__toString')) { - // 对象数据写入 - $result[$item] = $val->__toString(); } } return $result; @@ -279,6 +280,13 @@ abstract class Builder $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str); } + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + list($field, $condition) = $options['soft_delete']; + + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($field, $condition, '', $options, $binds); + } return $whereStr; } @@ -379,7 +387,7 @@ abstract class Builder } else { $zone = implode(',', $this->parseValue($value, $field)); } - $whereStr .= $key . ' ' . $exp . ' (' . $zone . ')'; + $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; } } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { // BETWEEN 查询 diff --git a/core/library/think/db/Query.php b/core/library/think/db/Query.php index cd5e9a2c..1534bf72 100644 --- a/core/library/think/db/Query.php +++ b/core/library/think/db/Query.php @@ -893,7 +893,7 @@ class Query } else { $name = $alias . '.' . $key; } - $fields[] = $name . ' AS ' . $val; + $fields[$name] = $val; $this->options['map'][$val] = $name; } } @@ -1120,6 +1120,20 @@ class Query return $this; } + /** + * 设置软删除字段及条件 + * @access public + * @param false|string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete($field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition ?: ['null', '']]; + } + } + /** * 分析查询表达式 * @access public @@ -1895,7 +1909,7 @@ class Query $relation = Loader::parseName($relation, 1, false); $model = $class->$relation(); if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { - $model->eagerly($this, $relation, $subRelation, $closure, $first); + $model->removeOption()->eagerly($this, $relation, $subRelation, $closure, $first); $first = false; } elseif ($closure) { $with[$key] = $closure; @@ -2210,7 +2224,7 @@ class Query // 执行操作 $result = '' == $sql ? 0 : $this->execute($sql, $bind); if ($result) { - if (isset($where[$pk])) { + if (is_string($pk) && isset($where[$pk])) { $data[$pk] = $where[$pk]; } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { list($a, $val) = explode('|', $key); @@ -2302,7 +2316,7 @@ class Query } } - if (isset($cache)) { + if (isset($cache) && $resultSet) { // 缓存数据集 $this->cacheData($key, $resultSet, $cache); } @@ -2459,7 +2473,7 @@ class Query $result = isset($resultSet[0]) ? $resultSet[0] : null; } - if (isset($cache)) { + if (isset($cache) && $result) { // 缓存数据 $this->cacheData($key, $result, $cache); } diff --git a/core/library/think/db/builder/Mysql.php b/core/library/think/db/builder/Mysql.php index e432e396..4aac4424 100644 --- a/core/library/think/db/builder/Mysql.php +++ b/core/library/think/db/builder/Mysql.php @@ -36,10 +36,11 @@ class Mysql extends Builder $key = 'json_extract(' . $field . ', \'$.' . $name . '\')'; } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } if (isset($options['alias'][$table])) { $table = $options['alias'][$table]; - } elseif ('__TABLE__' == $table) { - $table = $this->query->getTable(); } } if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { diff --git a/core/library/think/db/builder/Pgsql.php b/core/library/think/db/builder/Pgsql.php index 67b98b44..5be0468a 100644 --- a/core/library/think/db/builder/Pgsql.php +++ b/core/library/think/db/builder/Pgsql.php @@ -55,10 +55,11 @@ class Pgsql extends Builder $key = $field . '->>\'' . $name . '\''; } elseif (strpos($key, '.')) { list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } if (isset($options['alias'][$table])) { $table = $options['alias'][$table]; - } elseif ('__TABLE__' == $table) { - $table = $this->query->getTable(); } } if (isset($table)) { diff --git a/core/library/think/db/builder/Sqlite.php b/core/library/think/db/builder/Sqlite.php index 680b4965..55d3abc4 100644 --- a/core/library/think/db/builder/Sqlite.php +++ b/core/library/think/db/builder/Sqlite.php @@ -60,10 +60,11 @@ class Sqlite extends Builder $key = trim($key); if (strpos($key, '.')) { list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } if (isset($options['alias'][$table])) { $table = $options['alias'][$table]; - } elseif ('__TABLE__' == $table) { - $table = $this->query->getTable(); } } if (isset($table)) { diff --git a/core/library/think/db/builder/Sqlsrv.php b/core/library/think/db/builder/Sqlsrv.php index 99f68409..2e13d3fb 100644 --- a/core/library/think/db/builder/Sqlsrv.php +++ b/core/library/think/db/builder/Sqlsrv.php @@ -73,10 +73,11 @@ class Sqlsrv extends Builder $key = trim($key); if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } if (isset($options['alias'][$table])) { $table = $options['alias'][$table]; - } elseif ('__TABLE__' == $table) { - $table = $this->query->getTable(); } } if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { diff --git a/core/library/think/log/driver/File.php b/core/library/think/log/driver/File.php index b639fd04..fdf72658 100644 --- a/core/library/think/log/driver/File.php +++ b/core/library/think/log/driver/File.php @@ -25,6 +25,8 @@ class File 'apart_level' => [], ]; + protected $writed = []; + // 实例化并传入参数 public function __construct($config = []) { @@ -37,45 +39,17 @@ class File * 日志写入接口 * @access public * @param array $log 日志信息 - * @param bool $depr 是否写入分割线 * @return bool */ - public function save(array $log = [], $depr = true) + public function save(array $log = []) { - $now = date($this->config['time_format']); - $destination = $this->config['path'] . date('Ym') . DS . date('d') . '.log'; + $cli = IS_CLI ? '_cli' : ''; + $destination = $this->config['path'] . date('Ym') . DS . date('d') . $cli . '.log'; $path = dirname($destination); !is_dir($path) && mkdir($path, 0755, true); - //检测日志文件大小,超过配置大小则备份日志文件重新生成 - if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { - rename($destination, dirname($destination) . DS . $_SERVER['REQUEST_TIME'] . '-' . basename($destination)); - } - - $depr = $depr ? "---------------------------------------------------------------\r\n" : ''; $info = ''; - if (App::$debug) { - // 获取基本信息 - if (isset($_SERVER['HTTP_HOST'])) { - $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $current_uri = "cmd:" . implode(' ', $_SERVER['argv']); - } - - $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()) . ']'; - - $info = '[ log ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n"; - $server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '0.0.0.0'; - $remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; - $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; - $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; - } foreach ($log as $type => $val) { $level = ''; foreach ($val as $msg) { @@ -86,16 +60,60 @@ class File } if (in_array($type, $this->config['apart_level'])) { // 独立记录的日志级别 - $filename = $path . DS . date('d') . '_' . $type . '.log'; - error_log("[{$now}] {$level}\r\n{$depr}", 3, $filename); + $filename = $path . DS . date('d') . '_' . $type . $cli . '.log'; + $this->write($level, $filename, true); } else { $info .= $level; } } - if (App::$debug) { - $info = "{$server} {$remote} {$method} {$uri}\r\n" . $info; + if ($info) { + return $this->write($info, $destination); } - return error_log("[{$now}] {$info}\r\n{$depr}", 3, $destination); + return true; + } + + protected function write($message, $destination, $apart = false) + { + //检测日志文件大小,超过配置大小则备份日志文件重新生成 + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + rename($destination, dirname($destination) . DS . $_SERVER['REQUEST_TIME'] . '-' . basename($destination)); + $this->writed[$destination] = false; + } + + 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']); + } + + $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()) . ']'; + + $message = '[ info ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n" . $message; + } + $now = date($this->config['time_format']); + $server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '0.0.0.0'; + $remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; + $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; + $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + $message = "---------------------------------------------------------------\r\n[{$now}] {$server} {$remote} {$method} {$uri}\r\n" . $message; + + $this->writed[$destination] = true; + } + + if (IS_CLI) { + $now = date($this->config['time_format']); + $message = "[{$now}]" . $message; + } + + return error_log($message, 3, $destination); } } diff --git a/core/library/think/model/Pivot.php b/core/library/think/model/Pivot.php index 9fd45719..423921d7 100644 --- a/core/library/think/model/Pivot.php +++ b/core/library/think/model/Pivot.php @@ -16,13 +16,17 @@ use think\Model; class Pivot extends Model { + /** @var Model */ + public $parent; + /** * 构造函数 * @access public - * @param array|object $data 数据 - * @param string $table 中间数据表名 + * @param Model $parent + * @param array|object $data 数据 + * @param string $table 中间数据表名 */ - public function __construct($data = [], $table = '') + public function __construct(Model $parent, $data = [], $table = '') { if (is_object($data)) { $this->data = get_object_vars($data); @@ -30,6 +34,8 @@ class Pivot extends Model $this->data = $data; } + $this->parent = $parent; + $this->table = $table; } diff --git a/core/library/think/model/Relation.php b/core/library/think/model/Relation.php index 3d56091b..ed7e5603 100644 --- a/core/library/think/model/Relation.php +++ b/core/library/think/model/Relation.php @@ -91,7 +91,7 @@ abstract class Relation } /** - * 执行基础查询(进执行一次) + * 执行基础查询(仅执行一次) * @access protected * @return void */ diff --git a/core/library/think/model/relation/BelongsToMany.php b/core/library/think/model/relation/BelongsToMany.php index b7758e9f..34830e05 100644 --- a/core/library/think/model/relation/BelongsToMany.php +++ b/core/library/think/model/relation/BelongsToMany.php @@ -11,18 +11,23 @@ namespace think\model\relation; +use think\Collection; use think\db\Query; use think\Exception; use think\Loader; use think\Model; use think\model\Pivot; use think\model\Relation; +use think\Paginator; class BelongsToMany extends Relation { - // 中间表模型 + // 中间表表名 protected $middle; + // 中间表模型 + protected $pivot; + /** * 构造函数 * @access public @@ -42,6 +47,64 @@ class BelongsToMany extends Relation $this->query = (new $model)->db(); } + /** + * 设置中间表模型 + * @param $pivot + * @return $this + */ + public function pivot($pivot) + { + $this->pivot = $pivot; + return $this; + } + + /** + * 实例化中间表模型 + * @param $data + * @return mixed + */ + protected function newPivot($data) + { + $pivot = $this->pivot ?: '\\think\\model\\Pivot'; + return new $pivot($this->parent, $data, $this->middle); + } + + /** + * 合成中间表模型 + * @param array|Collection|Paginator $models + */ + protected function hydratePivot($models) + { + foreach ($models as $model) { + $pivot = []; + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + $model->pivot = $this->newPivot($pivot); + } + } + + /** + * 创建关联查询Query对象 + * @return Query + */ + protected function buildQuery() + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + $middle = $this->middle; + // 关联查询 + $pk = $this->parent->getPk(); + $condition['pivot.' . $localKey] = $this->parent->$pk; + return $this->belongsToManyQuery($middle, $foreignKey, $localKey, $condition); + } + /** * 延迟获取关联数据 * @param string $subRelation 子关联名 @@ -50,32 +113,74 @@ class BelongsToMany extends Relation */ public function getRelation($subRelation = '', $closure = null) { - $foreignKey = $this->foreignKey; - $localKey = $this->localKey; - $middle = $this->middle; if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - // 关联查询 - $pk = $this->parent->getPk(); - $condition['pivot.' . $localKey] = $this->parent->$pk; - $result = $this->belongsToManyQuery($middle, $foreignKey, $localKey, $condition)->relation($subRelation)->select(); - foreach ($result as $set) { - $pivot = []; - foreach ($set->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ('pivot' == $name) { - $pivot[$attr] = $val; - unset($set->$key); - } - } - } - $set->pivot = new Pivot($pivot, $this->middle); + call_user_func_array($closure, [& $this->query]); } + $result = $this->buildQuery()->relation($subRelation)->select(); + $this->hydratePivot($result); return $result; } + /** + * 重载select方法 + * @param null $data + * @return false|\PDOStatement|string|Collection + */ + public function select($data = null) + { + $result = $this->buildQuery()->select($data); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载paginate方法 + * @param null $listRows + * @param bool $simple + * @param array $config + * @return Paginator + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + $result = $this->buildQuery()->paginate($listRows, $simple, $config); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载find方法 + * @param null $data + * @return array|false|\PDOStatement|string|Model + */ + public function find($data = null) + { + $result = $this->buildQuery()->find($data); + $this->hydratePivot([$result]); + return $result; + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + /** * 根据关联条件查询当前模型 * @access public @@ -95,12 +200,27 @@ class BelongsToMany extends Relation * @access public * @param mixed $where 查询条件(数组或者闭包) * @return Query + * @throws Exception */ public function hasWhere($where = []) { throw new Exception('relation not support: hasWhere'); } + /** + * 设置中间表的查询条件 + * @param $field + * @param null $op + * @param null $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $field = 'pivot.' . $field; + $this->query->where($field, $op, $condition); + return $this; + } + /** * 预载入关联查询(数据集) * @access public @@ -230,7 +350,7 @@ class BelongsToMany extends Relation } } } - $set->pivot = new Pivot($pivot, $this->middle); + $set->pivot = $this->newPivot($pivot); $data[$pivot[$this->localKey]][] = $set; } return $data; @@ -250,10 +370,14 @@ class BelongsToMany extends Relation // 关联查询封装 $tableName = $this->query->getTable(); $relationFk = $this->query->getPk(); - return $this->query->field($tableName . '.*') - ->field(true, false, $table, 'pivot', 'pivot__') - ->join($table . ' pivot', 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) - ->where($condition); + $query = $this->query->field($tableName . '.*') + ->field(true, false, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $query->join($table . ' pivot', 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + return $query; } /** @@ -327,7 +451,7 @@ class BelongsToMany extends Relation foreach ($ids as $id) { $pivot[$this->foreignKey] = $id; $this->query->table($this->middle)->insert($pivot, true); - $result[] = new Pivot($pivot, $this->middle); + $result[] = $this->newPivot($pivot); } if (count($result) == 1) { // 返回中间表模型对象 diff --git a/core/library/think/model/relation/HasMany.php b/core/library/think/model/relation/HasMany.php index 364d793d..4c045494 100644 --- a/core/library/think/model/relation/HasMany.php +++ b/core/library/think/model/relation/HasMany.php @@ -183,7 +183,7 @@ class HasMany extends Relation * 保存(新增)当前关联数据对象 * @access public * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @return integer + * @return Model|false */ public function save($data) { @@ -191,9 +191,9 @@ class HasMany extends Relation $data = $data->getData(); } // 保存关联表数据 - $data[$this->foreignKey] = $this->parent->{$this->localKey}; $model = new $this->model; - return $model->save($data); + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + return $model->save($data) ? $model : false; } /** diff --git a/core/library/think/model/relation/MorphMany.php b/core/library/think/model/relation/MorphMany.php index 58e44135..964d3d3a 100644 --- a/core/library/think/model/relation/MorphMany.php +++ b/core/library/think/model/relation/MorphMany.php @@ -215,7 +215,7 @@ class MorphMany extends Relation * 保存(新增)当前关联数据对象 * @access public * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @return integer + * @return Model|false */ public function save($data) { @@ -225,10 +225,10 @@ class MorphMany extends Relation // 保存关联表数据 $pk = $this->parent->getPk(); + $model = new $this->model; $data[$this->morphKey] = $this->parent->$pk; $data[$this->morphType] = $this->type; - $model = new $this->model; - return $model->save($data); + return $model->save($data) ? $model : false; } /** diff --git a/core/library/think/model/relation/MorphTo.php b/core/library/think/model/relation/MorphTo.php index 6c31a752..5fafd47a 100644 --- a/core/library/think/model/relation/MorphTo.php +++ b/core/library/think/model/relation/MorphTo.php @@ -114,6 +114,16 @@ class MorphTo extends Relation return $this; } + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + /** * 预载入关联查询 * @access public diff --git a/core/library/think/model/relation/OneToOne.php b/core/library/think/model/relation/OneToOne.php index 18b92225..e2b37421 100644 --- a/core/library/think/model/relation/OneToOne.php +++ b/core/library/think/model/relation/OneToOne.php @@ -67,6 +67,7 @@ abstract class OneToOne extends Relation $field = true; } $query->field($field, false, $table, $alias); + $field = null; } // 预载入封装 @@ -82,7 +83,7 @@ abstract class OneToOne extends Relation if ($closure) { // 执行闭包查询 - call_user_func_array($closure, [& $query]); + call_user_func_array($closure, [ & $query]); // 使用withField指定获取关联的字段,如 // $query->where(['id'=>1])->withField('id,name'); if ($query->getOptions('with_field')) { @@ -91,10 +92,8 @@ abstract class OneToOne extends Relation } } elseif (isset($this->option['field'])) { $field = $this->option['field']; - } else { - $field = true; } - $query->field($field, false, $joinTable, $joinAlias, $relation . '__'); + $query->field(isset($field) ? $field : true, false, $joinTable, $joinAlias, $relation . '__'); } /** @@ -163,17 +162,17 @@ abstract class OneToOne extends Relation * 保存(新增)当前关联数据对象 * @access public * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @return integer + * @return Model|false */ public function save($data) { if ($data instanceof Model) { $data = $data->getData(); } + $model = new $this->model; // 保存关联表数据 $data[$this->foreignKey] = $this->parent->{$this->localKey}; - $model = new $this->model; - return $model->save($data); + return $model->save($data) ? $model : false; } /** @@ -195,7 +194,6 @@ abstract class OneToOne extends Relation */ public function getEagerlyType() { - $this->removeOption(); return $this->eagerlyType; } @@ -290,7 +288,7 @@ abstract class OneToOne extends Relation { // 预载入关联查询 支持嵌套预载入 if ($closure) { - call_user_func_array($closure, [& $model]); + call_user_func_array($closure, [ & $model]); if ($field = $model->getOptions('with_field')) { $model->field($field)->removeOption('with_field'); } diff --git a/core/library/think/template/TagLib.php b/core/library/think/template/TagLib.php index 2e0d232f..d343bed0 100644 --- a/core/library/think/template/TagLib.php +++ b/core/library/think/template/TagLib.php @@ -190,7 +190,7 @@ class TagLib * @param boolean $close 是否为闭合标签 * @return string */ - private function getRegex($tags, $close) + public function getRegex($tags, $close) { $begin = $this->tpl->config('taglib_begin'); $end = $this->tpl->config('taglib_end'); diff --git a/core/library/traits/model/SoftDelete.php b/core/library/traits/model/SoftDelete.php index 2b97ff72..c0924776 100644 --- a/core/library/traits/model/SoftDelete.php +++ b/core/library/traits/model/SoftDelete.php @@ -30,7 +30,7 @@ trait SoftDelete { $model = new static(); $field = $model->getDeleteTimeField(true); - return $model->db(false)->removeWhereField($field); + return $model->db(false); } /** @@ -42,7 +42,8 @@ trait SoftDelete { $model = new static(); $field = $model->getDeleteTimeField(true); - return $model->db(false)->whereNotNull($field); + return $model->db(false) + ->useSoftDelete($field, ['not null', '']); } /** @@ -112,12 +113,14 @@ trait SoftDelete { $name = $this->getDeleteTimeField(); if (empty($where)) { - $pk = $this->getPk(); - $where[$pk] = $this->getData($pk); - $where[$name] = ['not null', '']; + $pk = $this->getPk(); + $where[$pk] = $this->getData($pk); } // 恢复删除 - return $this->db(false)->removeWhereField($this->getDeleteTimeField(true))->where($where)->update([$name => null]); + return $this->db(false) + ->useSoftDelete($name, ['not null', '']) + ->where($where) + ->update([$name => null]); } /** @@ -129,7 +132,7 @@ trait SoftDelete protected function base($query) { $field = $this->getDeleteTimeField(true); - $query->whereNull($field); + $query->useSoftDelete($field); } /**