diff --git a/application/api/controller/AccessToken.php b/application/api/controller/AccessToken.php new file mode 100644 index 00000000..875e5915 --- /dev/null +++ b/application/api/controller/AccessToken.php @@ -0,0 +1,15 @@ + +// +---------------------------------------------------------------------- + +namespace app\api\controller; +use api\BaseAuth; + +class AccessToken extends BaseAuth { + +} \ No newline at end of file diff --git a/application/api/controller/Base.php b/application/api/controller/Base.php new file mode 100644 index 00000000..a9941d04 --- /dev/null +++ b/application/api/controller/Base.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- + +namespace app\api\controller; +use api\BaseRest; + +class Base extends BaseRest { + + // 允许访问的请求类型 + protected $restMethodList = 'get|post|put|delete|patch|head|options'; + //业务错误码的映射表 + public $errMap = [ + 0 => 'success', //没有错误 + 1001 => '参数错误', + 9999 => '自定义错误', //让程序给出的自定义错误 + ]; + + //是否开启权限认证 + public $apiAuth = true; +} \ No newline at end of file diff --git a/application/api/controller/Index.php b/application/api/controller/Index.php index bf1fe914..1a2c5ca3 100644 --- a/application/api/controller/Index.php +++ b/application/api/controller/Index.php @@ -8,10 +8,30 @@ // +---------------------------------------------------------------------- namespace app\api\controller; -use app\common\controller\User; -class Index extends Api{ +use think\Request; - public function index(){ +class Index extends Base { + + public $apiAuth = true; + // 允许访问的请求类型 + protected $restMethodList = 'get|post|'; + + /** + * get的响应 + * @param Request $request + * @return mixed + */ + public function getResponse(Request $request) { + return $this->sendError(1001, 'THIS IS GET', 400); + } + + /** + * post的响应 + * @param Request $request + * @return mixed + */ + public function postResponse(Request $request) { + return $this->sendSuccess('THIS IS POST'); } } diff --git a/core/base.php b/core/base.php index 9520a92f..9744f01d 100644 --- a/core/base.php +++ b/core/base.php @@ -9,7 +9,7 @@ // | Author: liu21st // +---------------------------------------------------------------------- -define('THINK_VERSION', '5.0.7'); +define('THINK_VERSION', '5.0.9'); define('THINK_START_TIME', microtime(true)); define('THINK_START_MEM', memory_get_usage()); define('EXT', '.php'); diff --git a/core/convention.php b/core/convention.php index 887b076d..62ab0ea4 100644 --- a/core/convention.php +++ b/core/convention.php @@ -5,8 +5,6 @@ return [ // | 应用设置 // +---------------------------------------------------------------------- - // 应用命名空间 - 'app_namespace' => 'app', // 应用调试模式 'app_debug' => true, // 应用Trace @@ -91,6 +89,8 @@ return [ 'url_route_must' => false, // 域名部署 'url_domain_deploy' => false, + // 默认Host地址 + 'default_host' => '', // 域名根,如thinkphp.cn 'url_domain_root' => '', // 是否自动转换URL中的控制器和操作名 diff --git a/core/helper.php b/core/helper.php index da0dc9c5..a23b6794 100644 --- a/core/helper.php +++ b/core/helper.php @@ -192,7 +192,7 @@ if (!function_exists('db')) { * @param bool $force 是否强制重新连接 * @return \think\db\Query */ - function db($name = '', $config = [], $force = true) + function db($name = '', $config = [], $force = false) { return Db::connect($config, $force)->name($name); } @@ -354,22 +354,25 @@ if (!function_exists('cache')) { { if (is_array($options)) { // 缓存操作的同时初始化 - Cache::connect($options); + $cache = Cache::connect($options); } elseif (is_array($name)) { // 缓存初始化 return Cache::connect($name); + } else { + $cache = Cache::init(); } + if (is_null($name)) { - return Cache::clear($value); + return $cache->clear($value); } elseif ('' === $value) { // 获取缓存 - return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); + return 0 === strpos($name, '?') ? $cache->has(substr($name, 1)) : $cache->get($name); } elseif (is_null($value)) { // 删除缓存 - return Cache::rm($name); + return $cache->rm($name); } elseif (0 === strpos($name, '?') && '' !== $value) { $expire = is_numeric($options) ? $options : null; - return Cache::remember(substr($name, 1), $value, $expire); + return $cache->remember(substr($name, 1), $value, $expire); } else { // 缓存数据 if (is_array($options)) { @@ -378,9 +381,9 @@ if (!function_exists('cache')) { $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 } if (is_null($tag)) { - return Cache::set($name, $value, $expire); + return $cache->set($name, $value, $expire); } else { - return Cache::tag($tag)->set($name, $value, $expire); + return $cache->tag($tag)->set($name, $value, $expire); } } } diff --git a/core/library/think/App.php b/core/library/think/App.php index 531bc7c4..f6ed5fdf 100644 --- a/core/library/think/App.php +++ b/core/library/think/App.php @@ -414,6 +414,11 @@ class App public static function initCommon() { if (empty(self::$init)) { + if (defined('APP_NAMESPACE')) { + self::$namespace = APP_NAMESPACE; + } + Loader::addNamespace(self::$namespace, APP_PATH); + // 初始化应用 $config = self::init(); self::$suffix = $config['class_suffix']; @@ -433,9 +438,6 @@ class App } } - // 注册应用命名空间 - self::$namespace = $config['app_namespace']; - Loader::addNamespace($config['app_namespace'], APP_PATH); if (!empty($config['root_namespace'])) { Loader::addNamespace($config['root_namespace']); } diff --git a/core/library/think/Cache.php b/core/library/think/Cache.php index 9e4b30ef..88029573 100644 --- a/core/library/think/Cache.php +++ b/core/library/think/Cache.php @@ -51,28 +51,29 @@ class Cache self::$instance[$name] = new $class($options); } } - self::$handler = self::$instance[$name]; - return self::$handler; + return self::$instance[$name]; } /** * 自动初始化缓存 * @access public * @param array $options 配置数组 - * @return void + * @return Driver */ public static function init(array $options = []) { if (is_null(self::$handler)) { // 自动初始化缓存 if (!empty($options)) { - self::connect($options); + $connect = self::connect($options); } elseif ('complex' == Config::get('cache.type')) { - self::connect(Config::get('cache.default')); + $connect = self::connect(Config::get('cache.default')); } else { - self::connect(Config::get('cache')); + $connect = self::connect(Config::get('cache')); } + self::$handler = $connect; } + return self::$handler; } /** @@ -84,9 +85,9 @@ class Cache public static function store($name = '') { if ('' !== $name && 'complex' == Config::get('cache.type')) { - self::connect(Config::get('cache.' . $name), strtolower($name)); + return self::connect(Config::get('cache.' . $name), strtolower($name)); } - return self::$handler; + return self::init(); } /** @@ -97,9 +98,8 @@ class Cache */ public static function has($name) { - self::init(); self::$readTimes++; - return self::$handler->has($name); + return self::init()->has($name); } /** @@ -111,9 +111,8 @@ class Cache */ public static function get($name, $default = false) { - self::init(); self::$readTimes++; - return self::$handler->get($name, $default); + return self::init()->get($name, $default); } /** @@ -126,9 +125,8 @@ class Cache */ public static function set($name, $value, $expire = null) { - self::init(); self::$writeTimes++; - return self::$handler->set($name, $value, $expire); + return self::init()->set($name, $value, $expire); } /** @@ -140,9 +138,8 @@ class Cache */ public static function inc($name, $step = 1) { - self::init(); self::$writeTimes++; - return self::$handler->inc($name, $step); + return self::init()->inc($name, $step); } /** @@ -154,9 +151,8 @@ class Cache */ public static function dec($name, $step = 1) { - self::init(); self::$writeTimes++; - return self::$handler->dec($name, $step); + return self::init()->dec($name, $step); } /** @@ -167,9 +163,8 @@ class Cache */ public static function rm($name) { - self::init(); self::$writeTimes++; - return self::$handler->rm($name); + return self::init()->rm($name); } /** @@ -180,9 +175,8 @@ class Cache */ public static function clear($tag = null) { - self::init(); self::$writeTimes++; - return self::$handler->clear($tag); + return self::init()->clear($tag); } /** @@ -193,10 +187,9 @@ class Cache */ public static function pull($name) { - self::init(); self::$readTimes++; self::$writeTimes++; - return self::$handler->pull($name); + return self::init()->pull($name); } /** @@ -209,9 +202,8 @@ class Cache */ public static function remember($name, $value, $expire = null) { - self::init(); self::$readTimes++; - return self::$handler->remember($name, $value, $expire); + return self::init()->remember($name, $value, $expire); } /** @@ -224,8 +216,7 @@ class Cache */ public static function tag($name, $keys = null, $overlay = false) { - self::init(); - return self::$handler->tag($name, $keys, $overlay); + return self::init()->tag($name, $keys, $overlay); } } diff --git a/core/library/think/Cookie.php b/core/library/think/Cookie.php index 93edb8d7..3205fcd9 100644 --- a/core/library/think/Cookie.php +++ b/core/library/think/Cookie.php @@ -135,22 +135,35 @@ class Cookie * @param string|null $prefix cookie前缀 * @return mixed */ - public static function get($name, $prefix = null) + public static function get($name = '', $prefix = null) { !isset(self::$init) && self::init(); $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; - $name = $prefix . $name; - if (isset($_COOKIE[$name])) { - $value = $_COOKIE[$name]; + $key = $prefix . $name; + + if ('' == $name) { + // 获取全部 + if ($prefix) { + $value = []; + foreach ($_COOKIE as $k => $val) { + if (0 === strpos($k, $prefix)) { + $value[$k] = $val; + } + } + } else { + $value = $_COOKIE; + } + } elseif (isset($_COOKIE[$key])) { + $value = $_COOKIE[$key]; if (0 === strpos($value, 'think:')) { $value = substr($value, 6); $value = json_decode($value, true); array_walk_recursive($value, 'self::jsonFormatProtect', 'decode'); } - return $value; } else { - return; + $value = null; } + return $value; } /** diff --git a/core/library/think/Db.php b/core/library/think/Db.php index 00f719e0..3e613f7a 100644 --- a/core/library/think/Db.php +++ b/core/library/think/Db.php @@ -36,7 +36,7 @@ use think\db\Query; * @method integer update(array $data) static 更新记录 * @method integer delete(mixed $data = null) static 删除记录 * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 - * @method mixed query(string $sql, array $bind = [], boolean $fetch = false, boolean $master = false, mixed $class = null) static SQL查询 + * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询 * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 * @method Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 * @method mixed transaction(callable $callback) static 执行数据库事务 @@ -44,6 +44,8 @@ use think\db\Query; * @method void commit() static 用于非自动提交状态下面的查询提交 * @method void rollback() static 事务回滚 * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 + * @method string quote(string $str) static SQL指令安全过滤 + * @method string getLastInsID($sequence = null) static 获取最近插入的ID */ class Db { diff --git a/core/library/think/Debug.php b/core/library/think/Debug.php index 844dfb89..9994e20c 100644 --- a/core/library/think/Debug.php +++ b/core/library/think/Debug.php @@ -38,7 +38,7 @@ class Debug } /** - * 统计某个区间的时间(微秒)使用情况 + * 统计某个区间的时间(微秒)使用情况 返回值以秒为单位 * @param string $start 开始标签 * @param string $end 结束标签 * @param integer|string $dec 小数位 @@ -53,7 +53,7 @@ class Debug } /** - * 统计从开始到统计时的时间(微秒)使用情况 + * 统计从开始到统计时的时间(微秒)使用情况 返回值以秒为单位 * @param integer|string $dec 小数位 * @return integer */ diff --git a/core/library/think/Lang.php b/core/library/think/Lang.php index d52f1947..2df3767c 100644 --- a/core/library/think/Lang.php +++ b/core/library/think/Lang.php @@ -25,6 +25,10 @@ class Lang protected static $langCookieExpire = 3600; // 允许语言列表 protected static $allowLangList = []; + // Accept-Language转义为对应语言包名称 系统默认配置 + protected static $acceptLanguage = [ + 'zh-hans-cn' => 'zh-cn', + ]; // 设定当前的语言 public static function range($range = '') @@ -34,6 +38,7 @@ class Lang } else { self::$range = $range; } + return self::$range; } /** @@ -93,7 +98,6 @@ class Lang /** * 获取语言定义(不区分大小写) * @param string|null $name 语言变量 - * @param array $vars 变量替换 * @param string $range 语言作用域 * @return mixed */ @@ -152,26 +156,25 @@ class Lang { // 自动侦测设置获取语言选择 $langSet = ''; + if (isset($_GET[self::$langDetectVar])) { // url中设置了语言变量 $langSet = strtolower($_GET[self::$langDetectVar]); - Cookie::set(self::$langCookieVar, $langSet, self::$langCookieExpire); - } elseif (Cookie::get(self::$langCookieVar)) { - // 获取上次用户的选择 - $langSet = strtolower(Cookie::get(self::$langCookieVar)); } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // 自动侦测浏览器语言 preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); - $langSet = strtolower($matches[1]); - Cookie::set(self::$langCookieVar, $langSet, self::$langCookieExpire); + $langSet = strtolower($matches[1]); + $acceptLangs = Config::get('header_accept_lang'); + if (isset($acceptLangs[$langSet])) { + $langSet = $acceptLangs[$langSet]; + } elseif (isset(self::$acceptLanguage[$langSet])) { + $langSet = self::$acceptLanguage[$langSet]; + } } if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) { // 合法的语言 self::$range = $langSet ?: self::$range; } - if ('zh-hans-cn' == self::$range) { - self::$range = 'zh-cn'; - } return self::$range; } diff --git a/core/library/think/Model.php b/core/library/think/Model.php index 5d050c51..55e7da8e 100644 --- a/core/library/think/Model.php +++ b/core/library/think/Model.php @@ -22,6 +22,7 @@ use think\model\relation\HasMany; use think\model\relation\HasManyThrough; use think\model\relation\HasOne; use think\model\relation\MorphMany; +use think\model\relation\MorphOne; use think\model\relation\MorphTo; /** @@ -31,10 +32,12 @@ use think\model\relation\MorphTo; */ abstract class Model implements \JsonSerializable, \ArrayAccess { - // 数据库对象池 + // 数据库查询对象池 protected static $links = []; // 数据库配置 protected $connection = []; + // 父关联模型对象 + protected $parent; // 数据库查询对象 protected $query; // 当前模型名称 @@ -63,8 +66,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected $append = []; // 数据信息 protected $data = []; - // 记录改变字段 - protected $change = []; + // 原始数据 + protected $origin = []; + // 关联模型 + protected $relation = []; // 保存自动完成列表 protected $auto = []; @@ -86,8 +91,6 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected $isUpdate = false; // 更新条件 protected $updateWhere; - // 当前执行的关联对象 - protected $relation; // 验证失败是否抛出异常 protected $failException = false; // 全局查询范围 @@ -98,8 +101,6 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected $resultSetType; // 关联自动写入 protected $relationWrite; - // - protected static $db; /** * 初始化过的模型. @@ -120,9 +121,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } else { $this->data = $data; } + // 记录原始数据 + $this->origin = $this->data; // 当前类名 - $this->class = get_class($this); + $this->class = get_called_class(); if (empty($this->name)) { // 当前模型名 @@ -136,63 +139,94 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_null($this->autoWriteTimestamp)) { // 自动写入时间戳 - $this->autoWriteTimestamp = $this->db(false)->getConfig('auto_timestamp'); + $this->autoWriteTimestamp = $this->getQuery()->getConfig('auto_timestamp'); } if (is_null($this->dateFormat)) { // 设置时间戳格式 - $this->dateFormat = $this->db(false)->getConfig('datetime_format'); + $this->dateFormat = $this->getQuery()->getConfig('datetime_format'); } if (is_null($this->resultSetType)) { - $this->resultSetType = $this->db(false)->getConfig('resultset_type'); + $this->resultSetType = $this->getQuery()->getConfig('resultset_type'); } // 执行初始化操作 $this->initialize(); } /** - * 获取当前模型的数据库查询对象 - * @access public - * @param bool $baseQuery 是否调用全局查询范围 + * 创建模型的查询对象 + * @access protected * @return Query */ - public function db($baseQuery = true) + protected function buildQuery() { - $model = $this->class; - if (!isset(self::$links[$model])) { - // 合并数据库配置 - if (!empty($this->connection)) { - if (is_array($this->connection)) { - $connection = array_merge(Config::get('database'), $this->connection); - } else { - $connection = $this->connection; - } + // 合并数据库配置 + if (!empty($this->connection)) { + if (is_array($this->connection)) { + $connection = array_merge(Config::get('database'), $this->connection); } else { - $connection = []; + $connection = $this->connection; } - // 设置当前模型 确保查询返回模型对象 - $query = Db::connect($connection)->getQuery($model, $this->query); - - // 设置当前数据表和模型名 - if (!empty($this->table)) { - $query->setTable($this->table); - } else { - $query->name($this->name); - } - - if (!empty($this->pk)) { - $query->pk($this->pk); - } - - self::$links[$model] = $query; + } else { + $connection = []; } + + $con = Db::connect($connection); + // 设置当前模型 确保查询返回模型对象 + $queryClass = $this->query ?: $con->getConfig('query'); + $query = new $queryClass($con, $this->class); + + // 设置当前数据表和模型名 + if (!empty($this->table)) { + $query->setTable($this->table); + } else { + $query->name($this->name); + } + + if (!empty($this->pk)) { + $query->pk($this->pk); + } + + return $query; + } + + /** + * 获取当前模型的查询对象 + * @access public + * @param bool $buildNewQuery 创建新的查询对象 + * @return Query + */ + public function getQuery($buildNewQuery = false) + { + if ($buildNewQuery) { + return $this->buildQuery(); + } elseif (!isset(self::$links[$this->class])) { + // 创建模型查询对象 + self::$links[$this->class] = $this->buildQuery(); + } + + return self::$links[$this->class]; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param bool $useBaseQuery 是否调用全局查询范围 + * @param bool $buildNewQuery 创建新的查询对象 + * @return Query + */ + public function db($useBaseQuery = true, $buildNewQuery = true) + { + $query = $this->getQuery($buildNewQuery); + // 全局作用域 - if ($baseQuery && method_exists($this, 'base')) { - call_user_func_array([$this, 'base'], [ & self::$links[$model]]); + if ($useBaseQuery && method_exists($this, 'base')) { + call_user_func_array([$this, 'base'], [ & $query]); } + // 返回当前模型的数据库查询对象 - return self::$links[$model]; + return $query; } /** @@ -218,6 +252,29 @@ abstract class Model implements \JsonSerializable, \ArrayAccess { } + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent($model) + { + $this->parent = $model; + + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + /** * 设置数据对象值 * @access public @@ -260,6 +317,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $this->data; } elseif (array_key_exists($name, $this->data)) { return $this->data[$name]; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; } else { throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); } @@ -282,26 +341,48 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 检测修改器 $method = 'set' . Loader::parseName($name, 1) . 'Attr'; if (method_exists($this, $method)) { - $value = $this->$method($value, array_merge($data, $this->data)); + $value = $this->$method($value, array_merge($this->data, $data)); } elseif (isset($this->type[$name])) { // 类型转换 $value = $this->writeTransform($value, $this->type[$name]); } } - // 标记字段更改 - if (!isset($this->data[$name])) { - $this->change[] = $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; - } // 设置数据对象属性 $this->data[$name] = $value; return $this; } + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @return mixed + */ + public function getRelation($name = null) + { + if (is_null($name)) { + return $this->relation; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + return; + } + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @return $this + */ + public function setRelation($name, $value) + { + $this->relation[$name] = $value; + return $this; + } + /** * 自动写入时间戳 * @access public @@ -319,12 +400,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess case 'datetime': case 'date': $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $format); + $value = $this->formatDateTime(time(), $format); break; case 'timestamp': case 'integer': default: - $value = $_SERVER['REQUEST_TIME']; + $value = time(); break; } } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ @@ -333,9 +414,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess 'timestamp', ]) ) { - $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $this->dateFormat); + $value = $this->formatDateTime(time(), $this->dateFormat); } else { - $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $this->dateFormat, true); + $value = $this->formatDateTime(time(), $this->dateFormat, true); } return $value; } @@ -367,6 +448,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected function writeTransform($value, $type) { + if (is_null($value)) { + return; + } + if (is_array($type)) { list($type, $param) = $type; } elseif (strpos($type, ':')) { @@ -451,14 +536,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $value = $this->formatDateTime($value, $this->dateFormat); } } elseif ($notFound) { - $method = Loader::parseName($name, 1, false); - if (method_exists($this, $method) && $this->$method() instanceof Relation) { - // 清空之前的查询参数 - $this->$method()->removeOption(); + $relation = Loader::parseName($name, 1, false); + if (method_exists($this, $relation)) { + $modelRelation = $this->$relation(); // 不存在该字段 获取关联数据 - $value = $this->$method()->getRelation(); + $value = $this->getRelationData($modelRelation); // 保存关联对象值 - $this->data[$name] = $value; + $this->relation[$name] = $value; } else { throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); } @@ -466,6 +550,23 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $value; } + /** + * 获取关联模型数据 + * @access public + * @param Relation $modelRelation 模型关联对象 + * @return mixed + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && get_class($this->parent) == $modelRelation->getModel()) { + $value = $this->parent; + } else { + // 首先获取关联数据 + $value = $modelRelation->getRelation(); + } + return $value; + } + /** * 数据读取 类型转换 * @access public @@ -475,6 +576,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ protected function readTransform($value, $type) { + if (is_null($value)) { + return; + } + if (is_array($type)) { list($type, $param) = $type; } elseif (strpos($type, ':')) { @@ -553,14 +658,23 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_string($append)) { $append = explode(',', $append); } - $model = $this->getAttr($relation); + + $relation = Loader::parseName($relation, 1, false); + + // 获取关联数据 + if (isset($this->relation[$relation])) { + $model = $this->relation[$relation]; + } else { + $model = $this->getRelationData($this->$relation()); + } + if ($model instanceof Model) { foreach ($append as $key => $attr) { $key = is_numeric($key) ? $attr : $key; - if ($this->__isset($key)) { + if (isset($this->data[$key])) { throw new Exception('bind attr has exists:' . $key); } else { - $this->setAttr($key, $model->$attr); + $this->data[$key] = $model->$attr; } } } @@ -653,15 +767,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $item = []; $visible = []; $hidden = []; + + $data = array_merge($this->data, $this->relation); + // 过滤属性 if (!empty($this->visible)) { $array = $this->parseAttr($this->visible, $visible); - $data = array_intersect_key($this->data, array_flip($array)); + $data = array_intersect_key($data, array_flip($array)); } elseif (!empty($this->hidden)) { $array = $this->parseAttr($this->hidden, $hidden, false); - $data = array_diff_key($this->data, array_flip($array)); - } else { - $data = $this->data; + $data = array_diff_key($data, array_flip($array)); } foreach ($data as $key => $val) { @@ -672,7 +787,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 关联模型数据集 $arr = []; foreach ($val as $k => $value) { - $arr[$k] = $this->subToArray($value, $visible, $hidden, $k); + $arr[$k] = $this->subToArray($value, $visible, $hidden, $key); } $item[$key] = $arr; } else { @@ -754,10 +869,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess public function getPk($name = '') { if (!empty($name)) { - $table = $this->db(false)->getTable($name); - return $this->db(false)->getPk($table); + $table = $this->getQuery()->getTable($name); + return $this->getQuery()->getPk($table); } elseif (empty($this->pk)) { - $this->pk = $this->db(false)->getPk(); + $this->pk = $this->getQuery()->getPk(); } return $this->pk; } @@ -807,31 +922,23 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (!empty($this->relationWrite)) { $relation = []; foreach ($this->relationWrite as $key => $name) { - if (!is_numeric($key)) { - $relation[$key] = []; - foreach ($name as $val) { - if (isset($this->data[$val])) { - $relation[$key][$val] = $this->data[$val]; - unset($this->data[$val]); + if (is_array($name)) { + if (key($name) === 0) { + $relation[$key] = []; + foreach ($name as $val) { + if (isset($this->data[$val])) { + $relation[$key][$val] = $this->data[$val]; + unset($this->data[$val]); + } } + } else { + $relation[$key] = $name; } + } elseif (isset($this->relation[$name])) { + $relation[$name] = $this->relation[$name]; } elseif (isset($this->data[$name])) { $relation[$name] = $this->data[$name]; - if (!$this->isUpdate) { - unset($this->data[$name]); - } - } - } - } - - // 检测字段 - if (!empty($this->field)) { - if (true === $this->field) { - $this->field = $this->db(false)->getTableInfo('', 'fields'); - } - foreach ($this->data as $key => $val) { - if (!in_array($key, $this->field)) { - unset($this->data[$key]); + unset($this->data[$name]); } } } @@ -845,33 +952,25 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } $pk = $this->getPk(); if ($this->isUpdate) { + // 检测字段 + $this->checkAllowField($this->data, array_merge($this->auto, $this->update)); + // 自动更新 $this->autoCompleteData($this->update); + // 获取有更新的数据 + $data = $this->getChangedData(); + // 事件回调 if (false === $this->trigger('before_update', $this)) { return false; } - // 去除没有更新的字段 - $data = []; - foreach ($this->data as $key => $val) { - if (in_array($key, $this->change) || $this->isPk($key)) { - $data[$key] = $val; - } - } - - if (!empty($this->readonly)) { - // 只读字段不允许更新 - foreach ($this->readonly as $key => $field) { - if (isset($data[$field])) { - unset($data[$field]); - } - } - } - if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) { - // 没有更新 + // 关联更新 + if (isset($relation)) { + $this->autoRelationUpdate($relation); + } return 0; } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { // 自动写入更新时间 @@ -882,6 +981,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $where = $this->updateWhere; } + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + if (is_string($pk) && isset($data[$pk])) { if (!isset($where[$pk])) { unset($where); @@ -890,40 +996,24 @@ abstract class Model implements \JsonSerializable, \ArrayAccess unset($data[$pk]); } - // 关联更新 - if (isset($relation)) { - foreach ($relation as $name => $val) { - if (isset($data[$name])) { - unset($data[$name]); - } - } - } - // 模型更新 - $result = $this->db()->where($where)->update($data); + $result = $this->getQuery()->where($where)->update($data); // 关联更新 if (isset($relation)) { - foreach ($relation as $name => $val) { - if ($val instanceof Model) { - $val->save(); - } else { - unset($this->data[$name]); - $model = $this->getAttr($name); - if ($model instanceof Model) { - $model->save($val); - } - } - } + $this->autoRelationUpdate($relation); } - // 清空change - $this->change = []; // 更新回调 $this->trigger('after_update', $this); + } else { + // 检测字段 + $this->checkAllowField($this->data, array_merge($this->auto, $this->insert)); + // 自动写入 $this->autoCompleteData($this->insert); + // 自动写入创建时间和更新时间 if ($this->autoWriteTimestamp) { if ($this->createTime && !isset($this->data[$this->createTime])) { @@ -938,11 +1028,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return false; } - $result = $this->db()->insert($this->data); + $result = $this->getQuery()->insert($this->data); // 获取自动增长主键 if ($result && is_string($pk) && (!isset($this->data[$pk]) || '' == $this->data[$pk])) { - $insertId = $this->db()->getLastInsID($sequence); + $insertId = $this->getQuery()->getLastInsID($sequence); if ($insertId) { $this->data[$pk] = $insertId; } @@ -958,17 +1048,78 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 标记为更新 $this->isUpdate = true; - // 清空change - $this->change = []; + // 新增回调 $this->trigger('after_insert', $this); } // 写入回调 $this->trigger('after_write', $this); + // 重新记录原始数据 + $this->origin = $this->data; + return $result; } + protected function checkAllowField(&$data, $auto = []) + { + if (!empty($this->field)) { + if (true === $this->field) { + $this->field = $this->getQuery()->getTableInfo('', 'fields'); + $field = $this->field; + } else { + $field = array_merge($this->field, $auto); + } + + foreach ($data as $key => $val) { + if (!in_array($key, $field)) { + unset($data[$key]); + } + } + } + } + + protected function autoRelationUpdate($relation) + { + foreach ($relation as $name => $val) { + if ($val instanceof Model) { + $val->save(); + } else { + unset($this->data[$name]); + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->save($val); + } + } + } + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData() + { + $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) { + if ((empty($b) || empty($b)) && $a !== $b) { + return 1; + } + return is_object($a) || $a != $b ? 1 : 0; + }); + + if (!empty($this->readonly)) { + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + } + + return $data; + } + /** * 保存多个数据到当前数据对象 * @access public @@ -990,7 +1141,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } $result = []; - $db = $this->db(); + $db = $this->getQuery(); $db->startTrans(); try { $pk = $this->getPk(); @@ -1071,9 +1222,14 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $field = $value; $value = null; } - if (!in_array($field, $this->change)) { - $this->setAttr($field, !is_null($value) ? $value : (isset($this->data[$field]) ? $this->data[$field] : $value)); + + if (!isset($this->data[$field])) { + $default = null; + } else { + $default = $this->data[$field]; } + + $this->setAttr($field, !is_null($value) ? $value : $default); } } @@ -1099,7 +1255,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } // 删除当前模型数据 - $result = $this->db()->where($where)->delete(); + $result = $this->getQuery()->where($where)->delete(); // 关联删除 if (!empty($this->relationWrite)) { @@ -1113,6 +1269,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } $this->trigger('after_delete', $this); + // 清空原始数据 + $this->origin = []; + return $result; } @@ -1207,7 +1366,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 返回模型的错误信息 * @access public - * @return string + * @return string|array */ public function getError() { @@ -1294,11 +1453,15 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param mixed $data 主键值或者查询条件(闭包) * @param array|string $with 关联预查询 * @param bool $cache 是否缓存 - * @return static + * @return static|null * @throws exception\DbException */ - public static function get($data = null, $with = [], $cache = false) + public static function get($data, $with = [], $cache = false) { + if (is_null($data)) { + return; + } + if (true === $with || is_int($with)) { $cache = $with; $with = []; @@ -1366,7 +1529,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } elseif ($data instanceof \Closure) { call_user_func_array($data, [ & $query]); $data = null; - } elseif (is_null($data)) { + } elseif (empty($data) && 0 !== $data) { return 0; } $resultSet = $query->select($data); @@ -1385,16 +1548,14 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @access public * @param string|array|\Closure $name 命名范围名称 逗号分隔 * @internal mixed ...$params 参数调用 - * @return Model|Query + * @return Query */ public static function scope($name) { - if ($name instanceof Query) { - return $name; - } - $model = new static(); - $params = func_get_args(); - $params[0] = $model->db(); + $model = new static(); + $query = $model->db(); + $params = func_get_args(); + array_unshift($params, $query); if ($name instanceof \Closure) { call_user_func_array($name, $params); } elseif (is_string($name)) { @@ -1408,7 +1569,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } } } - return $model; + return $query; } /** @@ -1419,9 +1580,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public static function useGlobalScope($use) { - $model = new static(); - static::$db = $model->db($use); - return $model; + $model = new static(); + return $model->db($use); } /** @@ -1638,7 +1798,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $model = $this->parseModel($model); $foreignKey = $foreignKey ?: $this->getForeignKey($model); $localKey = $localKey ?: (new $model)->getPk(); - return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType); + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType, $relation); } /** @@ -1693,7 +1855,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 记录当前关联信息 $model = $this->parseModel($model); $name = Loader::parseName(basename(str_replace('\\', '/', $model))); - $table = $table ?: $this->db(false)->getTable(Loader::parseName($this->name) . '_' . $name); + $table = $table ?: $this->getQuery()->getTable(Loader::parseName($this->name) . '_' . $name); $foreignKey = $foreignKey ?: $name . '_id'; $localKey = $localKey ?: $this->getForeignKey($this->name); return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); @@ -1725,6 +1887,32 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return new MorphMany($this, $model, $foreignKey, $morphType, $type); } + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: Loader::parseName($this->name); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + /** * MORPH TO 关联定义 * @access public @@ -1734,9 +1922,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function morphTo($morph = null, $alias = []) { + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + if (is_null($morph)) { - $trace = debug_backtrace(false, 2); - $morph = Loader::parseName($trace[1]['function']); + $morph = $relation; } // 记录当前关联信息 if (is_array($morph)) { @@ -1745,17 +1935,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $morphType = $morph . '_type'; $foreignKey = $morph . '_id'; } - return new MorphTo($this, $morphType, $foreignKey, $alias); + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); } public function __call($method, $args) { - if (isset(static::$db)) { - $query = static::$db; - static::$db = null; - } else { - $query = $this->db(); - } + $query = $this->db(true, false); if (method_exists($this, 'scope' . $method)) { // 动态调用命名范围 @@ -1768,16 +1953,21 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } } - public static function __callStatic($method, $params) + public static function __callStatic($method, $args) { - if (isset(static::$db)) { - $query = static::$db; - static::$db = null; - } else { - $query = (new static())->db(); - } + $model = new static(); + $query = $model->db(); - return call_user_func_array([$query, $method], $params); + if (method_exists($model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $query); + + call_user_func_array([$model, $method], $args); + return $query; + } else { + return call_user_func_array([$query, $method], $args); + } } /** @@ -1812,7 +2002,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess public function __isset($name) { try { - if (array_key_exists($name, $this->data)) { + if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { return true; } else { $this->getAttr($name); @@ -1832,7 +2022,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function __unset($name) { - unset($this->data[$name]); + unset($this->data[$name], $this->relation[$name]); } public function __toString() diff --git a/core/library/think/Paginator.php b/core/library/think/Paginator.php index d3547f8b..5a8fa20e 100644 --- a/core/library/think/Paginator.php +++ b/core/library/think/Paginator.php @@ -273,6 +273,23 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J return $this->items->isEmpty(); } + /** + * 给每个元素执行个回调 + * + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + if ($callback($item, $key) === false) { + break; + } + } + + return $this; + } + /** * Retrieve an external iterator * @return Traversable An instance of an object implementing Iterator or @@ -349,6 +366,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J 'total' => $total, 'per_page' => $this->listRows(), 'current_page' => $this->currentPage(), + 'last_page' => $this->lastPage, 'data' => $this->items->toArray(), ]; } diff --git a/core/library/think/Request.php b/core/library/think/Request.php index d4f96a75..13089576 100644 --- a/core/library/think/Request.php +++ b/core/library/think/Request.php @@ -88,20 +88,18 @@ class Request * @var array 资源类型 */ protected $mimeType = [ - 'xml' => 'application/xml,text/xml,application/x-xml', - 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', - 'js' => 'text/javascript,application/javascript,application/x-javascript', - 'css' => 'text/css', - 'rss' => 'application/rss+xml', - 'yaml' => 'application/x-yaml,text/yaml', - 'atom' => 'application/atom+xml', - 'pdf' => 'application/pdf', - 'text' => 'text/plain', - 'png' => 'image/png', - 'jpg' => 'image/jpg,image/jpeg,image/pjpeg', - 'gif' => 'image/gif', - 'csv' => 'text/csv', - 'html' => 'text/html,application/xhtml+xml,*/*', + 'xml' => 'application/xml,text/xml,application/x-xml', + 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', + 'js' => 'text/javascript,application/javascript,application/x-javascript', + 'css' => 'text/css', + 'rss' => 'application/rss+xml', + 'yaml' => 'application/x-yaml,text/yaml', + 'atom' => 'application/atom+xml', + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + 'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*', + 'csv' => 'text/csv', + 'html' => 'text/html,application/xhtml+xml,*/*', ]; protected $content; @@ -134,6 +132,7 @@ class Request if (is_null($this->filter)) { $this->filter = Config::get('default_filter'); } + // 保存 php://input $this->input = file_get_contents('php://input'); } @@ -802,12 +801,26 @@ class Request public function cookie($name = '', $default = null, $filter = '') { if (empty($this->cookie)) { - $this->cookie = $_COOKIE; + $this->cookie = Cookie::get(); } if (is_array($name)) { return $this->cookie = array_merge($this->cookie, $name); + } elseif (!empty($name)) { + $data = Cookie::has($name) ? Cookie::get($name) : $default; + } else { + $data = $this->cookie; } - return $this->input($this->cookie, $name, $default, $filter); + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + return $data; } /** @@ -984,18 +997,8 @@ class Request } // 解析过滤器 - if (is_null($filter)) { - $filter = []; - } else { - $filter = $filter ?: $this->filter; - if (is_string($filter)) { - $filter = explode(',', $filter); - } else { - $filter = (array) $filter; - } - } + $filter = $this->getFilter($filter, $default); - $filter[] = $default; if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); reset($data); @@ -1024,6 +1027,23 @@ class Request } } + protected function getFilter($filter, $default) + { + if (is_null($filter)) { + $filter = []; + } else { + $filter = $filter ?: $this->filter; + if (is_string($filter) && false === strpos($filter, '/')) { + $filter = explode(',', $filter); + } else { + $filter = (array) $filter; + } + } + + $filter[] = $default; + return $filter; + } + /** * 递归过滤给定的值 * @param mixed $value 键值 @@ -1039,7 +1059,7 @@ class Request // 调用函数或者方法过滤 $value = call_user_func($filter, $value); } elseif (is_scalar($value)) { - if (strpos($filter, '/')) { + if (false !== strpos($filter, '/')) { // 正则过滤 if (!preg_match($filter, $value)) { // 匹配不成功返回默认值 @@ -1527,13 +1547,13 @@ class Request } } // 自动缓存功能 - $key = md5($this->host()) . '__URL__'; + $key = '__URL__'; } elseif (strpos($key, '|')) { list($key, $fun) = explode('|', $key); } // 特殊规则替换 if (false !== strpos($key, '__')) { - $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url())], $key); + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); } if (false !== strpos($key, ':')) { diff --git a/core/library/think/Response.php b/core/library/think/Response.php index d0345782..96737dcd 100644 --- a/core/library/think/Response.php +++ b/core/library/think/Response.php @@ -19,7 +19,6 @@ use think\response\Xml as XmlResponse; class Response { - // 原始数据 protected $data; @@ -50,12 +49,12 @@ class Response public function __construct($data = '', $code = 200, array $header = [], $options = []) { $this->data($data); - $this->header = $header; - $this->code = $code; if (!empty($options)) { $this->options = array_merge($this->options, $options); } $this->contentType($this->contentType, $this->charset); + $this->header = array_merge($this->header, $header); + $this->code = $code; } /** @@ -113,7 +112,11 @@ class Response http_response_code($this->code); // 发送头部信息 foreach ($this->header as $name => $val) { - header($name . ':' . $val); + if (is_null($val)) { + header($name); + } else { + header($name . ':' . $val); + } } } diff --git a/core/library/think/Route.php b/core/library/think/Route.php index 0fa95d97..a5b19327 100644 --- a/core/library/think/Route.php +++ b/core/library/think/Route.php @@ -842,7 +842,7 @@ class Route } $method = strtolower($request->method()); // 获取当前请求类型的路由规则 - $rules = self::$rules[$method]; + $rules = isset(self::$rules[$method]) ? self::$rules[$method] : []; // 检测域名部署 if ($checkDomain) { self::checkDomain($request, $rules, $method); @@ -924,7 +924,7 @@ class Route } else { $str = $key; } - if (is_string($str) && $str && 0 !== strpos(str_replace('|', '/', $url), $str)) { + if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) { continue; } self::setOption($option); @@ -1184,9 +1184,9 @@ class Route } } $pattern = array_merge(self::$rules['pattern'], $pattern); - if (false !== $match = self::match($url, $rule, $pattern, $merge)) { + if (false !== $match = self::match($url, $rule, $pattern)) { // 匹配到路由规则 - return self::parseRule($rule, $route, $url, $option, $match, $merge); + return self::parseRule($rule, $route, $url, $option, $match); } } return false; diff --git a/core/library/think/Session.php b/core/library/think/Session.php index 35726220..d5a6a5a5 100644 --- a/core/library/think/Session.php +++ b/core/library/think/Session.php @@ -56,7 +56,7 @@ class Session $isDoStart = true; } - if (isset($config['prefix'])) { + if (isset($config['prefix']) && (self::$prefix === '' || self::$prefix === null)) { self::$prefix = $config['prefix']; } if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { diff --git a/core/library/think/Url.php b/core/library/think/Url.php index d6e57037..24a2b4e3 100644 --- a/core/library/think/Url.php +++ b/core/library/think/Url.php @@ -235,7 +235,7 @@ class Url $rootDomain = Config::get('url_domain_root'); if (true === $domain) { // 自动判断域名 - $domain = $request->host(); + $domain = Config::get('default_host') ?: $request->host(); $domains = Route::rules('domain'); if ($domains) { @@ -265,7 +265,7 @@ class Url } else { if (empty($rootDomain)) { - $host = $request->host(); + $host = Config::get('default_host') ?: $request->host(); $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; } if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) { @@ -297,7 +297,7 @@ class Url } foreach ($pattern as $key => $val) { if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $vars[$key], $url); + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], urlencode($vars[$key]), $url); unset($vars[$key]); $result = [$url, $domain, $suffix]; } elseif (2 == $val) { diff --git a/core/library/think/Validate.php b/core/library/think/Validate.php index 718ed7a1..69384667 100644 --- a/core/library/think/Validate.php +++ b/core/library/think/Validate.php @@ -456,11 +456,13 @@ class Validate * @access protected * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function egt($value, $rule) + protected function egt($value, $rule, $data) { - return $value >= $rule; + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value >= $val; } /** @@ -468,11 +470,13 @@ class Validate * @access protected * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function gt($value, $rule) + protected function gt($value, $rule, $data) { - return $value > $rule; + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value > $val; } /** @@ -480,11 +484,13 @@ class Validate * @access protected * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function elt($value, $rule) + protected function elt($value, $rule, $data) { - return $value <= $rule; + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value <= $val; } /** @@ -492,11 +498,13 @@ class Validate * @access protected * @param mixed $value 字段值 * @param mixed $rule 验证规则 + * @param array $data 数据 * @return bool */ - protected function lt($value, $rule) + protected function lt($value, $rule, $data) { - return $value < $rule; + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value < $val; } /** @@ -1180,13 +1188,15 @@ class Validate /** * 获取数据值 * @access protected - * @param array $data 数据 - * @param string $key 数据标识 支持二维 + * @param array $data 数据 + * @param string $key 数据标识 支持二维 * @return mixed */ protected function getDataValue($data, $key) { - if (strpos($key, '.')) { + if (is_numeric($key)) { + $value = $key; + } elseif (strpos($key, '.')) { // 支持二维数组验证 list($name1, $name2) = explode('.', $key); $value = isset($data[$name1][$name2]) ? $data[$name1][$name2] : null; diff --git a/core/library/think/cache/driver/Memcache.php b/core/library/think/cache/driver/Memcache.php index 1fbd3b08..e464f38c 100644 --- a/core/library/think/cache/driver/Memcache.php +++ b/core/library/think/cache/driver/Memcache.php @@ -113,7 +113,10 @@ class Memcache extends Driver public function inc($name, $step = 1) { $key = $this->getCacheKey($name); - return $this->handler->increment($key, $step); + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + return $this->handler->set($key, $step); } /** diff --git a/core/library/think/cache/driver/Memcached.php b/core/library/think/cache/driver/Memcached.php index fa312e8e..4a9d898c 100644 --- a/core/library/think/cache/driver/Memcached.php +++ b/core/library/think/cache/driver/Memcached.php @@ -125,7 +125,10 @@ class Memcached extends Driver public function inc($name, $step = 1) { $key = $this->getCacheKey($name); - return $this->handler->increment($key, $step); + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + return $this->handler->set($key, $step); } /** diff --git a/core/library/think/db/Builder.php b/core/library/think/db/Builder.php index 414672d1..ecec0b0e 100644 --- a/core/library/think/db/Builder.php +++ b/core/library/think/db/Builder.php @@ -27,7 +27,7 @@ abstract class Builder // SQL表达式 protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%LOCK%%COMMENT%'; protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; - protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; @@ -88,7 +88,7 @@ abstract class Builder } // 获取绑定信息 - $bind = $this->query->getFieldsBind($options); + $bind = $this->query->getFieldsBind($options['table']); if ('*' == $options['field']) { $fields = array_keys($bind); } else { @@ -222,6 +222,14 @@ abstract class Builder protected function parseWhere($where, $options) { $whereStr = $this->buildWhere($where, $options); + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + list($field, $condition) = $options['soft_delete']; + + $binds = $this->query->getFieldsBind($options['table']); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($field, $condition, '', $options, $binds); + } return empty($whereStr) ? '' : ' WHERE ' . $whereStr; } @@ -243,7 +251,7 @@ abstract class Builder } $whereStr = ''; - $binds = $this->query->getFieldsBind($options); + $binds = $this->query->getFieldsBind($options['table']); foreach ($where as $key => $val) { $str = []; foreach ($val as $field => $value) { @@ -280,13 +288,7 @@ 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; } @@ -346,9 +348,14 @@ abstract class Builder $whereStr = ''; if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { - // 比较运算 及 模糊匹配 - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + // 比较运算 + if ($value instanceof \Closure) { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { + // 模糊匹配 if (is_array($value)) { foreach ($value as $item) { $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); @@ -712,13 +719,14 @@ abstract class Builder * @access public * @param array $dataSet 数据集 * @param array $options 表达式 + * @param bool $replace 是否replace * @return string */ - public function insertAll($dataSet, $options) + public function insertAll($dataSet, $options, $replace = false) { // 获取合法的字段 if ('*' == $options['field']) { - $fields = array_keys($this->query->getFieldsType($options)); + $fields = array_keys($this->query->getFieldsType($options['table'])); } else { $fields = $options['field']; } @@ -747,8 +755,9 @@ abstract class Builder } $fields = array_map([$this, 'parseKey'], array_keys(reset($dataSet))); $sql = str_replace( - ['%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], [ + $replace ? 'REPLACE' : 'INSERT', $this->parseTable($options['table'], $options), implode(' , ', $fields), implode(' UNION ALL ', $values), diff --git a/core/library/think/db/Connection.php b/core/library/think/db/Connection.php index bc67b1d8..fa773e51 100644 --- a/core/library/think/db/Connection.php +++ b/core/library/think/db/Connection.php @@ -56,8 +56,6 @@ abstract class Connection protected $attrCase = PDO::CASE_LOWER; // 监听回调 protected static $event = []; - // 查询对象 - protected $query = []; // 使用Builder类 protected $builder; // 数据库连接参数配置 @@ -137,19 +135,14 @@ abstract class Connection } /** - * 创建指定模型的查询对象 - * @access public - * @param string $model 模型类名称 - * @param string $queryClass 查询对象类名 + * 获取新的查询对象 + * @access protected * @return Query */ - public function getQuery($model = 'db', $queryClass = '') + protected function getQuery() { - if (!isset($this->query[$model])) { - $class = $queryClass ?: $this->config['query']; - $this->query[$model] = new $class($this, 'db' == $model ? '' : $model); - } - return $this->query[$model]; + $class = $this->config['query']; + return new $class($this); } /** @@ -340,13 +333,9 @@ abstract class Connection /** * 执行查询 返回数据集 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 - * @param bool $master 是否在主服务器读操作 - * @param bool $class 是否返回PDO对象 * @param string $sql sql指令 * @param array $bind 参数绑定 - * @param boolean $master 是否在主服务器读操作 + * @param bool $master 是否在主服务器读操作 * @param bool $pdo 是否返回PDO对象 * @return mixed * @throws BindParamException @@ -393,10 +382,15 @@ abstract class Connection // 返回结果集 return $this->getResult($pdo, $procedure); } catch (\PDOException $e) { - if ($this->config['break_reconnect'] && $this->isBreak($e)) { + if ($this->isBreak($e)) { return $this->close()->query($sql, $bind, $master, $pdo); } throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\ErrorException $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw $e; } } @@ -451,10 +445,15 @@ abstract class Connection $this->numRows = $this->PDOStatement->rowCount(); return $this->numRows; } catch (\PDOException $e) { - if ($this->config['break_reconnect'] && $this->isBreak($e)) { + if ($this->isBreak($e)) { return $this->close()->execute($sql, $bind); } throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\ErrorException $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind); + } + throw $e; } } @@ -629,13 +628,25 @@ abstract class Connection } ++$this->transTimes; + try { + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } - if (1 == $this->transTimes) { - $this->linkID->beginTransaction(); - } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { - $this->linkID->exec( - $this->parseSavepoint('trans' . $this->transTimes) - ); + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->startTrans(); + } + throw $e; + } catch (\ErrorException $e) { + if ($this->isBreak($e)) { + return $this->close()->startTrans(); + } + throw $e; } } @@ -771,11 +782,35 @@ abstract class Connection /** * 是否断线 * @access protected - * @param \PDOException $e 异常 + * @param \PDOException $e 异常对象 * @return bool */ protected function isBreak($e) { + if (!$this->config['break_reconnect']) { + return false; + } + + $info = [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + ]; + + $error = $e->getMessage(); + + foreach ($info as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } return false; } @@ -860,7 +895,6 @@ abstract class Connection Debug::remark('queryEndTime', 'time'); $runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); $sql = $sql ?: $this->getLastsql(); - $log = $sql . ' [ RunTime:' . $runtime . 's ]'; $result = []; // SQL性能分析 if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { @@ -918,7 +952,7 @@ abstract class Connection { if (!empty($this->config['deploy'])) { // 采用分布式数据库 - if ($master) { + if ($master || $this->transTimes) { if (!$this->linkWrite) { $this->linkWrite = $this->multiConnect(true); } diff --git a/core/library/think/db/Query.php b/core/library/think/db/Query.php index 1534bf72..b08436cd 100644 --- a/core/library/think/db/Query.php +++ b/core/library/think/db/Query.php @@ -401,7 +401,7 @@ class Query if (empty($this->options['table'])) { $this->options['table'] = $this->getTable(); } - $key = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options)); + $key = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options) . serialize($this->bind)); $result = Cache::get($key); } if (false === $result) { @@ -444,14 +444,16 @@ class Query if (empty($this->options['table'])) { $this->options['table'] = $this->getTable(); } - $guid = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options)); + $guid = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options) . serialize($this->bind)); $result = Cache::get($guid); } if (false === $result) { if (isset($this->options['field'])) { unset($this->options['field']); } - if ($key && '*' != $field) { + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { $field = $key . ',' . $field; } $pdo = $this->field($field)->getPdo(); @@ -594,7 +596,7 @@ class Query } if ($lazyTime > 0) { // 延迟写入 - $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); $step = $this->lazyWrite('inc', $guid, $step, $lazyTime); if (false === $step) { // 清空查询条件 @@ -623,7 +625,7 @@ class Query } if ($lazyTime > 0) { // 延迟写入 - $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); if (false === $step) { // 清空查询条件 @@ -649,16 +651,16 @@ class Query if (!Cache::has($guid . '_time')) { // 计时开始 Cache::set($guid . '_time', $_SERVER['REQUEST_TIME'], 0); - Cache::$type($guid, $step, 0); + Cache::$type($guid, $step); } elseif ($_SERVER['REQUEST_TIME'] > Cache::get($guid . '_time') + $lazyTime) { // 删除缓存 - $value = Cache::$type($guid, $step, 0); + $value = Cache::$type($guid, $step); Cache::rm($guid); Cache::rm($guid . '_time'); return 0 === $value ? false : $value; } else { // 更新缓存 - Cache::$type($guid, $step, 0); + Cache::$type($guid, $step); } return false; } @@ -1132,6 +1134,7 @@ class Query if ($field) { $this->options['soft_delete'] = [$field, $condition ?: ['null', '']]; } + return $this; } /** @@ -1773,21 +1776,21 @@ class Query } // 获取当前数据表字段信息 - public function getTableFields($options) + public function getTableFields($table = '') { - return $this->getTableInfo($options['table'], 'fields'); + return $this->getTableInfo($table ?: $this->getOptions('table'), 'fields'); } // 获取当前数据表字段类型 - public function getFieldsType($options) + public function getFieldsType($table = '') { - return $this->getTableInfo($options['table'], 'type'); + return $this->getTableInfo($table ?: $this->getOptions('table'), 'type'); } // 获取当前数据表绑定信息 - public function getFieldsBind($options) + public function getFieldsBind($table = '') { - $types = $this->getFieldsType($options); + $types = $this->getFieldsType($table); $bind = []; if ($types) { foreach ($types as $key => $type) { @@ -1900,6 +1903,9 @@ class Query $closure = $relation; $relation = $key; $with[$key] = $key; + } elseif (is_array($relation)) { + $subRelation = $relation; + $relation = $key; } elseif (is_string($relation) && strpos($relation, '.')) { $with[$key] = $relation; list($relation, $subRelation) = explode('.', $relation, 2); @@ -1909,7 +1915,7 @@ class Query $relation = Loader::parseName($relation, 1, false); $model = $class->$relation(); if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { - $model->removeOption()->eagerly($this, $relation, $subRelation, $closure, $first); + $model->eagerly($this, $relation, $subRelation, $closure, $first); $first = false; } elseif ($closure) { $with[$key] = $closure; @@ -1997,7 +2003,7 @@ class Query $relation = explode(',', $relation); } if (isset($this->options['relation'])) { - $this->options['relation'] = array_mrege($this->options['relation'], $relation); + $this->options['relation'] = array_merge($this->options['relation'], $relation); } else { $this->options['relation'] = $relation; } @@ -2111,9 +2117,10 @@ class Query * 批量插入记录 * @access public * @param mixed $dataSet 数据集 + * @param boolean $replace 是否replace * @return integer|string */ - public function insertAll(array $dataSet) + public function insertAll(array $dataSet, $replace = false) { // 分析查询表达式 $options = $this->parseExpress(); @@ -2121,7 +2128,7 @@ class Query return false; } // 生成SQL语句 - $sql = $this->builder->insertAll($dataSet, $options); + $sql = $this->builder->insertAll($dataSet, $options, $replace); // 获取参数绑定 $bind = $this->getBind(); if ($options['fetch_sql']) { @@ -2203,7 +2210,7 @@ class Query $options['where']['AND'] = $where; } } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options); + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); } // 生成UPDATE SQL语句 @@ -2291,7 +2298,7 @@ class Query // 判断查询缓存 $cache = $options['cache']; unset($options['cache']); - $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options)); + $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options) . serialize($this->bind)); $resultSet = Cache::get($key); } if (!$resultSet) { @@ -2383,8 +2390,9 @@ class Query * @access public * @param mixed $value 缓存数据 * @param array $options 缓存参数 + * @param array $bind 绑定参数 */ - protected function getCacheKey($value, $options) + protected function getCacheKey($value, $options, $bind = []) { if (is_scalar($value)) { $data = $value; @@ -2392,9 +2400,9 @@ class Query $data = $value[1]; } if (isset($data)) { - return 'think:' . $options['table'] . '|' . $data; + return 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; } else { - return md5(serialize($options)); + return md5(serialize($options) . serialize($bind)); } } @@ -2422,7 +2430,7 @@ class Query // AR模式分析主键条件 $this->parsePkWhere($data, $options); } elseif (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options); + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); } $options['limit'] = 1; @@ -2435,7 +2443,7 @@ class Query } elseif (is_string($cache['key'])) { $key = $cache['key']; } elseif (!isset($key)) { - $key = md5(serialize($options)); + $key = md5(serialize($options) . serialize($this->bind)); } $result = Cache::get($key); } @@ -2646,7 +2654,7 @@ class Query // AR模式分析主键条件 $this->parsePkWhere($data, $options); } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options); + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); } if (true !== $data && empty($options['where'])) { diff --git a/core/library/think/db/builder/Mysql.php b/core/library/think/db/builder/Mysql.php index 4aac4424..5bc9d03b 100644 --- a/core/library/think/db/builder/Mysql.php +++ b/core/library/think/db/builder/Mysql.php @@ -47,6 +47,9 @@ class Mysql extends Builder $key = '`' . $key . '`'; } if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } $key = '`' . $table . '`.' . $key; } return $key; diff --git a/core/library/think/db/builder/Sqlsrv.php b/core/library/think/db/builder/Sqlsrv.php index 2e13d3fb..52629e73 100644 --- a/core/library/think/db/builder/Sqlsrv.php +++ b/core/library/think/db/builder/Sqlsrv.php @@ -21,7 +21,7 @@ class Sqlsrv extends Builder protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; - protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; /** * order分析 diff --git a/core/library/think/db/connector/Mysql.php b/core/library/think/db/connector/Mysql.php index 0081fb2e..c38181df 100644 --- a/core/library/think/db/connector/Mysql.php +++ b/core/library/think/db/connector/Mysql.php @@ -51,7 +51,7 @@ class Mysql extends Connection */ public function getFields($tableName) { - $this->initConnect(true); + $this->initConnect(false); list($tableName) = explode(' ', $tableName); if (false === strpos($tableName, '`')) { if (strpos($tableName, '.')) { @@ -91,7 +91,7 @@ class Mysql extends Connection */ public function getTables($dbName = '') { - $this->initConnect(true); + $this->initConnect(false); $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; // 调试开始 $this->debug(true); @@ -130,17 +130,4 @@ class Mysql extends Connection return true; } - /** - * 是否断线 - * @access protected - * @param \PDOException $e 异常对象 - * @return bool - */ - protected function isBreak($e) - { - if (false !== stripos($e->getMessage(), 'server has gone away')) { - return true; - } - return false; - } } diff --git a/core/library/think/db/connector/Pgsql.php b/core/library/think/db/connector/Pgsql.php index 04c3e782..3ad78079 100644 --- a/core/library/think/db/connector/Pgsql.php +++ b/core/library/think/db/connector/Pgsql.php @@ -44,7 +44,7 @@ class Pgsql extends Connection */ public function getFields($tableName) { - $this->initConnect(true); + $this->initConnect(false); list($tableName) = explode(' ', $tableName); $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; // 调试开始 @@ -78,7 +78,7 @@ class Pgsql extends Connection */ public function getTables($dbName = '') { - $this->initConnect(true); + $this->initConnect(false); $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; // 调试开始 $this->debug(true); diff --git a/core/library/think/db/connector/Sqlite.php b/core/library/think/db/connector/Sqlite.php index a0e0873c..1de177a1 100644 --- a/core/library/think/db/connector/Sqlite.php +++ b/core/library/think/db/connector/Sqlite.php @@ -42,7 +42,7 @@ class Sqlite extends Connection */ public function getFields($tableName) { - $this->initConnect(true); + $this->initConnect(false); list($tableName) = explode(' ', $tableName); $sql = 'PRAGMA table_info( ' . $tableName . ' )'; // 调试开始 @@ -76,7 +76,7 @@ class Sqlite extends Connection */ public function getTables($dbName = '') { - $this->initConnect(true); + $this->initConnect(false); $sql = "SELECT name FROM sqlite_master WHERE type='table' " . "UNION ALL SELECT name FROM sqlite_temp_master " . "WHERE type='table' ORDER BY name"; diff --git a/core/library/think/db/connector/Sqlsrv.php b/core/library/think/db/connector/Sqlsrv.php index 20d3491d..e9ee985b 100644 --- a/core/library/think/db/connector/Sqlsrv.php +++ b/core/library/think/db/connector/Sqlsrv.php @@ -49,7 +49,7 @@ class Sqlsrv extends Connection */ public function getFields($tableName) { - $this->initConnect(true); + $this->initConnect(false); list($tableName) = explode(' ', $tableName); $sql = "SELECT column_name, data_type, column_default, is_nullable FROM information_schema.tables AS t @@ -99,7 +99,7 @@ class Sqlsrv extends Connection */ public function getTables($dbName = '') { - $this->initConnect(true); + $this->initConnect(false); $sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' diff --git a/core/library/think/log/driver/File.php b/core/library/think/log/driver/File.php index fdf72658..d82a5243 100644 --- a/core/library/think/log/driver/File.php +++ b/core/library/think/log/driver/File.php @@ -76,7 +76,7 @@ class File { //检测日志文件大小,超过配置大小则备份日志文件重新生成 if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { - rename($destination, dirname($destination) . DS . $_SERVER['REQUEST_TIME'] . '-' . basename($destination)); + rename($destination, dirname($destination) . DS . time() . '-' . basename($destination)); $this->writed[$destination] = false; } diff --git a/core/library/think/model/Collection.php b/core/library/think/model/Collection.php index 08e11ad8..4e4bb4dc 100644 --- a/core/library/think/model/Collection.php +++ b/core/library/think/model/Collection.php @@ -16,6 +16,20 @@ use think\Model; class Collection extends BaseCollection { + /** + * 返回数组中指定的一列 + * @param string $column_key + * @param string|null $index_key + * @return array + */ + public function column($column_key, $index_key = null) + { + if (function_exists('array_column')) { + return array_column($this->toArray(), $column_key, $index_key); + } + return parent::column($column_key, $index_key); + } + /** * 延迟预载入关联查询 * @access public diff --git a/core/library/think/model/Merge.php b/core/library/think/model/Merge.php index 01f403f8..d944979e 100644 --- a/core/library/think/model/Merge.php +++ b/core/library/think/model/Merge.php @@ -11,6 +11,7 @@ namespace think\model; +use think\Db; use think\db\Query; use think\Model; @@ -120,22 +121,19 @@ class Merge extends Model * @access public * @param string $model 模型名称 * @param array $data 数据 - * @param bool $insert 是否新增 * @return array */ - protected function parseData($model, $data, $insert = false) + protected function parseData($model, $data) { $item = []; foreach ($data as $key => $val) { - if ($insert || in_array($key, $this->change) || $this->isPk($key)) { - if ($this->fk != $key && array_key_exists($key, $this->mapFields)) { - list($name, $key) = explode('.', $this->mapFields[$key]); - if ($model == $name) { - $item[$key] = $val; - } - } else { + if ($this->fk != $key && array_key_exists($key, $this->mapFields)) { + list($name, $key) = explode('.', $this->mapFields[$key]); + if ($model == $name) { $item[$key] = $val; } + } else { + $item[$key] = $val; } } return $item; @@ -174,6 +172,11 @@ class Merge extends Model $this->setAttr($this->updateTime, null); } + // 事件回调 + if (false === $this->trigger('before_write', $this)) { + return false; + } + $db = $this->db(); $db->startTrans(); $pk = $this->getPk(); @@ -190,8 +193,16 @@ class Merge extends Model $where = $this->updateWhere; } + // 获取有更新的数据 + $data = $this->getChangedData(); + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } // 处理模型数据 - $data = $this->parseData($this->name, $this->data); + $data = $this->parseData($this->name, $data); if (is_string($pk) && isset($data[$pk])) { if (!isset($where[$pk])) { unset($where); @@ -207,14 +218,12 @@ class Merge extends Model $name = is_int($key) ? $model : $key; $table = is_int($key) ? $db->getTable($model) : $model; // 处理关联模型数据 - $data = $this->parseData($name, $this->data); - $query = new Query; - if ($query->table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { + $data = $this->parseData($name, $data); + if (Db::table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { $result = 1; } } - // 清空change - $this->change = []; + // 新增回调 $this->trigger('after_update', $this); } else { @@ -231,7 +240,7 @@ class Merge extends Model } // 处理模型数据 - $data = $this->parseData($this->name, $this->data, true); + $data = $this->parseData($this->name, $this->data); // 写入主表数据 $result = $db->name($this->name)->strict(false)->insert($data); if ($result) { @@ -240,9 +249,6 @@ class Merge extends Model if ($insertId) { if (is_string($pk)) { $this->data[$pk] = $insertId; - if ($this->fk == $pk) { - $this->change[] = $pk; - } } $this->data[$this->fk] = $insertId; } @@ -256,19 +262,20 @@ class Merge extends Model $name = is_int($key) ? $model : $key; $table = is_int($key) ? $db->getTable($model) : $model; // 处理关联模型数据 - $data = $this->parseData($name, $source, true); - $query = new Query; - $query->table($table)->strict(false)->insert($data); + $data = $this->parseData($name, $source); + Db::table($table)->strict(false)->insert($data); } } // 标记为更新 $this->isUpdate = true; - // 清空change - $this->change = []; // 新增回调 $this->trigger('after_insert', $this); } $db->commit(); + // 写入回调 + $this->trigger('after_write', $this); + + $this->origin = $this->data; return $result; } catch (\Exception $e) { $db->rollback(); diff --git a/core/library/think/model/Pivot.php b/core/library/think/model/Pivot.php index 423921d7..06c6b181 100644 --- a/core/library/think/model/Pivot.php +++ b/core/library/think/model/Pivot.php @@ -19,24 +19,26 @@ class Pivot extends Model /** @var Model */ public $parent; + protected $autoWriteTimestamp = false; + /** - * 构造函数 + * 架构函数 * @access public - * @param Model $parent - * @param array|object $data 数据 - * @param string $table 中间数据表名 + * @param Model $parent 上级模型 + * @param array|object $data 数据 + * @param string $table 中间数据表名 */ public function __construct(Model $parent, $data = [], $table = '') { - if (is_object($data)) { - $this->data = get_object_vars($data); - } else { - $this->data = $data; - } - $this->parent = $parent; - $this->table = $table; + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + + $this->class = $this->name; } } diff --git a/core/library/think/model/Relation.php b/core/library/think/model/Relation.php index ed7e5603..4869e526 100644 --- a/core/library/think/model/Relation.php +++ b/core/library/think/model/Relation.php @@ -33,8 +33,6 @@ abstract class Relation protected $foreignKey; // 关联表主键 protected $localKey; - // 关联查询参数 - protected $option; // 基础查询 protected $baseQuery; @@ -79,15 +77,30 @@ abstract class Relation return (new $this->model)->toCollection($resultSet); } - /** - * 移除关联查询参数 - * @access public - * @return $this - */ - public function removeOption() + protected function getQueryFields($model) { - $this->query->removeOption(); - return $this; + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, $model) + { + if ($fields) { + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + } else { + $fields = $model . '.*'; + } + + return $fields; } /** @@ -105,10 +118,8 @@ abstract class Relation $result = call_user_func_array([$this->query, $method], $args); if ($result instanceof Query) { - $this->option = $result->getOptions(); return $this; } else { - $this->option = []; $this->baseQuery = false; return $result; } diff --git a/core/library/think/model/relation/BelongsTo.php b/core/library/think/model/relation/BelongsTo.php index 6996526d..905ea944 100644 --- a/core/library/think/model/relation/BelongsTo.php +++ b/core/library/think/model/relation/BelongsTo.php @@ -24,8 +24,9 @@ class BelongsTo extends OneToOne * @param string $foreignKey 关联外键 * @param string $localKey 关联主键 * @param string $joinType JOIN类型 + * @param string $relation 关联名 */ - public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER', $relation = null) { $this->parent = $parent; $this->model = $model; @@ -33,6 +34,7 @@ class BelongsTo extends OneToOne $this->localKey = $localKey; $this->joinType = $joinType; $this->query = (new $model)->db(); + $this->relation = $relation; } /** @@ -48,7 +50,16 @@ class BelongsTo extends OneToOne if ($closure) { call_user_func_array($closure, [ & $this->query]); } - return $this->query->where($this->localKey, $this->parent->$foreignKey)->relation($subRelation)->find(); + $relationModel = $this->query + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; } /** @@ -128,6 +139,8 @@ class BelongsTo extends OneToOne $relationModel = null; } else { $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); } if ($relationModel && !empty($this->bindAttr)) { @@ -135,7 +148,7 @@ class BelongsTo extends OneToOne $this->bindAttr($relationModel, $result, $this->bindAttr); } // 设置关联属性 - $result->setAttr($attr, $relationModel); + $result->setRelation($attr, $relationModel); } } } @@ -159,13 +172,46 @@ class BelongsTo extends OneToOne $relationModel = null; } else { $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); } if ($relationModel && !empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); } // 设置关联属性 - $result->setAttr(Loader::parseName($relation), $relationModel); + $result->setRelation(Loader::parseName($relation), $relationModel); } + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @return Model + */ + public function associate($model) + { + $foreignKey = $this->foreignKey; + $pk = $model->getPk(); + + $this->parent->setAttr($foreignKey, $model->$pk); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $foreignKey = $this->foreignKey; + + $this->parent->setAttr($foreignKey, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } } diff --git a/core/library/think/model/relation/BelongsToMany.php b/core/library/think/model/relation/BelongsToMany.php index 34830e05..9eeb88dd 100644 --- a/core/library/think/model/relation/BelongsToMany.php +++ b/core/library/think/model/relation/BelongsToMany.php @@ -24,8 +24,9 @@ class BelongsToMany extends Relation { // 中间表表名 protected $middle; - - // 中间表模型 + // 中间表模型名称 + protected $pivotName; + // 中间表模型对象 protected $pivot; /** @@ -43,8 +44,14 @@ class BelongsToMany extends Relation $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; - $this->middle = $table; - $this->query = (new $model)->db(); + if (false !== strpos($table, '\\')) { + $this->pivotName = $table; + $this->middle = basename(str_replace('\\', '/', $table)); + } else { + $this->middle = $table; + } + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); } /** @@ -54,7 +61,7 @@ class BelongsToMany extends Relation */ public function pivot($pivot) { - $this->pivot = $pivot; + $this->pivotName = $pivot; return $this; } @@ -63,9 +70,9 @@ class BelongsToMany extends Relation * @param $data * @return mixed */ - protected function newPivot($data) + protected function newPivot($data = []) { - $pivot = $this->pivot ?: '\\think\\model\\Pivot'; + $pivot = $this->pivotName ?: '\\think\\model\\Pivot'; return new $pivot($this->parent, $data, $this->middle); } @@ -102,7 +109,7 @@ class BelongsToMany extends Relation // 关联查询 $pk = $this->parent->getPk(); $condition['pivot.' . $localKey] = $this->parent->$pk; - return $this->belongsToManyQuery($middle, $foreignKey, $localKey, $condition); + return $this->belongsToManyQuery($foreignKey, $localKey, $condition); } /** @@ -114,7 +121,7 @@ class BelongsToMany extends Relation public function getRelation($subRelation = '', $closure = null) { if ($closure) { - call_user_func_array($closure, [& $this->query]); + call_user_func_array($closure, [ & $this->query]); } $result = $this->buildQuery()->relation($subRelation)->select(); $this->hydratePivot($result); @@ -260,7 +267,7 @@ class BelongsToMany extends Relation $data[$result->$pk] = []; } - $result->setAttr($attr, $this->resultSetBuild($data[$result->$pk])); + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); } } } @@ -286,7 +293,7 @@ class BelongsToMany extends Relation if (!isset($data[$pk])) { $data[$pk] = []; } - $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); } } @@ -303,7 +310,7 @@ class BelongsToMany extends Relation $count = 0; if (isset($result->$pk)) { $pk = $result->$pk; - $count = $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); + $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); } return $count; } @@ -316,7 +323,7 @@ class BelongsToMany extends Relation */ public function getRelationCountQuery($closure) { - return $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, [ + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ 'pivot.' . $this->localKey => [ 'exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), @@ -335,7 +342,7 @@ class BelongsToMany extends Relation protected function eagerlyManyToMany($where, $relation, $subRelation = '') { // 预载入关联查询 支持嵌套预载入 - $list = $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); // 组装模型数据 $data = []; @@ -359,21 +366,23 @@ class BelongsToMany extends Relation /** * BELONGS TO MANY 关联查询 * @access public - * @param string $table 中间表名 * @param string $foreignKey 关联模型关联键 * @param string $localKey 当前模型关联键 * @param array $condition 关联查询条件 * @return Query */ - protected function belongsToManyQuery($table, $foreignKey, $localKey, $condition = []) + protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) { // 关联查询封装 - $tableName = $this->query->getTable(); - $relationFk = $this->query->getPk(); - $query = $this->query->field($tableName . '.*') + $tableName = $this->query->getTable(); + $table = $this->pivot->getTable(); + $fields = $this->getQueryFields($tableName); + + $query = $this->query->field($fields) ->field(true, false, $table, 'pivot', 'pivot__'); if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); $query->join($table . ' pivot', 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) ->where($condition); } @@ -450,7 +459,7 @@ class BelongsToMany extends Relation $ids = (array) $id; foreach ($ids as $id) { $pivot[$this->foreignKey] = $id; - $this->query->table($this->middle)->insert($pivot, true); + $this->pivot->insert($pivot, true); $result[] = $this->newPivot($pivot); } if (count($result) == 1) { @@ -488,8 +497,7 @@ class BelongsToMany extends Relation if (isset($id)) { $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; } - $this->query->table($this->middle)->where($pivot)->delete(); - + $this->pivot->where($pivot)->delete(); // 删除关联表数据 if (isset($id) && $relationDel) { $model = $this->model; @@ -497,6 +505,55 @@ class BelongsToMany extends Relation } } + /** + * 数据同步 + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + $pk = $this->parent->getPk(); + $current = $this->pivot->where($this->localKey, $this->parent->$pk) + ->column($this->foreignKey); + $records = []; + + foreach ($ids as $key => $value) { + if (!is_array($value)) { + $records[$value] = []; + } else { + $records[$key] = $value; + } + } + + $detach = array_diff($current, array_keys($records)); + + if ($detaching && count($detach) > 0) { + $this->detach($detach); + + $changes['detached'] = $detach; + } + + foreach ($records as $id => $attributes) { + if (!in_array($id, $current)) { + $this->attach($id, $attributes); + $changes['attached'][] = $id; + } elseif (count($attributes) > 0 && + $this->attach($id, $attributes) + ) { + $changes['updated'][] = $id; + } + } + + return $changes; + + } + /** * 执行基础查询(进执行一次) * @access protected @@ -504,9 +561,10 @@ class BelongsToMany extends Relation */ protected function baseQuery() { - if (empty($this->baseQuery)) { - $pk = $this->parent->getPk(); - $this->query->join($this->middle . ' pivot', 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $table = $this->pivot->getTable(); + $this->query->join($table . ' pivot', 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); $this->baseQuery = true; } } diff --git a/core/library/think/model/relation/HasMany.php b/core/library/think/model/relation/HasMany.php index 4c045494..9624fc67 100644 --- a/core/library/think/model/relation/HasMany.php +++ b/core/library/think/model/relation/HasMany.php @@ -11,7 +11,6 @@ namespace think\model\relation; -use think\Db; use think\db\Query; use think\Loader; use think\Model; @@ -47,7 +46,14 @@ class HasMany extends Relation if ($closure) { call_user_func_array($closure, [ & $this->query]); } - return $this->relation($subRelation)->select(); + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; } /** @@ -84,7 +90,12 @@ class HasMany extends Relation if (!isset($data[$result->$localKey])) { $data[$result->$localKey] = []; } - $result->setAttr($attr, $this->resultSetBuild($data[$result->$localKey])); + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$localKey])); } } } @@ -108,7 +119,12 @@ class HasMany extends Relation if (!isset($data[$result->$localKey])) { $data[$result->$localKey] = []; } - $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); } } diff --git a/core/library/think/model/relation/HasManyThrough.php b/core/library/think/model/relation/HasManyThrough.php index dd6f6a55..1573fc65 100644 --- a/core/library/think/model/relation/HasManyThrough.php +++ b/core/library/think/model/relation/HasManyThrough.php @@ -11,7 +11,6 @@ namespace think\model\relation; -use think\Db; use think\db\Query; use think\Exception; use think\Loader; @@ -57,6 +56,7 @@ class HasManyThrough extends Relation if ($closure) { call_user_func_array($closure, [ & $this->query]); } + return $this->relation($subRelation)->select(); } @@ -96,8 +96,7 @@ class HasManyThrough extends Relation * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) - { - } + {} /** * 预载入关联查询 返回模型对象 @@ -110,8 +109,7 @@ class HasManyThrough extends Relation * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) - { - } + {} /** * 关联统计 @@ -121,8 +119,7 @@ class HasManyThrough extends Relation * @return integer */ public function relationCount($result, $closure) - { - } + {} /** * 执行基础查询(进执行一次) @@ -131,12 +128,11 @@ class HasManyThrough extends Relation */ protected function baseQuery() { - if (empty($this->baseQuery)) { + if (empty($this->baseQuery) && $this->parent->getData()) { $through = $this->through; - $model = $this->model; - $alias = Loader::parseName(basename(str_replace('\\', '/', $model))); + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); $throughTable = $through::getTable(); - $pk = (new $this->model)->getPk(); + $pk = (new $through)->getPk(); $throughKey = $this->throughKey; $modelTable = $this->parent->getTable(); $this->query->field($alias . '.*')->alias($alias) diff --git a/core/library/think/model/relation/HasOne.php b/core/library/think/model/relation/HasOne.php index 95a401bf..c500c02a 100644 --- a/core/library/think/model/relation/HasOne.php +++ b/core/library/think/model/relation/HasOne.php @@ -50,7 +50,13 @@ class HasOne extends OneToOne call_user_func_array($closure, [ & $this->query]); } // 判断关联类型执行查询 - return $this->query->where($this->foreignKey, $this->parent->$localKey)->relation($subRelation)->find(); + $relationModel = $this->query->where($this->foreignKey, $this->parent->$localKey)->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; } /** @@ -132,13 +138,15 @@ class HasOne extends OneToOne $relationModel = null; } else { $relationModel = $data[$result->$localKey]; - } - if ($relationModel && !empty($this->bindAttr)) { - // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } } // 设置关联属性 - $result->setAttr($attr, $relationModel); + $result->setRelation($attr, $relationModel); } } } @@ -163,13 +171,15 @@ class HasOne extends OneToOne $relationModel = null; } else { $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } } - if ($relationModel && !empty($this->bindAttr)) { - // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); - } - $result->setAttr(Loader::parseName($relation), $relationModel); + $result->setRelation(Loader::parseName($relation), $relationModel); } } diff --git a/core/library/think/model/relation/MorphMany.php b/core/library/think/model/relation/MorphMany.php index 964d3d3a..87c2d66c 100644 --- a/core/library/think/model/relation/MorphMany.php +++ b/core/library/think/model/relation/MorphMany.php @@ -11,7 +11,6 @@ namespace think\model\relation; -use think\Db; use think\db\Query; use think\Exception; use think\Loader; @@ -56,7 +55,14 @@ class MorphMany extends Relation if ($closure) { call_user_func_array($closure, [ & $this->query]); } - return $this->relation($subRelation)->select(); + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; } /** @@ -119,7 +125,11 @@ class MorphMany extends Relation if (!isset($data[$result->$pk])) { $data[$result->$pk] = []; } - $result->setAttr($attr, $this->resultSetBuild($data[$result->$pk])); + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); } } } @@ -141,7 +151,17 @@ class MorphMany extends Relation $this->morphKey => $result->$pk, $this->morphType => $this->type, ], $relation, $subRelation, $closure); - $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); + + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); } } @@ -253,7 +273,7 @@ class MorphMany extends Relation */ protected function baseQuery() { - if (empty($this->baseQuery)) { + if (empty($this->baseQuery) && $this->parent->getData()) { $pk = $this->parent->getPk(); $map[$this->morphKey] = $this->parent->$pk; $map[$this->morphType] = $this->type; diff --git a/core/library/think/model/relation/MorphOne.php b/core/library/think/model/relation/MorphOne.php new file mode 100644 index 00000000..4dfd1c0f --- /dev/null +++ b/core/library/think/model/relation/MorphOne.php @@ -0,0 +1,229 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphOne extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToOne([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + $this->morphKey => $pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } else { + $relationModel = null; + } + + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 多态一对一 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->find(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($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; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + +} diff --git a/core/library/think/model/relation/MorphTo.php b/core/library/think/model/relation/MorphTo.php index 5fafd47a..df104a90 100644 --- a/core/library/think/model/relation/MorphTo.php +++ b/core/library/think/model/relation/MorphTo.php @@ -23,6 +23,7 @@ class MorphTo extends Relation protected $morphType; // 多态别名 protected $alias; + protected $relation; /** * 构造函数 @@ -31,13 +32,15 @@ class MorphTo extends Relation * @param string $morphType 多态字段名 * @param string $morphKey 外键名 * @param array $alias 多态别名定义 + * @param string $relation 关联名 */ - public function __construct(Model $parent, $morphType, $morphKey, $alias = []) + public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) { $this->parent = $parent; $this->morphType = $morphType; $this->morphKey = $morphKey; $this->alias = $alias; + $this->relation = $relation; } /** @@ -53,8 +56,13 @@ class MorphTo extends Relation // 多态模型 $model = $this->parseModel($this->parent->$morphType); // 主键数据 - $pk = $this->parent->$morphKey; - return (new $model)->relation($subRelation)->find($pk); + $pk = $this->parent->$morphKey; + $relationModel = (new $model)->relation($subRelation)->find($pk); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + return $relationModel; } /** @@ -165,7 +173,11 @@ class MorphTo extends Relation if (!isset($data[$result->$morphKey])) { throw new Exception('relation data not exists :' . $this->model); } else { - $result->setAttr($attr, $data[$result->$morphKey]); + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + $result->setRelation($attr, $relationModel); } } } @@ -217,9 +229,46 @@ class MorphTo extends Relation $pk = $this->parent->{$this->morphKey}; $data = (new $model)->with($subRelation)->find($pk); if ($data) { + $data->setParent(clone $result); $data->isUpdate(true); } - $result->setAttr(Loader::parseName($relation), $data ?: null); + $result->setRelation(Loader::parseName($relation), $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @return Model + */ + public function associate($model) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $pk = $model->getPk(); + + $this->parent->setAttr($morphKey, $model->$pk); + $this->parent->setAttr($morphType, get_class($model)); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + $this->parent->setAttr($morphKey, null); + $this->parent->setAttr($morphType, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); } /** @@ -228,6 +277,5 @@ class MorphTo extends Relation * @return void */ protected function baseQuery() - { - } + {} } diff --git a/core/library/think/model/relation/OneToOne.php b/core/library/think/model/relation/OneToOne.php index e2b37421..ff066ed3 100644 --- a/core/library/think/model/relation/OneToOne.php +++ b/core/library/think/model/relation/OneToOne.php @@ -30,6 +30,8 @@ abstract class OneToOne extends Relation protected $joinType; // 要绑定的属性 protected $bindAttr = []; + // 关联方法名 + protected $relation; /** * 设置join类型 @@ -243,13 +245,19 @@ abstract class OneToOne extends Relation } } } + if (isset($list[$relation])) { $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + if (!empty($this->bindAttr)) { $this->bindAttr($relationModel, $result, $this->bindAttr); } + } else { + $relationModel = null; } - $result->setAttr(Loader::parseName($relation), !isset($relationModel) ? null : $relationModel->isUpdate(true)); + $result->setRelation(Loader::parseName($relation), $relationModel); } /** @@ -309,6 +317,5 @@ abstract class OneToOne extends Relation * @return void */ protected function baseQuery() - { - } + {} } diff --git a/core/library/traits/model/SoftDelete.php b/core/library/traits/model/SoftDelete.php index c0924776..be586342 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); + return $model->getQuery(); } /** @@ -42,7 +42,7 @@ trait SoftDelete { $model = new static(); $field = $model->getDeleteTimeField(true); - return $model->db(false) + return $model->getQuery() ->useSoftDelete($field, ['not null', '']); } @@ -60,11 +60,10 @@ trait SoftDelete $name = $this->getDeleteTimeField(); if (!$force) { // 软删除 - $this->change[] = $name; $this->data[$name] = $this->autoWriteTimestamp($name); $result = $this->isUpdate()->save(); } else { - $result = $this->db(false)->delete($this->data); + $result = $this->getQuery()->delete($this->data); } $this->trigger('after_delete', $this); @@ -117,7 +116,7 @@ trait SoftDelete $where[$pk] = $this->getData($pk); } // 恢复删除 - return $this->db(false) + return $this->getQuery() ->useSoftDelete($name, ['not null', '']) ->where($where) ->update([$name => null]); @@ -143,7 +142,7 @@ trait SoftDelete */ protected function getDeleteTimeField($read = false) { - $field = isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; if (!strpos($field, '.')) { $field = '__TABLE__.' . $field; } diff --git a/core/tpl/think_exception.tpl b/core/tpl/think_exception.tpl index 2d898c30..0c9d31ed 100644 --- a/core/tpl/think_exception.tpl +++ b/core/tpl/think_exception.tpl @@ -79,7 +79,7 @@ - <?php echo lang('System Error'); ?> + <?php echo \think\Lang::get('System Error'); ?>