diff --git a/README.md b/README.md index d81d68c8..4b0eb838 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,11 @@ ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PH + 真正惰性加载 + 分布式环境支持 + 支持Composer + + 支持MongoDb > ThinkPHP5的运行环境要求PHP5.4以上。 -详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) +详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) 以及[ThinkPHP5入门系列教程](http://www.kancloud.cn/special/thinkphp5_quickstart) ## 目录结构 diff --git a/base.php b/base.php index fdbd5d90..63fc16c3 100644 --- a/base.php +++ b/base.php @@ -9,7 +9,7 @@ // | Author: liu21st // +---------------------------------------------------------------------- -define('THINK_VERSION', '5.0.5beta'); +define('THINK_VERSION', '5.0.6'); define('THINK_START_TIME', microtime(true)); define('THINK_START_MEM', memory_get_usage()); define('EXT', '.php'); diff --git a/convention.php b/convention.php index c66ef583..887b076d 100644 --- a/convention.php +++ b/convention.php @@ -107,6 +107,8 @@ return [ 'request_cache' => false, // 请求缓存有效期 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], // +---------------------------------------------------------------------- // | 模板设置 @@ -204,7 +206,7 @@ return [ // 是否自动开启 SESSION 'auto_start' => true, 'httponly' => true, - 'secure' => true, + 'secure' => false, ], // +---------------------------------------------------------------------- @@ -272,10 +274,6 @@ return [ 'datetime_format' => 'Y-m-d H:i:s', // 是否需要进行SQL性能分析 'sql_explain' => false, - // Builder类 - 'builder' => '', - // Query类 - 'query' => '\\think\\db\\Query', ], //分页配置 diff --git a/helper.php b/helper.php index 83dbddda..da0dc9c5 100644 --- a/helper.php +++ b/helper.php @@ -23,6 +23,7 @@ use think\exception\HttpResponseException; use think\Lang; use think\Loader; use think\Log; +use think\Model; use think\Request; use think\Response; use think\Session; @@ -329,7 +330,7 @@ if (!function_exists('cookie')) { Cookie::clear($value); } elseif ('' === $value) { // 获取 - return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name); + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option); } elseif (is_null($value)) { // 删除 return Cookie::delete($name); @@ -494,15 +495,16 @@ if (!function_exists('redirect')) { * @param mixed $url 重定向地址 支持Url::build方法的地址 * @param array|integer $params 额外参数 * @param integer $code 状态码 + * @param array $with 隐式传参 * @return \think\response\Redirect */ - function redirect($url = [], $params = [], $code = 302) + function redirect($url = [], $params = [], $code = 302, $with = []) { if (is_integer($params)) { $code = $params; $params = []; } - return Response::create($url, 'redirect', $code)->params($params); + return Response::create($url, 'redirect', $code)->params($params)->with($with); } } @@ -548,3 +550,37 @@ if (!function_exists('token')) { return ''; } } + +if (!function_exists('load_relation')) { + /** + * 延迟预载入关联查询 + * @param mixed $resultSet 数据集 + * @param mixed $relation 关联 + * @return array + */ + function load_relation($resultSet, $relation) + { + $item = current($resultSet); + if ($item instanceof Model) { + $item->eagerlyResultSet($resultSet, $relation); + } + return $resultSet; + } +} + +if (!function_exists('collection')) { + /** + * 数组转换为数据集对象 + * @param array $resultSet 数据集数组 + * @return \think\model\Collection|\think\Collection + */ + function collection($resultSet) + { + $item = current($resultSet); + if ($item instanceof Model) { + return \think\model\Collection::make($resultSet); + } else { + return \think\Collection::make($resultSet); + } + } +} diff --git a/lang/zh-cn.php b/lang/zh-cn.php index 6d33b0ce..c6c5a50f 100644 --- a/lang/zh-cn.php +++ b/lang/zh-cn.php @@ -63,4 +63,6 @@ return [ 'route name not exists' => '路由标识不存在(或参数不够)', 'invalid request' => '非法请求', 'bind attr has exists' => '模型的属性已经存在', + 'relation data not exists' => '关联数据不存在', + 'relation not support' => '关联不支持', ]; diff --git a/library/think/App.php b/library/think/App.php index 77721112..963d8b35 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -118,7 +118,7 @@ class App // 监听app_begin Hook::listen('app_begin', $dispatch); // 请求缓存检查 - $request->cache($config['request_cache'], $config['request_cache_expire']); + $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']); switch ($dispatch['type']) { case 'redirect': @@ -336,7 +336,7 @@ class App $request->module($module); $config = self::init($module); // 模块请求缓存检查 - $request->cache($config['request_cache'], $config['request_cache_expire']); + $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']); } else { throw new HttpException(404, 'module not exists:' . $module); } diff --git a/library/think/Cache.php b/library/think/Cache.php index 12107091..9e4b30ef 100644 --- a/library/think/Cache.php +++ b/library/think/Cache.php @@ -81,9 +81,9 @@ class Cache * @param string $name 缓存标识 * @return Driver */ - public static function store($name) + public static function store($name = '') { - if ('complex' == Config::get('cache.type')) { + if ('' !== $name && 'complex' == Config::get('cache.type')) { self::connect(Config::get('cache.' . $name), strtolower($name)); } return self::$handler; diff --git a/library/think/Db.php b/library/think/Db.php index a29dac3e..00f719e0 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -13,7 +13,6 @@ namespace think; use think\db\Connection; use think\db\Query; -use think\paginator\Collection as PaginatorCollection; /** * Class Db @@ -25,21 +24,21 @@ use think\paginator\Collection as PaginatorCollection; * @method Query union(mixed $union, boolean $all = false) static UNION查询 * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT * @method Query order(mixed $field, string $order = null) static 查询ORDER - * @method Query cache(mixed $key = true , integer $expire = null) static 设置查询缓存 + * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 * @method mixed value(string $field) static 获取某个字段的值 * @method array column(string $field, string $key = '') static 获取某个列的值 * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 - * @method mixed find(mixed $data = []) static 查询单个记录 - * @method mixed select(mixed $data = []) static 查询多个记录 + * @method mixed find(mixed $data = null) static 查询单个记录 + * @method mixed select(mixed $data = null) static 查询多个记录 * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID * @method integer insertAll(array $dataSet) static 插入多条记录 * @method integer update(array $data) static 更新记录 - * @method integer delete(mixed $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 = false) static SQL查询 + * @method mixed query(string $sql, array $bind = [], boolean $fetch = false, boolean $master = false, mixed $class = null) static SQL查询 * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 - * @method PaginatorCollection paginate(integer $listRows = 15, mixed $simple = false, array $config = []) static 分页查询 + * @method Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 * @method mixed transaction(callable $callback) static 执行数据库事务 * @method void startTrans() static 启动事务 * @method void commit() static 用于非自动提交状态下面的查询提交 diff --git a/library/think/Model.php b/library/think/Model.php index e875d252..42c08410 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -14,6 +14,7 @@ namespace think; use InvalidArgumentException; use think\db\Query; use think\Exception\ValidateException; +use think\model\Collection as ModelCollection; use think\model\Relation; use think\model\relation\BelongsTo; use think\model\relation\BelongsToMany; @@ -22,23 +23,11 @@ use think\model\relation\HasManyThrough; use think\model\relation\HasOne; use think\model\relation\MorphMany; use think\model\relation\MorphTo; -use think\paginator\Collection as PaginatorCollection; /** * Class Model * @package think - * @method static PaginatorCollection paginate(integer $listRows = 15, boolean $simple = false, array $config = []) 分页查询 - * @method static mixed value($field, $default = null) 得到某个字段的值 - * @method static array column($field, $key = '') 得到某个列的数组 - * @method static integer count($field = '*') COUNT查询 - * @method static integer sum($field = '*') SUM查询 - * @method static integer min($field = '*') MIN查询 - * @method static integer max($field = '*') MAX查询 - * @method static integer avg($field = '*') AVG查询 - * @method static setField($field, $value = '') - * @method static Query where($field, $op = null, $condition = null) 指定AND查询条件 - * @method static static findOrFail($data = null) 查找单条记录 如果不存在则抛出异常 - * + * @mixin Query */ abstract class Model implements \JsonSerializable, \ArrayAccess { @@ -107,6 +96,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess protected $batchValidate = false; // 查询数据集对象 protected $resultSetType; + // 关联自动写入 + protected $relationWrite; // protected static $db; @@ -153,6 +144,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->dateFormat = $this->db(false)->getConfig('datetime_format'); } + if (is_null($this->resultSetType)) { + $this->resultSetType = $this->db(false)->getConfig('resultset_type'); + } // 执行初始化操作 $this->initialize(); } @@ -221,12 +215,13 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @return void */ protected static function init() - {} + { + } /** * 设置数据对象值 * @access public - * @param mixed $data 数据或者属性名 + * @param mixed $data 数据或者属性名 * @param mixed $value 值 * @return $this */ @@ -273,9 +268,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 修改器 设置数据对象值 * @access public - * @param string $name 属性名 - * @param mixed $value 属性值 - * @param array $data 数据 + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 * @return $this */ public function setAttr($name, $value, $data = []) @@ -308,7 +303,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 自动写入时间戳 * @access public - * @param string $name 时间戳字段 + * @param string $name 时间戳字段 * @return mixed */ protected function autoWriteTimestamp($name) @@ -330,7 +325,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $value = $_SERVER['REQUEST_TIME']; break; } - } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), ['datetime', 'date', 'timestamp'])) { + } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $this->dateFormat); } else { $value = $this->formatDateTime($_SERVER['REQUEST_TIME'], $this->dateFormat, true); @@ -341,16 +341,16 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 时间日期字段格式化处理 * @access public - * @param mixed $time 时间日期表达式 - * @param mixed $format 日期格式 - * @param bool $timestamp 是否进行时间戳转换 + * @param mixed $time 时间日期表达式 + * @param mixed $format 日期格式 + * @param bool $timestamp 是否进行时间戳转换 * @return mixed */ protected function formatDateTime($time, $format, $timestamp = false) { if (false !== strpos($format, '\\')) { $time = new $format($time); - } elseif (!$timestamp) { + } elseif (!$timestamp && false !== $format) { $time = date($format, $time); } return $time; @@ -359,8 +359,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 数据写入 类型转换 * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 + * @param mixed $value 值 + * @param string|array $type 要转换的类型 * @return mixed */ protected function writeTransform($value, $type) @@ -438,7 +438,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 类型转换 $value = $this->readTransform($value, $this->type[$name]); } elseif (in_array($name, [$this->createTime, $this->updateTime])) { - if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), ['datetime', 'date', 'timestamp'])) { + if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { $value = $this->formatDateTime(strtotime($value), $this->dateFormat); } else { $value = $this->formatDateTime($value, $this->dateFormat); @@ -462,8 +467,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 数据读取 类型转换 * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 + * @param mixed $value 值 + * @param string|array $type 要转换的类型 * @return mixed */ protected function readTransform($value, $type) @@ -523,8 +528,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置需要追加的输出属性 * @access public - * @param array $append 属性列表 - * @param bool $override 是否覆盖 + * @param array $append 属性列表 + * @param bool $override 是否覆盖 * @return $this */ public function append($append = [], $override = false) @@ -536,9 +541,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置附加关联对象的属性 * @access public - * @param string $relation 关联方法 - * @param string|array $append 追加属性名 + * @param string $relation 关联方法 + * @param string|array $append 追加属性名 * @return $this + * @throws Exception */ public function appendRelationAttr($relation, $append) { @@ -562,8 +568,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置需要隐藏的输出属性 * @access public - * @param array $hidden 属性列表 - * @param bool $override 是否覆盖 + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 * @return $this */ public function hidden($hidden = [], $override = false) @@ -574,8 +580,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置需要输出的属性 + * @access public * @param array $visible - * @param bool $override 是否覆盖 + * @param bool $override 是否覆盖 * @return $this */ public function visible($visible = [], $override = false) @@ -584,6 +591,56 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $this; } + /** + * 解析隐藏及显示属性 + * @access protected + * @param array $attrs 属性 + * @param array $result 结果集 + * @param bool $visible + * @return array + */ + protected function parseAttr($attrs, &$result, $visible = true) + { + $array = []; + foreach ($attrs as $key => $val) { + if (is_array($val)) { + if ($visible) { + $array[] = $key; + } + $result[$key] = $val; + } elseif (strpos($val, '.')) { + list($key, $name) = explode('.', $val); + if ($visible) { + $array[] = $key; + } + $result[$key][] = $name; + } else { + $array[] = $val; + } + } + return $array; + } + + /** + * 转换子模型对象 + * @access protected + * @param Model|ModelCollection $model + * @param $visible + * @param $hidden + * @param $key + * @return array + */ + protected function subToArray($model, $visible, $hidden, $key) + { + // 关联模型对象 + if (isset($visible[$key])) { + $model->visible($visible[$key]); + } elseif (isset($hidden[$key])) { + $model->hidden($hidden[$key]); + } + return $model->toArray(); + } + /** * 转换当前模型对象为数组 * @access public @@ -591,26 +648,29 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public function toArray() { - $item = []; - - //过滤属性 + $item = []; + $visible = []; + $hidden = []; + // 过滤属性 if (!empty($this->visible)) { - $data = array_intersect_key($this->data, array_flip($this->visible)); + $array = $this->parseAttr($this->visible, $visible); + $data = array_intersect_key($this->data, array_flip($array)); } elseif (!empty($this->hidden)) { - $data = array_diff_key($this->data, array_flip($this->hidden)); + $array = $this->parseAttr($this->hidden, $hidden, false); + $data = array_diff_key($this->data, array_flip($array)); } else { $data = $this->data; } foreach ($data as $key => $val) { - if ($val instanceof Model || $val instanceof Collection) { + if ($val instanceof Model || $val instanceof ModelCollection) { // 关联模型对象 - $item[$key] = $val->toArray(); + $item[$key] = $this->subToArray($val, $visible, $hidden, $key); } elseif (is_array($val) && reset($val) instanceof Model) { // 关联模型数据集 $arr = []; foreach ($val as $k => $value) { - $arr[$k] = $value->toArray(); + $arr[$k] = $this->subToArray($value, $visible, $hidden, $k); } $item[$key] = $arr; } else { @@ -620,8 +680,19 @@ abstract class Model implements \JsonSerializable, \ArrayAccess } // 追加属性(必须定义获取器) if (!empty($this->append)) { - foreach ($this->append as $name) { - $item[$name] = $this->getAttr($name); + foreach ($this->append as $key => $name) { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append($name)->toArray(); + } elseif (strpos($name, '.')) { + list($key, $attr) = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append([$attr])->toArray(); + } else { + $item[$name] = $this->getAttr($name); + } } } return !empty($item) ? $item : []; @@ -630,7 +701,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 转换当前模型对象为JSON字符串 * @access public - * @param integer $options json参数 + * @param integer $options json参数 * @return string */ public function toJson($options = JSON_UNESCAPED_UNICODE) @@ -641,15 +712,15 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 转换当前模型数据集为数据集对象 * @access public - * @param array|Collection $collection 数据集 - * @return Collection + * @param array|\think\Collection $collection 数据集 + * @return \think\Collection */ public function toCollection($collection) { if ($this->resultSetType) { if ('collection' == $this->resultSetType) { - $collection = new Collection($collection); - } else { + $collection = new ModelCollection($collection); + } elseif (false !== strpos($this->resultSetType, '\\')) { $class = $this->resultSetType; $collection = new $class($collection); } @@ -657,6 +728,21 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return $collection; } + /** + * 关联数据一起更新 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function together($relation) + { + if (is_string($relation)) { + $relation = explode(',', $relation); + } + $this->relationWrite = $relation; + return $this; + } + /** * 获取模型对象的主键 * @access public @@ -694,9 +780,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 保存当前数据对象 * @access public - * @param array $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 * @return integer|false */ public function save($data = [], $where = [], $sequence = null) @@ -715,8 +801,32 @@ 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]); + } + } + } 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]); @@ -775,7 +885,33 @@ 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); + + // 关联更新 + 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); + } + } + } + } + // 清空change $this->change = []; // 更新回调 @@ -802,6 +938,15 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $this->data[$pk] = $insertId; } } + + // 关联写入 + if (isset($relation)) { + foreach ($relation as $name => $val) { + $method = Loader::parseName($name, 1, false); + $this->$method()->save($val); + } + } + // 标记为更新 $this->isUpdate = true; // 清空change @@ -818,9 +963,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 保存多个数据到当前数据对象 * @access public - * @param array $dataSet 数据 - * @param boolean $replace 是否自动识别更新和写入 + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 * @return array|false + * @throws \Exception */ public function saveAll($dataSet, $replace = true) { @@ -860,18 +1006,33 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置允许写入的字段 * @access public - * @param bool|array $field 允许写入的字段 如果为true只允许写入数据表字段 + * @param mixed $field 允许写入的字段 如果为true只允许写入数据表字段 * @return $this */ public function allowField($field) { - if (true === $field) { - $field = $this->db(false)->getTableInfo('', 'fields'); + if (is_string($field)) { + $field = explode(',', $field); } $this->field = $field; return $this; } + /** + * 设置只读字段 + * @access public + * @param mixed $field 只读字段 + * @return $this + */ + public function readonly($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->readonly = $field; + return $this; + } + /** * 是否为更新数据 * @access public @@ -918,7 +1079,29 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return false; } - $result = $this->db()->delete($this->data); + // 删除条件 + $pk = $this->getPk(); + if (isset($this->data[$pk])) { + $where = [$pk => $this->data[$pk]]; + } elseif (!empty($this->updateWhere)) { + $where = $this->updateWhere; + } else { + $where = null; + } + + // 删除当前模型数据 + $result = $this->db()->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->delete(); + } + } + } $this->trigger('after_delete', $this); return $result; @@ -939,8 +1122,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置字段验证 * @access public - * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 - * @param array $msg 提示信息 + * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 + * @param array $msg 提示信息 * @param bool $batch 批量验证 * @return $this */ @@ -973,8 +1156,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 自动验证数据 * @access protected - * @param array $data 验证数据 - * @param mixed $rule 验证规则 + * @param array $data 验证数据 + * @param mixed $rule 验证规则 * @param bool $batch 批量验证 * @return bool */ @@ -1025,9 +1208,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 注册回调方法 * @access public - * @param string $event 事件名 - * @param callable $callback 回调方法 - * @param bool $override 是否覆盖 + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @param bool $override 是否覆盖 * @return void */ public static function event($event, $callback, $override = false) @@ -1042,8 +1225,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 触发事件 * @access protected - * @param string $event 事件名 - * @param mixed $params 传入参数(引用) + * @param string $event 事件名 + * @param mixed $params 传入参数(引用) * @return bool */ protected function trigger($event, &$params) @@ -1064,8 +1247,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 写入数据 * @access public - * @param array $data 数据数组 - * @param array|true $field 允许字段 + * @param array $data 数据数组 + * @param array|true $field 允许字段 * @return $this */ public static function create($data = [], $field = null) @@ -1081,9 +1264,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 更新数据 * @access public - * @param array $data 数据数组 - * @param array $where 更新条件 - * @param array|true $field 允许字段 + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 * @return $this */ public static function update($data = [], $where = [], $field = null) @@ -1107,6 +1290,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public static function get($data = null, $with = [], $cache = false) { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } $query = static::parseQuery($data, $with, $cache); return $query->find($data); } @@ -1122,6 +1309,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess */ public static function all($data = null, $with = [], $cache = false) { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } $query = static::parseQuery($data, $with, $cache); return $query->select($data); } @@ -1129,9 +1320,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 分析查询表达式 * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param string $with 关联预查询 - * @param bool $cache 是否缓存 + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 * @return Query */ protected static function parseQuery(&$data, $with, $cache) @@ -1183,9 +1374,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 命名范围 * @access public - * @param string|array|Closure $name 命名范围名称 逗号分隔 - * @param mixed ...$params 参数调用 - * @return Model + * @param string|array|\Closure $name 命名范围名称 逗号分隔 + * @internal mixed ...$params 参数调用 + * @return Model|Query */ public static function scope($name) { @@ -1213,7 +1404,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 设置是否使用全局查询范围 - * @param bool $use 是否启用全局查询范围 + * @param bool $use 是否启用全局查询范围 * @access public * @return Model */ @@ -1227,29 +1418,31 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 根据关联条件查询当前模型 * @access public - * @param string $relation 关联方法名 - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @return Model + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Relation|Query */ public static function has($relation, $operator = '>=', $count = 1, $id = '*') { - $model = new static(); - return $model->$relation()->has($model, $operator, $count, $id); + $relation = (new static())->$relation(); + if (is_array($operator) || $operator instanceof \Closure) { + return $relation->hasWhere($operator); + } + return $relation->has($operator, $count, $id); } /** * 根据关联条件查询当前模型 * @access public - * @param string $relation 关联方法名 - * @param mixed $where 查询条件(数组或者闭包) - * @return Model + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @return Relation|Query */ public static function hasWhere($relation, $where = []) { - $model = new static(); - return $model->$relation()->hasWhere($model, $where); + return (new static())->$relation()->hasWhere($where); } /** @@ -1281,8 +1474,22 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $relations = explode(',', $relations); } - foreach ($relations as $relation) { - $this->data[$relation] = $this->$relation()->getRelation(); + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $method = Loader::parseName($relation, 1, false); + $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure); } return $this; } @@ -1290,12 +1497,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 预载入关联查询 返回数据集 * @access public - * @param array $resultSet 数据集 - * @param string $relation 关联名 - * @param string $class 数据集对象名 为空表示数组 + * @param array $resultSet 数据集 + * @param string $relation 关联名 * @return array */ - public function eagerlyResultSet(&$resultSet, $relation, $class = '') + public function eagerlyResultSet(&$resultSet, $relation) { $relations = is_string($relation) ? explode(',', $relation) : $relation; foreach ($relations as $key => $relation) { @@ -1305,23 +1511,25 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $closure = $relation; $relation = $key; } - if (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation); + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); } $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $class); + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); } } /** * 预载入关联查询 返回模型对象 * @access public - * @param Model $result 数据对象 - * @param string $relation 关联名 - * @param string $class 数据集对象名 为空表示数组 + * @param Model $result 数据对象 + * @param string $relation 关联名 * @return Model */ - public function eagerlyResult(&$result, $relation, $class = '') + public function eagerlyResult(&$result, $relation) { $relations = is_string($relation) ? explode(',', $relation) : $relation; @@ -1332,19 +1540,22 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $closure = $relation; $relation = $key; } - if (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation); + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); } $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure, $class); + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); } } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param string|array $relation 关联名 + * @param Model $result 数据对象 + * @param string|array $relation 关联名 * @return void */ public function relationCount(&$result, $relation) @@ -1356,21 +1567,41 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if ($relation instanceof \Closure) { $closure = $relation; $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; } $relation = Loader::parseName($relation, 1, false); $count = $this->$relation()->relationCount($result, $closure); - $result->setAttr(Loader::parseName($relation) . '_count', $count); + if (!isset($name)) { + $name = Loader::parseName($relation) . '_count'; + } + $result->setAttr($name, $count); } } + /** + * 获取模型的默认外键名 + * @access public + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey($name) + { + if (strpos($name, '\\')) { + $name = basename(str_replace('\\', '/', $name)); + } + return Loader::parseName($name) . '_id'; + } + /** * HAS ONE 关联定义 * @access public - * @param string $model 模型名 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 + * @param string $localKey 关联主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 * @return HasOne */ public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') @@ -1378,97 +1609,93 @@ abstract class Model implements \JsonSerializable, \ArrayAccess // 记录当前关联信息 $model = $this->parseModel($model); $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - return new HasOne($this, $model, $foreignKey, $localKey, $alias, $joinType); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasOne($this, $model, $foreignKey, $localKey, $joinType); } /** * BELONGS TO 关联定义 * @access public - * @param string $model 模型名 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $otherKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 + * @param string $localKey 关联主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 * @return BelongsTo */ - public function belongsTo($model, $foreignKey = '', $otherKey = '', $alias = [], $joinType = 'INNER') + public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') { // 记录当前关联信息 $model = $this->parseModel($model); - $foreignKey = $foreignKey ?: Loader::parseName(basename(str_replace('\\', '/', $model))) . '_id'; - $otherKey = $otherKey ?: (new $model)->getPk(); - return new BelongsTo($this, $model, $foreignKey, $otherKey, $alias, $joinType); + $foreignKey = $foreignKey ?: $this->getForeignKey($model); + $localKey = $localKey ?: (new $model)->getPk(); + return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType); } /** * HAS MANY 关联定义 * @access public - * @param string $model 模型名 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 + * @param string $localKey 关联主键 * @return HasMany */ - public function hasMany($model, $foreignKey = '', $localKey = '', $alias = []) + public function hasMany($model, $foreignKey = '', $localKey = '') { // 记录当前关联信息 $model = $this->parseModel($model); $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - return new HasMany($this, $model, $foreignKey, $localKey, $alias); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasMany($this, $model, $foreignKey, $localKey); } /** * HAS MANY 远程关联定义 * @access public - * @param string $model 模型名 - * @param string $through 中间模型名 + * @param string $model 模型名 + * @param string $through 中间模型名 * @param string $foreignKey 关联外键 * @param string $throughKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 + * @param string $localKey 关联主键 * @return HasManyThrough */ - public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '', $alias = []) + public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') { // 记录当前关联信息 $model = $this->parseModel($model); $through = $this->parseModel($through); $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - $name = Loader::parseName(basename(str_replace('\\', '/', $through))); - $throughKey = $throughKey ?: $name . '_id'; - return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $alias); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey($through); + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); } /** * BELONGS TO MANY 关联定义 * @access public - * @param string $model 模型名 - * @param string $table 中间表名 + * @param string $model 模型名 + * @param string $table 中间表名 * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型关联键 - * @param array $alias 别名定义 + * @param string $localKey 当前模型关联键 * @return BelongsToMany */ - public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '', $alias = []) + public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') { // 记录当前关联信息 $model = $this->parseModel($model); $name = Loader::parseName(basename(str_replace('\\', '/', $model))); $table = $table ?: $this->db(false)->getTable(Loader::parseName($this->name) . '_' . $name); $foreignKey = $foreignKey ?: $name . '_id'; - $localKey = $localKey ?: Loader::parseName($this->name) . '_id'; - return new BelongsToMany($this, $model, $table, $foreignKey, $localKey, $alias); + $localKey = $localKey ?: $this->getForeignKey($this->name); + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); } /** * MORPH MANY 关联定义 * @access public - * @param string $model 模型名 - * @param string|array $morph 多态字段信息 - * @param string $type 多态类型 + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 * @return MorphMany */ public function morphMany($model, $morph = null, $type = '') @@ -1492,8 +1719,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * MORPH TO 关联定义 * @access public - * @param string|array $morph 多态字段信息 - * @param array $alias 多态别名定义 + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 * @return MorphTo */ public function morphTo($morph = null, $alias = []) @@ -1547,8 +1774,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 修改器 设置数据对象的值 * @access public - * @param string $name 名称 - * @param mixed $value 值 + * @param string $name 名称 + * @param mixed $value 值 * @return void */ public function __set($name, $value) @@ -1641,6 +1868,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess /** * 模型事件快捷方法 + * @param $callback + * @param bool $override */ protected static function beforeInsert($callback, $override = false) { diff --git a/library/think/Paginator.php b/library/think/Paginator.php index d2fe79d2..0c1bea8a 100644 --- a/library/think/Paginator.php +++ b/library/think/Paginator.php @@ -11,14 +11,19 @@ namespace think; -use think\paginator\Collection as PaginatorCollection; +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use Traversable; -abstract class Paginator +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable { /** @var bool 是否为简洁模式 */ protected $simple = false; - /** @var PaginatorCollection 数据集 */ + /** @var Collection 数据集 */ protected $items; /** @var integer 当前页 */ @@ -44,7 +49,7 @@ abstract class Paginator 'fragment' => '', ]; - protected function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) { $this->options = array_merge($this->options, $options); @@ -53,10 +58,11 @@ abstract class Paginator $this->simple = $simple; $this->listRows = $listRows; + if (!$items instanceof Collection) { + $items = Collection::make($items); + } + if ($simple) { - if (!$items instanceof Collection) { - $items = Collection::make($items); - } $this->currentPage = $this->setCurrentPage($currentPage); $this->hasMore = count($items) > ($this->listRows); $items = $items->slice(0, $this->listRows); @@ -66,8 +72,7 @@ abstract class Paginator $this->currentPage = $this->setCurrentPage($currentPage); $this->hasMore = $this->currentPage < $this->lastPage; } - - $this->items = PaginatorCollection::make($items, $this); + $this->items = $items; } /** @@ -77,12 +82,11 @@ abstract class Paginator * @param bool $simple * @param null $total * @param array $options - * @return PaginatorCollection + * @return Paginator */ public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) { - $paginator = new static($items, $listRows, $currentPage, $total, $simple, $options); - return $paginator->items; + return new static($items, $listRows, $currentPage, $total, $simple, $options); } protected function setCurrentPage($currentPage) @@ -253,4 +257,113 @@ abstract class Paginator * @return mixed */ abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + public function getCollection() + { + return $this->items; + } + + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * Retrieve an external iterator + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * Count elements of an object + */ + public function count() + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + public function toArray() + { + try { + $total = $this->total(); + } catch (Exception $e) { + $total = null; + } + + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'data' => $this->items->toArray() + ]; + } + + /** + * Specify data which should be serialized to JSON + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + public function __call($name, $arguments) + { + return call_user_func_array([$this->getCollection(), $name], $arguments); + } + } diff --git a/library/think/Request.php b/library/think/Request.php index a405763e..f32637d5 100644 --- a/library/think/Request.php +++ b/library/think/Request.php @@ -1502,9 +1502,10 @@ class Request * @access public * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 * @return void */ - public function cache($key, $expire = null) + public function cache($key, $expire = null, $except = []) { if (false !== $key && $this->isGet() && !$this->isCheckCache) { // 标记请求缓存检查 @@ -1516,14 +1517,19 @@ class Request if ($key instanceof \Closure) { $key = call_user_func_array($key, [$this]); } elseif (true === $key) { + foreach ($except as $rule) { + if (0 === strpos($this->url(), $rule)) { + return; + } + } // 自动缓存功能 - $key = '__URL__'; + $key = md5($this->host()) . '__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())], $key); } if (false !== strpos($key, ':')) { diff --git a/library/think/Route.php b/library/think/Route.php index 1629d636..679a1372 100644 --- a/library/think/Route.php +++ b/library/think/Route.php @@ -304,8 +304,9 @@ class Route } $vars = self::parseVar($rule); if (isset($name)) { - $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; - self::name($name, [$key, $vars, self::$domain]); + $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; + $suffix = isset($option['ext']) ? $option['ext'] : null; + self::name($name, [$key, $vars, self::$domain, $suffix]); } if ($group) { if ('*' != $type) { @@ -447,7 +448,8 @@ class Route $vars = self::parseVar($key); $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; // 设置路由标识 - self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain]); + $suffix = isset($options['ext']) ? $options['ext'] : null; + self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]); } self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; } @@ -1126,8 +1128,8 @@ class Route || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 - || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', $request->ext() ? '|' . $request->ext() . '|' : '')) // 伪静态后缀检测 - || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', $request->ext() ? '|' . $request->ext() . '|' : '')) + || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 diff --git a/library/think/Url.php b/library/think/Url.php index 51c846d7..de3cd912 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -80,6 +80,9 @@ class Url if (!empty($match[1])) { $domain = $match[1]; } + if (!is_null($match[2])) { + $suffix = $match[2]; + } } elseif (!empty($rule) && isset($name)) { throw new \InvalidArgumentException('route name not exists:' . $name); } else { @@ -153,7 +156,8 @@ class Url // 检测域名 $domain = self::parseDomain($url, $domain); // URL组装 - $url = $domain . (self::$root ?: Request::instance()->root()) . '/' . ltrim($url, '/'); + $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/'); + self::$bindCheck = false; return $url; } @@ -287,18 +291,18 @@ class Url public static function getRuleUrl($rule, &$vars = []) { foreach ($rule as $item) { - list($url, $pattern, $domain) = $item; + list($url, $pattern, $domain, $suffix) = $item; if (empty($pattern)) { - return [$url, $domain]; + return [$url, $domain, $suffix]; } foreach ($pattern as $key => $val) { if (isset($vars[$key])) { $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $vars[$key], $url); unset($vars[$key]); - $result = [$url, $domain]; + $result = [$url, $domain, $suffix]; } elseif (2 == $val) { $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); - $result = [$url, $domain]; + $result = [$url, $domain, $suffix]; } else { break; } diff --git a/library/think/Validate.php b/library/think/Validate.php index b4443a0c..718ed7a1 100644 --- a/library/think/Validate.php +++ b/library/think/Validate.php @@ -568,7 +568,7 @@ class Validate break; case 'ip': // 是否为IP地址 - $result = $this->filter($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6); + $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); break; case 'url': // 是否为一个URL地址 @@ -591,7 +591,7 @@ class Validate break; case 'boolean': // 是否为布尔值 - $result = in_array($value, [0, 1, true, false]); + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); break; case 'array': // 是否为数组 @@ -656,7 +656,7 @@ class Validate if (!in_array($rule, ['ipv4', 'ipv6'])) { $rule = 'ipv4'; } - return $this->filter($value, FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4); + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); } /** @@ -872,6 +872,7 @@ class Validate list($rule, $param) = explode(',', $rule); } elseif (is_array($rule)) { $param = isset($rule[1]) ? $rule[1] : null; + $rule = $rule[0]; } else { $param = null; } @@ -1222,9 +1223,9 @@ class Validate $msg = Lang::get(substr($msg, 2, -1)); } - if (is_string($msg) && is_string($rule) && false !== strpos($msg, ':')) { + if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { // 变量替换 - if (strpos($rule, ',')) { + if (is_string($rule) && strpos($rule, ',')) { $array = array_pad(explode(',', $rule), 3, ''); } else { $array = array_pad([], 3, ''); diff --git a/library/think/cache/driver/File.php b/library/think/cache/driver/File.php index 4c3c49e0..5aadb2b1 100644 --- a/library/think/cache/driver/File.php +++ b/library/think/cache/driver/File.php @@ -225,6 +225,7 @@ class File extends Driver foreach ($files as $path) { if (is_dir($path)) { array_map('unlink', glob($path . '/*.php')); + rmdir($path); } else { unlink($path); } diff --git a/library/think/db/Builder.php b/library/think/db/Builder.php index d98f4334..56665527 100644 --- a/library/think/db/Builder.php +++ b/library/think/db/Builder.php @@ -112,8 +112,8 @@ abstract class Builder $result[$item] = $val; } else { $key = str_replace('.', '_', $key); - $this->query->bind($key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); - $result[$item] = ':' . $key; + $this->query->bind('__data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + $result[$item] = ':__data__' . $key; } } elseif (is_object($val) && method_exists($val, '__toString')) { // 对象数据写入 @@ -250,7 +250,10 @@ abstract class Builder // 使用闭包查询 $query = new Query($this->connection); call_user_func_array($value, [ & $query]); - $str[] = ' ' . $key . ' ( ' . $this->buildWhere($query->getOptions('where'), $options) . ' )'; + $whereClause = $this->buildWhere($query->getOptions('where'), $options); + if (!empty($whereClause)) { + $str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; + } } elseif (strpos($field, '|')) { // 不同字段使用相同查询条件(OR) $array = explode('|', $field); @@ -523,10 +526,12 @@ abstract class Builder $array = []; foreach ($order as $key => $val) { if (is_numeric($key)) { - if (false === strpos($val, '(')) { - $array[] = $this->parseKey($val, $options); - } elseif ('[rand]' == $val) { + if ('[rand]' == $val) { $array[] = $this->parseRand(); + } elseif (false === strpos($val, '(')) { + $array[] = $this->parseKey($val, $options); + } else { + $array[] = $val; } } else { $sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index dd56bd5f..bc67b1d8 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -13,7 +13,6 @@ namespace think\db; use PDO; use PDOStatement; -use think\Collection; use think\Db; use think\db\exception\BindParamException; use think\Debug; @@ -51,8 +50,6 @@ abstract class Connection protected $linkRead; protected $linkWrite; - // 查询结果类型 - protected $resultSetType = 'array'; // 查询结果类型 protected $fetchType = PDO::FETCH_ASSOC; // 字段属性大小写 @@ -61,6 +58,8 @@ abstract class Connection protected static $event = []; // 查询对象 protected $query = []; + // 使用Builder类 + protected $builder; // 数据库连接参数配置 protected $config = [ // 数据库类型 @@ -109,6 +108,8 @@ abstract class Connection 'builder' => '', // Query类 'query' => '\\think\\db\\Query', + // 是否需要断线重连 + 'break_reconnect' => false, ]; // PDO连接参数 @@ -151,6 +152,20 @@ abstract class Connection return $this->query[$model]; } + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilder() + { + if (!empty($this->builder)) { + return $this->builder; + } else { + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + } + /** * 调用Query类的查询方法 * @access public @@ -270,10 +285,7 @@ abstract class Connection } // 记录当前字段属性大小写设置 $this->attrCase = $params[PDO::ATTR_CASE]; - // 记录数据集返回类型 - if (isset($config['resultset_type'])) { - $this->resultSetType = $config['resultset_type']; - } + // 数据返回类型 if (isset($config['result_type'])) { $this->fetchType = $config['result_type']; @@ -353,8 +365,8 @@ abstract class Connection $this->bind = $bind; } - //释放前次的查询结果 - if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) { + // 释放前次的查询结果 + if (!empty($this->PDOStatement)) { $this->free(); } @@ -381,6 +393,9 @@ abstract class Connection // 返回结果集 return $this->getResult($pdo, $procedure); } catch (\PDOException $e) { + if ($this->config['break_reconnect'] && $this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } throw new PDOException($e, $this->config, $this->getLastsql()); } } @@ -436,6 +451,9 @@ abstract class Connection $this->numRows = $this->PDOStatement->rowCount(); return $this->numRows; } catch (\PDOException $e) { + if ($this->config['break_reconnect'] && $this->isBreak($e)) { + return $this->close()->execute($sql, $bind); + } throw new PDOException($e, $this->config, $this->getLastsql()); } } @@ -511,11 +529,13 @@ abstract class Connection protected function bindParam($bind) { foreach ($bind as $key => $val) { - if (is_numeric($key)) { - $key = $key + 1; + $param = is_numeric($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { + array_unshift($val, $param); + $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); + } else { + $result = $this->PDOStatement->bindValue($param, $val); } - array_unshift($val, $key); - $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); if (!$result) { $param = array_shift($val); throw new BindParamException( @@ -529,11 +549,11 @@ abstract class Connection } /** - * 获得数据集 + * 获得数据集数组 * @access protected * @param bool $pdo 是否返回PDOStatement * @param bool $procedure 是否存储过程 - * @return mixed + * @return array */ protected function getResult($pdo = false, $procedure = false) { @@ -547,11 +567,6 @@ abstract class Connection } $result = $this->PDOStatement->fetchAll($this->fetchType); $this->numRows = count($result); - - if ('collection' == $this->resultSetType) { - // 返回数据集Collection对象 - $result = new Collection($result); - } return $result; } @@ -740,12 +755,28 @@ abstract class Connection } /** - * 关闭数据库 + * 关闭数据库(或者重新连接) * @access public + * @return $this */ public function close() { - $this->linkID = null; + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException $e 异常 + * @return bool + */ + protected function isBreak($e) + { + return false; } /** diff --git a/library/think/db/Query.php b/library/think/db/Query.php index f63d86a7..cd5e9a2c 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -124,8 +124,7 @@ class Query */ protected function setBuilder() { - $builder = $this->connection->getConfig('builder') ?: $this->connection->getConfig('type'); - $class = false !== strpos($builder, '\\') ? $builder : '\\think\\db\\builder\\' . ucfirst($builder); + $class = $this->connection->getBuilder(); $this->builder = new $class($this->connection, $this); } @@ -229,8 +228,8 @@ class Query /** * 执行语句 * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 + * @param string $sql sql指令 + * @param array $bind 参数绑定 * @return int * @throws BindParamException * @throws PDOException @@ -243,7 +242,7 @@ class Query /** * 获取最近插入的ID * @access public - * @param string $sequence 自增序列名 + * @param string $sequence 自增序列名 * @return string */ public function getLastInsID($sequence = null) @@ -390,7 +389,7 @@ class Query * @access public * @param string $field 字段名 * @param mixed $default 默认值 - * @param bool $force 强制转为数字类型 + * @param bool $force 强制转为数字类型 * @return mixed */ public function value($field, $default = null, $force = false) @@ -409,7 +408,7 @@ class Query if (isset($this->options['field'])) { unset($this->options['field']); } - $pdo = $this->field($field)->fetchPdo(true)->find(); + $pdo = $this->field($field)->limit(1)->getPdo(); if (is_string($pdo)) { // 返回SQL语句 return $pdo; @@ -420,11 +419,7 @@ class Query } if (isset($cache)) { // 缓存数据 - if (isset($cache['tag'])) { - Cache::tag($cache['tag'])->set($key, $result, $cache['expire']); - } else { - Cache::set($key, $result, $cache['expire']); - } + $this->cacheData($key, $result, $cache); } } else { // 清空查询条件 @@ -459,7 +454,7 @@ class Query if ($key && '*' != $field) { $field = $key . ',' . $field; } - $pdo = $this->field($field)->fetchPdo(true)->select(); + $pdo = $this->field($field)->getPdo(); if (is_string($pdo)) { // 返回SQL语句 return $pdo; @@ -492,11 +487,7 @@ class Query } if (isset($cache) && isset($guid)) { // 缓存数据 - if (isset($cache['tag'])) { - Cache::tag($cache['tag'])->set($guid, $result, $cache['expire']); - } else { - Cache::set($guid, $result, $cache['expire']); - } + $this->cacheData($guid, $result, $cache); } } else { // 清空查询条件 @@ -509,10 +500,17 @@ class Query * COUNT查询 * @access public * @param string $field 字段名 - * @return integer + * @return integer|string */ public function count($field = '*') { + if (isset($this->options['group'])) { + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); + return $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + } + return $this->value('COUNT(' . $field . ') AS tp_count', 0, true); } @@ -522,7 +520,7 @@ class Query * @param string $field 字段名 * @return float|int */ - public function sum($field = '*') + public function sum($field) { return $this->value('SUM(' . $field . ') AS tp_sum', 0, true); } @@ -533,7 +531,7 @@ class Query * @param string $field 字段名 * @return mixed */ - public function min($field = '*') + public function min($field) { return $this->value('MIN(' . $field . ') AS tp_min', 0, true); } @@ -544,7 +542,7 @@ class Query * @param string $field 字段名 * @return mixed */ - public function max($field = '*') + public function max($field) { return $this->value('MAX(' . $field . ') AS tp_max', 0, true); } @@ -555,7 +553,7 @@ class Query * @param string $field 字段名 * @return float|int */ - public function avg($field = '*') + public function avg($field) { return $this->value('AVG(' . $field . ') AS tp_avg', 0, true); } @@ -694,7 +692,7 @@ class Query * 获取Join表名及别名 支持 * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' * @access public - * @param array|string $join + * @param array|string $join * @return array|string */ protected function getJoinTable($join, &$alias = null) @@ -817,8 +815,8 @@ class Query /** * 字段值增长 * @access public - * @param string|array $field 字段名 - * @param integer $step 增长值 + * @param string|array $field 字段名 + * @param integer $step 增长值 * @return $this */ public function inc($field, $step = 1) @@ -833,8 +831,8 @@ class Query /** * 字段值减少 * @access public - * @param string|array $field 字段名 - * @param integer $step 增长值 + * @param string|array $field 字段名 + * @param integer $step 增长值 * @return $this */ public function dec($field, $step = 1) @@ -849,8 +847,8 @@ class Query /** * 使用表达式设置数据 * @access public - * @param string $field 字段名 - * @param string $value 字段值 + * @param string $field 字段名 + * @param string $value 字段值 * @return $this */ public function exp($field, $value) @@ -871,7 +869,7 @@ class Query public function view($join, $field = true, $on = null, $type = 'INNER') { $this->options['view'] = true; - if (is_array($join) && is_null($field)) { + if (is_array($join) && key($join) !== 0) { foreach ($join as $key => $val) { $this->view($key, $val[0], isset($val[1]) ? $val[1] : null, isset($val[2]) ? $val[2] : 'INNER'); } @@ -972,6 +970,156 @@ class Query return $this; } + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'null', null); + return $this; + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'notnull', null); + return $this; + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, $logic = 'AND') + { + $this->options['where'][strtoupper($logic)][] = ['exists', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, $logic = 'AND') + { + $this->options['where'][strtoupper($logic)][] = ['not exists', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'in', $condition); + return $this; + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not in', $condition); + return $this; + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'like', $condition); + return $this; + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not like', $condition); + return $this; + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'between', $condition); + return $this; + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not between', $condition); + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'exp', $condition); + return $this; + } + /** * 分析查询表达式 * @access public @@ -984,6 +1132,7 @@ class Query */ protected function parseWhereExp($logic, $field, $op, $condition, $param = []) { + $logic = strtoupper($logic); if ($field instanceof \Closure) { $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; return; @@ -1003,20 +1152,25 @@ class Query // 数组批量查询 $where = $field; foreach ($where as $k => $val) { - $this->options['multi'][$k][] = $val; + $this->options['multi'][$logic][$k][] = $val; } } elseif ($field && is_string($field)) { // 字符串查询 - $where[$field] = ['null', '']; + $where[$field] = ['null', '']; + $this->options['multi'][$logic][$field][] = $where[$field]; } } elseif (is_array($op)) { $where[$field] = $param; } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { // null查询 - $where[$field] = [$op, '']; + $where[$field] = [$op, '']; + $this->options['multi'][$logic][$field][] = $where[$field]; } elseif (is_null($condition)) { // 字段相等查询 $where[$field] = ['eq', $op]; + if ('AND' != $logic) { + $this->options['multi'][$logic][$field][] = $where[$field]; + } } else { $where[$field] = [$op, $condition, isset($param[2]) ? $param[2] : null]; if ('exp' == strtolower($op) && isset($param[2]) && is_array($param[2])) { @@ -1024,18 +1178,18 @@ class Query $this->bind($param[2]); } // 记录一个字段多次查询条件 - $this->options['multi'][$field][] = $where[$field]; + $this->options['multi'][$logic][$field][] = $where[$field]; } if (!empty($where)) { if (!isset($this->options['where'][$logic])) { $this->options['where'][$logic] = []; } - if (is_string($field) && $this->checkMultiField($field)) { - $where[$field] = $this->options['multi'][$field]; + if (is_string($field) && $this->checkMultiField($field, $logic)) { + $where[$field] = $this->options['multi'][$logic][$field]; } elseif (is_array($field)) { foreach ($field as $key => $val) { - if ($this->checkMultiField($key)) { - $where[$key] = $this->options['multi'][$key]; + if ($this->checkMultiField($key, $logic)) { + $where[$key] = $this->options['multi'][$logic][$key]; } } } @@ -1043,17 +1197,23 @@ class Query } } - // 检查是否存在一个字段多次查询条件 - private function checkMultiField($field) + /** + * 检查是否存在一个字段多次查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return bool + */ + private function checkMultiField($field, $logic) { - return isset($this->options['multi'][$field]) && count($this->options['multi'][$field]) > 1; + return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; } /** * 去除某个查询条件 * @access public - * @param string $field 查询字段 - * @param string $logic 查询逻辑 and or xor + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor * @return $this */ public function removeWhereField($field, $logic = 'AND') @@ -1068,7 +1228,7 @@ class Query /** * 去除查询参数 * @access public - * @param string|bool $option 参数名 true 表示去除所有参数 + * @param string|bool $option 参数名 true 表示去除所有参数 * @return $this */ public function removeOption($option = true) @@ -1115,17 +1275,17 @@ class Query /** * 分页查询 - * @param int|null $listRows 每页数量 - * @param int|bool $simple 简洁模式或者总记录数 - * @param array $config 配置参数 - * page:当前页, - * path:url路径, - * query:url额外参数, - * fragment:url锚点, - * var_page:分页变量, - * list_rows:每页数量 - * type:分页类名 - * @return \think\paginator\Collection + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 + * page:当前页, + * path:url路径, + * query:url额外参数, + * fragment:url锚点, + * var_page:分页变量, + * list_rows:每页数量 + * type:分页类名 + * @return \think\Paginator * @throws DbException */ public function paginate($listRows = null, $simple = false, $config = []) @@ -1134,8 +1294,13 @@ class Query $total = $simple; $simple = false; } - $config = array_merge(Config::get('paginate'), $config); - $listRows = $listRows ?: $config['list_rows']; + if (is_array($listRows)) { + $config = array_merge(Config::get('paginate'), $listRows); + $listRows = $config['list_rows']; + } else { + $config = array_merge(Config::get('paginate'), $config); + $listRows = $listRows ?: $config['list_rows']; + } /** @var Paginator $class */ $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); @@ -1150,9 +1315,9 @@ class Query if (!isset($total) && !$simple) { $options = $this->getOptions(); - if (isset($options['order'])) { - unset($this->options['order']); - } + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + $bind = $this->bind; $total = $this->count(); $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); @@ -1400,7 +1565,7 @@ class Query */ public function fetchPdo($pdo = true) { - $this->options['fetch_class'] = $pdo; + $this->options['fetch_pdo'] = $pdo; return $this; } @@ -1479,7 +1644,7 @@ class Query switch (strtolower($op)) { case 'today': case 'd': - $range = 'today'; + $range = ['today', 'tomorrow']; break; case 'week': case 'w': @@ -1505,6 +1670,8 @@ class Query case 'last year': $range = [mktime(0, 0, 0, 1, 1, $date['year'] - 1), mktime(0, 0, 0, 1, 1, $date['year'])]; break; + default: + $range = $op; } $op = is_array($range) ? 'between' : '>'; } @@ -1735,15 +1902,19 @@ class Query } } $this->via(); - $this->options['with'] = $with; + if (isset($this->options['with'])) { + $this->options['with'] = array_merge($this->options['with'], $with); + } else { + $this->options['with'] = $with; + } return $this; } /** * 关联统计 * @access public - * @param string|array $relation 关联方法名 - * @param bool $subQuery 是否使用子查询 + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 * @return $this */ public function withCount($relation, $subQuery = true) @@ -1800,12 +1971,22 @@ class Query /** * 设置关联查询 * @access public - * @param string $relation 关联名称 + * @param string|array $relation 关联名称 * @return $this */ public function relation($relation) { - $this->options['relation'] = $relation; + if (empty($relation)) { + return $this; + } + if (is_string($relation)) { + $relation = explode(',', $relation); + } + if (isset($this->options['relation'])) { + $this->options['relation'] = array_mrege($this->options['relation'], $relation); + } else { + $this->options['relation'] = $relation; + } return $this; } @@ -1881,11 +2062,20 @@ class Query // 执行操作 $result = $this->execute($sql, $bind); if ($result) { + $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); + $lastInsId = $this->getLastInsID($sequence); + if ($lastInsId) { + $pk = $this->getPk($options); + if (is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + $options['data'] = $data; $this->trigger('after_insert', $options); - } - if ($getLastInsID) { - $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); - return $this->getLastInsID($sequence); + + if ($getLastInsID) { + return $lastInsId; + } } return $result; } @@ -1968,8 +2158,8 @@ class Query $options = $this->parseExpress(); $data = array_merge($options['data'], $data); $pk = $this->getPk($options); - if (isset($options['cache']) && is_string($options['cache'])) { - $key = $options['cache']; + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; } if (empty($options['where'])) { @@ -1998,9 +2188,10 @@ class Query } else { $options['where']['AND'] = $where; } - } elseif (is_string($pk) && isset($options['where']['AND'][$pk]) && is_scalar($options['where']['AND'][$pk])) { - $key = 'think:' . $options['table'] . '|' . $options['where']['AND'][$pk]; + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options); } + // 生成UPDATE SQL语句 $sql = $this->builder->update($data, $options); // 获取参数绑定 @@ -2013,16 +2204,46 @@ class Query if (isset($key) && Cache::get($key)) { // 删除缓存 Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); } // 执行操作 $result = '' == $sql ? 0 : $this->execute($sql, $bind); if ($result) { + if (isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + $options['data'] = $data; $this->trigger('after_update', $options); } return $result; } } + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function getPdo() + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); + } + /** * 查找记录 * @access public @@ -2068,10 +2289,12 @@ class Query // 获取实际执行的SQL语句 return $this->connection->getRealSql($sql, $bind); } + + $options['data'] = $data; if ($resultSet = $this->trigger('before_select', $options)) { } else { // 执行查询操作 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_class']); + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); if ($resultSet instanceof \PDOStatement) { // 返回PDOStatement对象 @@ -2081,40 +2304,42 @@ class Query if (isset($cache)) { // 缓存数据集 - if (isset($cache['tag'])) { - Cache::tag($cache['tag'])->set($key, $resultSet, $cache['expire']); - } else { - Cache::set($key, $resultSet, $cache['expire']); - } + $this->cacheData($key, $resultSet, $cache); } } // 数据列表读取后的处理 if (!empty($this->model)) { // 生成模型对象 - $model = $this->model; + $modelName = $this->model; if (count($resultSet) > 0) { foreach ($resultSet as $key => $result) { /** @var Model $result */ - $result = new $model($result); - $result->isUpdate(true); + $model = new $modelName($result); + $model->isUpdate(true); + // 关联查询 if (!empty($options['relation'])) { - $result->relationQuery($options['relation']); + $model->relationQuery($options['relation']); } // 关联统计 if (!empty($options['with_count'])) { - $result->relationCount($result, $options['with_count']); + $model->relationCount($model, $options['with_count']); } - $resultSet[$key] = $result; + $resultSet[$key] = $model; } if (!empty($options['with'])) { // 预载入 - $result->eagerlyResultSet($resultSet, $options['with'], is_object($resultSet) ? get_class($resultSet) : ''); + $model->eagerlyResultSet($resultSet, $options['with']); } + // 模型数据集转换 + $resultSet = $model->toCollection($resultSet); + } else { + $resultSet = (new $modelName)->toCollection($resultSet); } - // 模型数据集转换 - $resultSet = (new $model)->toCollection($resultSet); + } elseif ('collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); } // 返回结果处理 if (!empty($options['fail']) && count($resultSet) == 0) { @@ -2123,6 +2348,42 @@ class Query return $resultSet; } + /** + * 缓存数据 + * @access public + * @param string $key 缓存标识 + * @param mixed $data 缓存数据 + * @param array $config 缓存参数 + */ + protected function cacheData($key, $data, $config = []) + { + if (isset($config['tag'])) { + Cache::tag($config['tag'])->set($key, $data, $config['expire']); + } else { + Cache::set($key, $data, $config['expire']); + } + } + + /** + * 生成缓存标识 + * @access public + * @param mixed $value 缓存数据 + * @param array $options 缓存参数 + */ + protected function getCacheKey($value, $options) + { + if (is_scalar($value)) { + $data = $value; + } elseif (is_array($value) && 'eq' == strtolower($value[0])) { + $data = $value[1]; + } + if (isset($data)) { + return 'think:' . $options['table'] . '|' . $data; + } else { + return md5(serialize($options)); + } + } + /** * 查找单条记录 * @access public @@ -2142,10 +2403,12 @@ class Query } // 分析查询表达式 $options = $this->parseExpress(); - + $pk = $this->getPk($options); if (!is_null($data)) { // 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); } $options['limit'] = 1; @@ -2155,12 +2418,14 @@ class Query $cache = $options['cache']; if (true === $cache['key'] && !is_null($data) && !is_array($data)) { $key = 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; - } else { - $key = is_string($cache['key']) ? $cache['key'] : md5(serialize($options)); + } elseif (is_string($cache['key'])) { + $key = $cache['key']; + } elseif (!isset($key)) { + $key = md5(serialize($options)); } $result = Cache::get($key); } - if (!$result) { + if (false === $result) { // 生成查询SQL $sql = $this->builder->select($options); // 获取参数绑定 @@ -2169,56 +2434,61 @@ class Query // 获取实际执行的SQL语句 return $this->connection->getRealSql($sql, $bind); } - + if (is_string($pk)) { + if (!is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; + } + $data = $item; + } + } + $options['data'] = $data; // 事件回调 if ($result = $this->trigger('before_find', $options)) { } else { // 执行查询 - $result = $this->query($sql, $bind, $options['master'], $options['fetch_class']); + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); - if ($result instanceof \PDOStatement) { + if ($resultSet instanceof \PDOStatement) { // 返回PDOStatement对象 - return $result; + return $resultSet; } + $result = isset($resultSet[0]) ? $resultSet[0] : null; } if (isset($cache)) { // 缓存数据 - if (isset($cache['tag'])) { - Cache::tag($cache['tag'])->set($key, $result, $cache['expire']); - } else { - Cache::set($key, $result, $cache['expire']); - } + $this->cacheData($key, $result, $cache); } } // 数据处理 - if (!empty($result[0])) { - $data = $result[0]; + if (!empty($result)) { if (!empty($this->model)) { // 返回模型对象 - $model = $this->model; - $data = new $model($data); - $data->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); + $model = $this->model; + $result = new $model($result); + $result->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); // 关联查询 if (!empty($options['relation'])) { - $data->relationQuery($options['relation']); + $result->relationQuery($options['relation']); } // 预载入查询 if (!empty($options['with'])) { - $data->eagerlyResult($data, $options['with'], is_object($result) ? get_class($result) : ''); + $result->eagerlyResult($result, $options['with']); } // 关联统计 if (!empty($options['with_count'])) { - $data->relationCount($data, $options['with_count']); + $result->relationCount($result, $options['with_count']); } } } elseif (!empty($options['fail'])) { $this->throwNotFound($options); - } else { - $data = null; } - return $data; + return $result; } /** @@ -2349,8 +2619,9 @@ class Query { // 分析查询表达式 $options = $this->parseExpress(); - if (isset($options['cache']) && is_string($options['cache'])) { - $key = $options['cache']; + $pk = $this->getPk($options); + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; } if (!is_null($data) && true !== $data) { @@ -2360,6 +2631,8 @@ 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); } if (true !== $data && empty($options['where'])) { @@ -2379,10 +2652,18 @@ class Query if (isset($key) && Cache::get($key)) { // 删除缓存 Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); } // 执行操作 $result = $this->execute($sql, $bind); if ($result) { + if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + $data = $item; + } + $options['data'] = $data; $this->trigger('after_delete', $options); } return $result; @@ -2454,7 +2735,7 @@ class Query $options['strict'] = $this->getConfig('fields_strict'); } - foreach (['master', 'lock', 'fetch_class', 'fetch_sql', 'distinct'] as $name) { + foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) { if (!isset($options[$name])) { $options[$name] = false; } @@ -2482,8 +2763,8 @@ class Query /** * 注册回调方法 * @access public - * @param string $event 事件名 - * @param callable $callback 回调方法 + * @param string $event 事件名 + * @param callable $callback 回调方法 * @return void */ public static function event($event, $callback) @@ -2494,16 +2775,16 @@ class Query /** * 触发事件 * @access protected - * @param string $event 事件名 - * @param mixed $options 当前查询参数 + * @param string $event 事件名 + * @param mixed $params 额外参数 * @return bool */ - protected function trigger($event, $options = []) + protected function trigger($event, $params = []) { $result = false; if (isset(self::$event[$event])) { $callback = self::$event[$event]; - $result = call_user_func_array($callback, [$options, $this]); + $result = call_user_func_array($callback, [$params, $this]); } return $result; } diff --git a/library/think/db/builder/Mysql.php b/library/think/db/builder/Mysql.php index de38fac5..e432e396 100644 --- a/library/think/db/builder/Mysql.php +++ b/library/think/db/builder/Mysql.php @@ -38,6 +38,8 @@ class Mysql extends Builder list($table, $key) = explode('.', $key, 2); if (isset($options['alias'][$table])) { $table = $options['alias'][$table]; + } elseif ('__TABLE__' == $table) { + $table = $this->query->getTable(); } } if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { diff --git a/library/think/db/builder/Pgsql.php b/library/think/db/builder/Pgsql.php index 8b853a2f..67b98b44 100644 --- a/library/think/db/builder/Pgsql.php +++ b/library/think/db/builder/Pgsql.php @@ -57,6 +57,8 @@ class Pgsql extends Builder list($table, $key) = explode('.', $key, 2); if (isset($options['alias'][$table])) { $table = $options['alias'][$table]; + } elseif ('__TABLE__' == $table) { + $table = $this->query->getTable(); } } if (isset($table)) { diff --git a/library/think/db/builder/Sqlite.php b/library/think/db/builder/Sqlite.php index 02d1bf2e..680b4965 100644 --- a/library/think/db/builder/Sqlite.php +++ b/library/think/db/builder/Sqlite.php @@ -62,6 +62,8 @@ class Sqlite extends Builder list($table, $key) = explode('.', $key, 2); if (isset($options['alias'][$table])) { $table = $options['alias'][$table]; + } elseif ('__TABLE__' == $table) { + $table = $this->query->getTable(); } } if (isset($table)) { diff --git a/library/think/db/builder/Sqlsrv.php b/library/think/db/builder/Sqlsrv.php index d2f418f3..99f68409 100644 --- a/library/think/db/builder/Sqlsrv.php +++ b/library/think/db/builder/Sqlsrv.php @@ -75,6 +75,8 @@ class Sqlsrv extends Builder list($table, $key) = explode('.', $key, 2); if (isset($options['alias'][$table])) { $table = $options['alias'][$table]; + } elseif ('__TABLE__' == $table) { + $table = $this->query->getTable(); } } if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { diff --git a/library/think/db/connector/Mysql.php b/library/think/db/connector/Mysql.php index 31435694..0081fb2e 100644 --- a/library/think/db/connector/Mysql.php +++ b/library/think/db/connector/Mysql.php @@ -21,6 +21,8 @@ use think\Log; class Mysql extends Connection { + protected $builder = '\\think\\db\\builder\\Mysql'; + /** * 解析pdo连接的dsn信息 * @access protected @@ -127,4 +129,18 @@ 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/library/think/db/connector/Pgsql.php b/library/think/db/connector/Pgsql.php index 4f8756ca..04c3e782 100644 --- a/library/think/db/connector/Pgsql.php +++ b/library/think/db/connector/Pgsql.php @@ -19,6 +19,7 @@ use think\db\Connection; */ class Pgsql extends Connection { + protected $builder = '\\think\\db\\builder\\Pgsql'; /** * 解析pdo连接的dsn信息 diff --git a/library/think/db/connector/Sqlite.php b/library/think/db/connector/Sqlite.php index 4a08c740..a0e0873c 100644 --- a/library/think/db/connector/Sqlite.php +++ b/library/think/db/connector/Sqlite.php @@ -20,6 +20,8 @@ use think\db\Connection; class Sqlite extends Connection { + protected $builder = '\\think\\db\\builder\\Sqlite'; + /** * 解析pdo连接的dsn信息 * @access protected diff --git a/library/think/db/connector/Sqlsrv.php b/library/think/db/connector/Sqlsrv.php index 18148051..20d3491d 100644 --- a/library/think/db/connector/Sqlsrv.php +++ b/library/think/db/connector/Sqlsrv.php @@ -25,7 +25,7 @@ class Sqlsrv extends Connection PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_STRINGIFY_FETCHES => false, ]; - + protected $builder = '\\think\\db\\builder\\Sqlsrv'; /** * 解析pdo连接的dsn信息 * @access protected diff --git a/library/think/model/Collection.php b/library/think/model/Collection.php new file mode 100644 index 00000000..08e11ad8 --- /dev/null +++ b/library/think/model/Collection.php @@ -0,0 +1,79 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; + +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function load($relation) + { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->each(function ($model) use ($hidden, $override) { + /** @var Model $model */ + $model->hidden($hidden, $override); + }); + return $this; + } + + /** + * 设置需要输出的属性 + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->each(function ($model) use ($visible, $override) { + /** @var Model $model */ + $model->visible($visible, $override); + }); + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->each(function ($model) use ($append, $override) { + /** @var Model $model */ + $model->append($append, $override); + }); + return $this; + } + +} diff --git a/library/think/model/Merge.php b/library/think/model/Merge.php index e1070d1d..01f403f8 100644 --- a/library/think/model/Merge.php +++ b/library/think/model/Merge.php @@ -11,7 +11,6 @@ namespace think\model; -use think\Db; use think\db\Query; use think\Model; @@ -40,9 +39,9 @@ class Merge extends Model /** * 查找单条记录 * @access public - * @param mixed $data 主键值或者查询条件(闭包) - * @param string $with 关联预查询 - * @param bool $cache 是否缓存 + * @param mixed $data 主键值或者查询条件(闭包) + * @param string|array $with 关联预查询 + * @param bool $cache 是否缓存 * @return \think\Model */ public static function get($data = null, $with = [], $cache = false) @@ -78,11 +77,11 @@ class Merge extends Model /** * 获取关联模型的字段 并解决混淆 * @access protected - * @param \think\db\Query $query 查询对象 - * @param string $name 模型名称 - * @param string $table 关联表名称 - * @param array $map 字段映射 - * @param array $fields 查询字段 + * @param \think\db\Query $query 查询对象 + * @param string $name 模型名称 + * @param string $table 关联表名称 + * @param array $map 字段映射 + * @param array $fields 查询字段 * @return array */ protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) @@ -104,8 +103,9 @@ class Merge extends Model /** * 查找所有记录 * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param string $with 关联预查询 + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache * @return array|false|string */ public static function all($data = null, $with = [], $cache = false) @@ -118,10 +118,10 @@ class Merge extends Model /** * 处理写入的模型数据 * @access public - * @param string $model 模型名称 - * @param array $data 数据 - * @param bool $insert 是否新增 - * @return void + * @param string $model 模型名称 + * @param array $data 数据 + * @param bool $insert 是否新增 + * @return array */ protected function parseData($model, $data, $insert = false) { @@ -144,10 +144,11 @@ class Merge extends Model /** * 保存模型数据 以及关联数据 * @access public - * @param mixed $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 - * @return integer|false + * @param mixed $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return false|int + * @throws \Exception */ public function save($data = [], $where = [], $sequence = null) { @@ -158,7 +159,7 @@ class Merge extends Model } // 数据对象赋值 foreach ($data as $key => $value) { - $this->setAttr($key, $value); + $this->setAttr($key, $value, $data); } if (!empty($where)) { $this->isUpdate = true; @@ -278,7 +279,8 @@ class Merge extends Model /** * 删除当前的记录 并删除关联数据 * @access public - * @return integer + * @return int + * @throws \Exception */ public function delete() { diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index e940a78d..3d56091b 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -15,24 +15,24 @@ use think\db\Query; use think\Exception; use think\Model; +/** + * Class Relation + * @package think\model + * + * @mixin Query + */ abstract class Relation { // 父模型对象 protected $parent; /** @var Model 当前关联的模型类 */ protected $model; + /** @var Query 关联模型查询对象 */ + protected $query; // 关联表外键 protected $foreignKey; // 关联表主键 protected $localKey; - // 数据表别名 - protected $alias; - // 当前关联的JOIN类型 - protected $joinType; - // 关联模型查询对象 - protected $query; - // 关联查询条件 - protected $where; // 关联查询参数 protected $option; // 基础查询 @@ -71,25 +71,12 @@ abstract class Relation /** * 封装关联数据集 * @access public - * @param array $resultSet 数据集 - * @param string $class 数据集类名 + * @param array $resultSet 数据集 * @return mixed */ - protected function resultSetBuild($resultSet, $class = '') + protected function resultSetBuild($resultSet) { - return $class ? new $class($resultSet) : $resultSet; - } - - /** - * 设置当前关联定义的数据表别名 - * @access public - * @param array $alias 别名定义 - * @return $this - */ - public function setAlias($alias) - { - $this->alias = $alias; - return $this; + return (new $this->model)->toCollection($resultSet); } /** @@ -103,6 +90,13 @@ abstract class Relation return $this; } + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + abstract protected function baseQuery(); + public function __call($method, $args) { if ($this->query) { diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php index 373f6cbf..6996526d 100644 --- a/library/think/model/relation/BelongsTo.php +++ b/library/think/model/relation/BelongsTo.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use think\Loader; use think\Model; class BelongsTo extends OneToOne @@ -22,29 +23,71 @@ class BelongsTo extends OneToOne * @param string $model 模型名 * @param string $foreignKey 关联外键 * @param string $localKey 关联主键 - * @param array $alias 别名定义 * @param string $joinType JOIN类型 */ - public function __construct(Model $parent, $model, $foreignKey, $localKey, $alias = [], $joinType = 'INNER') + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') { $this->parent = $parent; $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; - $this->alias = $alias; $this->joinType = $joinType; $this->query = (new $model)->db(); } /** * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 * @access public + * @return array|false|\PDOStatement|string|Model */ - public function getRelation() + public function getRelation($subRelation = '', $closure = null) { $foreignKey = $this->foreignKey; - $localKey = $this->localKey; - return $this->query->where($localKey, $this->parent->$foreignKey)->find(); + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + return $this->query->where($this->localKey, $this->parent->$foreignKey)->relation($subRelation)->find(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + return $this->parent->db()->alias($model) + ->field($model . '.*') + ->join($table . ' ' . $relation, $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->where($where); } /** @@ -54,10 +97,9 @@ class BelongsTo extends OneToOne * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 * @return void */ - protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure, $class) + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; @@ -71,26 +113,29 @@ class BelongsTo extends OneToOne } if (!empty($range)) { - $this->where[$localKey] = ['in', $range]; - $data = $this->eagerlyWhere($this, [ + $data = $this->eagerlyWhere($this, [ $localKey => [ 'in', $range, ], ], $localKey, $relation, $subRelation, $closure); - + // 关联属性名 + $attr = Loader::parseName($relation); // 关联数据封装 foreach ($resultSet as $result) { + // 关联模型 if (!isset($data[$result->$foreignKey])) { - $data[$result->$foreignKey] = []; + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; } - $relationModel = $this->resultSetBuild($data[$result->$foreignKey], $class); - if (!empty($this->bindAttr)) { + + if ($relationModel && !empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); } // 设置关联属性 - $result->setAttr($relation, $relationModel); + $result->setAttr($attr, $relationModel); } } } @@ -102,25 +147,25 @@ class BelongsTo extends OneToOne * @param string $relation 当前关联名 * @param string $subRelation 子关联名 * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 * @return void */ - protected function eagerlyOne(&$result, $relation, $subRelation, $closure, $class) + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; $data = $this->eagerlyWhere($this, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); - // 关联数据封装 + // 关联模型 if (!isset($data[$result->$foreignKey])) { - $data[$result->$foreignKey] = []; + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; } - $relationModel = $this->resultSetBuild($data[$result->$foreignKey], $class); - if (!empty($this->bindAttr)) { + if ($relationModel && !empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); } // 设置关联属性 - $result->setAttr($relation, $relationModel); + $result->setAttr(Loader::parseName($relation), $relationModel); } } diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php index 88079f56..b7758e9f 100644 --- a/library/think/model/relation/BelongsToMany.php +++ b/library/think/model/relation/BelongsToMany.php @@ -11,9 +11,9 @@ namespace think\model\relation; -use think\Db; use think\db\Query; use think\Exception; +use think\Loader; use think\Model; use think\model\Pivot; use think\model\Relation; @@ -26,37 +26,40 @@ class BelongsToMany extends Relation /** * 构造函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $table 中间表名 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 * @param string $foreignKey 关联模型外键 - * @param string $localKey 当前模型关联键 - * @param array $alias 别名定义 + * @param string $localKey 当前模型关联键 */ - public function __construct(Model $parent, $model, $table, $foreignKey, $localKey, $alias = []) + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) { $this->parent = $parent; $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; $this->middle = $table; - $this->alias = $alias; $this->query = (new $model)->db(); } /** * 延迟获取关联数据 - * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection */ - public function getRelation() + public function getRelation($subRelation = '', $closure = null) { $foreignKey = $this->foreignKey; $localKey = $this->localKey; $middle = $this->middle; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } // 关联查询 $pk = $this->parent->getPk(); $condition['pivot.' . $localKey] = $this->parent->$pk; - $result = $this->belongsToManyQuery($middle, $foreignKey, $localKey, $condition)->select(); + $result = $this->belongsToManyQuery($middle, $foreignKey, $localKey, $condition)->relation($subRelation)->select(); foreach ($result as $set) { $pivot = []; foreach ($set->getData() as $key => $val) { @@ -73,17 +76,41 @@ class BelongsToMany extends Relation return $result; } + /** + * 根据关联条件查询当前模型 + * @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 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; @@ -105,14 +132,15 @@ class BelongsToMany extends Relation $range, ], ], $relation, $subRelation); - + // 关联属性名 + $attr = Loader::parseName($relation); // 关联数据封装 foreach ($resultSet as $result) { if (!isset($data[$result->$pk])) { $data[$result->$pk] = []; } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); + $result->setAttr($attr, $this->resultSetBuild($data[$result->$pk])); } } } @@ -120,14 +148,13 @@ class BelongsToMany extends Relation /** * 预载入关联查询(单个数据) * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); if (isset($result->$pk)) { @@ -139,15 +166,15 @@ class BelongsToMany extends Relation if (!isset($data[$pk])) { $data[$pk] = []; } - $result->setAttr($relation, $this->resultSetBuild($data[$pk], $class)); + $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); } } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 * @return integer */ public function relationCount($result, $closure) @@ -164,20 +191,25 @@ class BelongsToMany extends Relation /** * 获取关联统计子查询 * @access public - * @param \Closure $closure 闭包 + * @param \Closure $closure 闭包 * @return string */ public function getRelationCountQuery($closure) { - return $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => ['exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk()]])->fetchSql()->count(); + return $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, [ + 'pivot.' . $this->localKey => [ + 'exp', + '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), + ], + ])->fetchSql()->count(); } /** * 多对多 关联模型预查询 * @access public - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 * @return array */ protected function eagerlyManyToMany($where, $relation, $subRelation = '') @@ -207,10 +239,10 @@ class BelongsToMany extends Relation /** * BELONGS TO MANY 关联查询 * @access public - * @param string $table 中间表名 - * @param string $foreignKey 关联模型关联键 - * @param string $localKey 当前模型关联键 - * @param array $condition 关联查询条件 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 * @return Query */ protected function belongsToManyQuery($table, $foreignKey, $localKey, $condition = []) @@ -227,8 +259,8 @@ class BelongsToMany extends Relation /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @param array $pivot 中间表额外数据 + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 * @return integer */ public function save($data, array $pivot = []) @@ -240,9 +272,9 @@ class BelongsToMany extends Relation /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 - * @param array $pivot 中间表额外数据 - * @param bool $samePivot 额外数据是否相同 + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 * @return integer */ public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) @@ -262,9 +294,10 @@ class BelongsToMany extends Relation /** * 附加关联的一个中间表数据 * @access public - * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return integer + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception */ public function attach($data, $pivot = []) { @@ -293,7 +326,12 @@ class BelongsToMany extends Relation $ids = (array) $id; foreach ($ids as $id) { $pivot[$this->foreignKey] = $id; - $result = $this->query->table($this->middle)->insert($pivot, true); + $this->query->table($this->middle)->insert($pivot, true); + $result[] = new Pivot($pivot, $this->middle); + } + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; } return $result; } else { @@ -304,8 +342,8 @@ class BelongsToMany extends Relation /** * 解除关联的一个中间表数据 * @access public - * @param integer|array $data 数据 可以使用关联对象的主键 - * @param bool $relationDel 是否同时删除关联表数据 + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 * @return integer */ public function detach($data = null, $relationDel = false) diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php index be130287..364d793d 100644 --- a/library/think/model/relation/HasMany.php +++ b/library/think/model/relation/HasMany.php @@ -13,6 +13,7 @@ namespace think\model\relation; use think\Db; use think\db\Query; +use think\Loader; use think\Model; use think\model\Relation; @@ -21,42 +22,44 @@ class HasMany extends Relation /** * 构造函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 + * @param string $localKey 关联主键 */ - public function __construct(Model $parent, $model, $foreignKey, $localKey, $alias = []) + public function __construct(Model $parent, $model, $foreignKey, $localKey) { $this->parent = $parent; $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; - $this->alias = $alias; $this->query = (new $model)->db(); } /** * 延迟获取关联数据 - * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection */ - public function getRelation() + public function getRelation($subRelation = '', $closure = null) { - return $this->select(); + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + return $this->relation($subRelation)->select(); } /** * 预载入关联查询 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $localKey = $this->localKey; $range = []; @@ -68,35 +71,34 @@ class HasMany extends Relation } if (!empty($range)) { - $this->where[$this->foreignKey] = ['in', $range]; - $data = $this->eagerlyOneToMany($this, [ + $data = $this->eagerlyOneToMany($this, [ $this->foreignKey => [ 'in', $range, ], ], $relation, $subRelation, $closure); - + // 关联属性名 + $attr = Loader::parseName($relation); // 关联数据封装 foreach ($resultSet as $result) { if (!isset($data[$result->$localKey])) { $data[$result->$localKey] = []; } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$localKey], $class)); + $result->setAttr($attr, $this->resultSetBuild($data[$result->$localKey])); } } } /** * 预载入关联查询 - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $localKey = $this->localKey; @@ -106,15 +108,15 @@ class HasMany extends Relation if (!isset($data[$result->$localKey])) { $data[$result->$localKey] = []; } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$localKey], $class)); + $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); } } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 * @return integer */ public function relationCount($result, $closure) @@ -133,7 +135,7 @@ class HasMany extends Relation /** * 创建关联统计子查询 * @access public - * @param \Closure $closure 闭包 + * @param \Closure $closure 闭包 * @return string */ public function getRelationCountQuery($closure) @@ -142,17 +144,22 @@ class HasMany extends Relation call_user_func_array($closure, [ & $this->query]); } - return $this->query->where([$this->foreignKey => ['exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk()]])->fetchSql()->count(); + return $this->query->where([ + $this->foreignKey => [ + 'exp', + '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), + ], + ])->fetchSql()->count(); } /** * 一对多 关联模型预查询 * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool $closure + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool $closure * @return array */ protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) @@ -175,7 +182,7 @@ class HasMany extends Relation /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 * @return integer */ public function save($data) @@ -192,7 +199,7 @@ class HasMany extends Relation /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 + * @param array $dataSet 数据集 * @return integer */ public function saveAll(array $dataSet) @@ -207,17 +214,17 @@ class HasMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param Model $model 模型对象 - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 * @return Query */ - public function has($model, $operator = '>=', $count = 1, $id = '*') + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') { $table = $this->query->getTable(); - return $model->db()->alias('a') - ->join($table . ' b', 'a.' . $this->localKey . '=b.' . $this->foreignKey, $this->joinType) + return $this->parent->db()->alias('a') + ->join($table . ' b', 'a.' . $this->localKey . '=b.' . $this->foreignKey, $joinType) ->group('b.' . $this->foreignKey) ->having('count(' . $id . ')' . $operator . $count); } @@ -225,24 +232,25 @@ class HasMany extends Relation /** * 根据关联条件查询当前模型 * @access public - * @param Model $model 模型对象 - * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $where 查询条件(数组或者闭包) * @return Query */ - public function hasWhere($model, $where = []) + public function hasWhere($where = []) { - $table = $this->query->getTable(); + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); if (is_array($where)) { foreach ($where as $key => $val) { if (false === strpos($key, '.')) { - $where['b.' . $key] = $val; + $where[$relation . '.' . $key] = $val; unset($where[$key]); } } } - return $model->db()->alias('a') - ->field('a.*') - ->join($table . ' b', 'a.' . $this->localKey . '=b.' . $this->foreignKey, $this->joinType) + return $this->parent->db()->alias($model) + ->field($model . '.*') + ->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) ->where($where); } @@ -254,9 +262,7 @@ class HasMany extends Relation protected function baseQuery() { if (empty($this->baseQuery)) { - if (isset($this->where)) { - $this->query->where($this->where); - } elseif (isset($this->parent->{$this->localKey})) { + if (isset($this->parent->{$this->localKey})) { // 关联查询带入关联条件 $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); } diff --git a/library/think/model/relation/HasManyThrough.php b/library/think/model/relation/HasManyThrough.php index 6c4bdefd..dd6f6a55 100644 --- a/library/think/model/relation/HasManyThrough.php +++ b/library/think/model/relation/HasManyThrough.php @@ -13,6 +13,7 @@ namespace think\model\relation; use think\Db; use think\db\Query; +use think\Exception; use think\Loader; use think\Model; use think\model\Relation; @@ -26,16 +27,15 @@ class HasManyThrough extends Relation /** * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $through 中间模型名 - * @param string $firstkey 关联外键 - * @param string $secondKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 关联主键 */ - public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey, $alias = []) + public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) { $this->parent = $parent; $this->model = $model; @@ -43,54 +43,86 @@ class HasManyThrough extends Relation $this->foreignKey = $foreignKey; $this->throughKey = $throughKey; $this->localKey = $localKey; - $this->alias = $alias; $this->query = (new $model)->db(); } /** * 延迟获取关联数据 - * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection */ - public function getRelation() + public function getRelation($subRelation = '', $closure = null) { - return $this->select(); + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + return $this->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @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 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param string $class 数据集对象名 为空表示数组 * @return void */ public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) - {} + { + } /** * 预载入关联查询 返回模型对象 * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param string $class 数据集对象名 为空表示数组 * @return void */ public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) - {} + { + } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 * @return integer */ public function relationCount($result, $closure) - {} + { + } /** * 执行基础查询(进执行一次) diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php index 49b5bfab..95a401bf 100644 --- a/library/think/model/relation/HasOne.php +++ b/library/think/model/relation/HasOne.php @@ -11,6 +11,8 @@ namespace think\model\relation; +use think\db\Query; +use think\Loader; use think\Model; class HasOne extends OneToOne @@ -18,72 +20,90 @@ class HasOne extends OneToOne /** * 构造函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 + * @param string $localKey 关联主键 + * @param string $joinType JOIN类型 */ - public function __construct(Model $parent, $model, $foreignKey, $localKey, $alias = [], $joinType = 'INNER') + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') { $this->parent = $parent; $this->model = $model; $this->foreignKey = $foreignKey; $this->localKey = $localKey; - $this->alias = $alias; $this->joinType = $joinType; $this->query = (new $model)->db(); } /** * 延迟获取关联数据 - * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return array|false|\PDOStatement|string|Model */ - public function getRelation() + public function getRelation($subRelation = '', $closure = null) { // 执行关联定义方法 $localKey = $this->localKey; - + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } // 判断关联类型执行查询 - return $this->query->where($this->foreignKey, $this->parent->$localKey)->find(); + return $this->query->where($this->foreignKey, $this->parent->$localKey)->relation($subRelation)->find(); } /** * 根据关联条件查询当前模型 * @access public - * @param Model $model 模型对象 - * @param mixed $where 查询条件(数组或者闭包) * @return Query */ - public function hasWhere($model, $where = []) + public function has() { - $table = $this->query->getTable(); + $table = $this->query->getTable(); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + return $this->parent->db()->alias('a') + ->whereExists(function ($query) use ($table, $localKey, $foreignKey) { + $query->table([$table => 'b'])->field('b.' . $foreignKey)->whereExp('a.' . $localKey, '=b.' . $foreignKey); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public function hasWhere($where = []) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); if (is_array($where)) { foreach ($where as $key => $val) { if (false === strpos($key, '.')) { - $where['b.' . $key] = $val; + $where[$relation . '.' . $key] = $val; unset($where[$key]); } } } - return $model->db()->alias('a') - ->field('a.*') - ->join($table . ' b', 'a.' . $this->localKey . '=b.' . $this->foreignKey, $this->joinType) + return $this->parent->db()->alias($model) + ->field($model . '.*') + ->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) ->where($where); } /** * 预载入关联查询(数据集) * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure, $class) + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; @@ -97,26 +117,28 @@ class HasOne extends OneToOne } if (!empty($range)) { - $this->where[$foreignKey] = ['in', $range]; - $data = $this->eagerlyWhere($this, [ + $data = $this->eagerlyWhere($this, [ $foreignKey => [ 'in', $range, ], ], $foreignKey, $relation, $subRelation, $closure); - + // 关联属性名 + $attr = Loader::parseName($relation); // 关联数据封装 foreach ($resultSet as $result) { + // 关联模型 if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; } - $relationModel = $this->resultSetBuild($data[$result->$localKey], $class); - if (!empty($this->bindAttr)) { + if ($relationModel && !empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); } // 设置关联属性 - $result->setAttr($relation, $relationModel); + $result->setAttr($attr, $relationModel); } } } @@ -124,28 +146,30 @@ class HasOne extends OneToOne /** * 预载入关联查询(数据) * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - protected function eagerlyOne(&$result, $relation, $subRelation, $closure, $class) + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) { $localKey = $this->localKey; $foreignKey = $this->foreignKey; $data = $this->eagerlyWhere($this, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); - // 关联数据封装 + + // 关联模型 if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; } - $relationModel = $this->resultSetBuild($data[$result->$localKey], $class); - if (!empty($this->bindAttr)) { + + if ($relationModel && !empty($this->bindAttr)) { // 绑定关联属性 $this->bindAttr($relationModel, $result, $this->bindAttr); } - $result->setAttr($relation, $relationModel); + $result->setAttr(Loader::parseName($relation), $relationModel); } } diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php index 79aaf02e..58e44135 100644 --- a/library/think/model/relation/MorphMany.php +++ b/library/think/model/relation/MorphMany.php @@ -13,6 +13,8 @@ namespace think\model\relation; use think\Db; use think\db\Query; +use think\Exception; +use think\Loader; use think\Model; use think\model\Relation; @@ -27,11 +29,11 @@ class MorphMany extends Relation /** * 构造函数 * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $morphKey 关联外键 + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 * @param string $morphType 多态字段名 - * @param string $type 多态类型 + * @param string $type 多态类型 */ public function __construct(Model $parent, $model, $morphKey, $morphType, $type) { @@ -45,24 +47,53 @@ class MorphMany extends Relation /** * 延迟获取关联数据 - * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection */ - public function getRelation() + public function getRelation($subRelation = '', $closure = null) { - return $this->select(); + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + return $this->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @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') + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @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 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $morphType = $this->morphType; $morphKey = $this->morphKey; @@ -77,19 +108,18 @@ class MorphMany extends Relation } if (!empty($range)) { - $this->where[$morphKey] = ['in', $range]; - $this->where[$morphType] = $type; - $data = $this->eagerlyMorphToMany([ + $data = $this->eagerlyMorphToMany([ $morphKey => ['in', $range], $morphType => $type, ], $relation, $subRelation, $closure); - + // 关联属性名 + $attr = Loader::parseName($relation); // 关联数据封装 foreach ($resultSet as $result) { if (!isset($data[$result->$pk])) { $data[$result->$pk] = []; } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); + $result->setAttr($attr, $this->resultSetBuild($data[$result->$pk])); } } } @@ -97,27 +127,29 @@ class MorphMany extends Relation /** * 预载入关联查询 * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $pk = $result->getPk(); if (isset($result->$pk)) { - $data = $this->eagerlyMorphToMany([$this->morphKey => $result->$pk, $this->morphType => $this->type], $relation, $subRelation, $closure); - $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); + $data = $this->eagerlyMorphToMany([ + $this->morphKey => $result->$pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + $result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); } } /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 * @return integer */ public function relationCount($result, $closure) @@ -136,7 +168,7 @@ class MorphMany extends Relation /** * 获取关联统计子查询 * @access public - * @param \Closure $closure 闭包 + * @param \Closure $closure 闭包 * @return string */ public function getRelationCountQuery($closure) @@ -145,16 +177,22 @@ class MorphMany extends Relation call_user_func_array($closure, [ & $this->query]); } - return $this->query->where([$this->morphKey => ['exp', '=' . $this->parent->getTable() . '.' . $this->parent->getPk()], $this->morphType => $this->type])->fetchSql()->count(); + return $this->query->where([ + $this->morphKey => [ + 'exp', + '=' . $this->parent->getTable() . '.' . $this->parent->getPk(), + ], + $this->morphType => $this->type, + ])->fetchSql()->count(); } /** * 多态一对多 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 * @return array */ protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) @@ -176,7 +214,7 @@ class MorphMany extends Relation /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 * @return integer */ public function save($data) @@ -196,7 +234,7 @@ class MorphMany extends Relation /** * 批量保存当前关联数据对象 * @access public - * @param array $dataSet 数据集 + * @param array $dataSet 数据集 * @return integer */ public function saveAll(array $dataSet) diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php index 55e03ac3..6c31a752 100644 --- a/library/think/model/relation/MorphTo.php +++ b/library/think/model/relation/MorphTo.php @@ -11,6 +11,7 @@ namespace think\model\relation; +use think\Exception; use think\Loader; use think\Model; use think\model\Relation; @@ -20,14 +21,16 @@ class MorphTo extends Relation // 多态字段 protected $morphKey; protected $morphType; + // 多态别名 + protected $alias; /** * 构造函数 * @access public - * @param Model $parent 上级模型对象 + * @param Model $parent 上级模型对象 * @param string $morphType 多态字段名 - * @param string $morphKey 外键名 - * @param array $alias 多态别名定义 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 */ public function __construct(Model $parent, $morphType, $morphKey, $alias = []) { @@ -39,9 +42,11 @@ class MorphTo extends Relation /** * 延迟获取关联数据 - * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return mixed */ - public function getRelation() + public function getRelation($subRelation = '', $closure = null) { $morphKey = $this->morphKey; $morphType = $this->morphType; @@ -49,7 +54,32 @@ class MorphTo extends Relation $model = $this->parseModel($this->parent->$morphType); // 主键数据 $pk = $this->parent->$morphKey; - return (new $model)->find($pk); + return (new $model)->relation($subRelation)->find($pk); + } + + /** + * 根据关联条件查询当前模型 + * @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'); } /** @@ -72,17 +102,29 @@ class MorphTo extends Relation return $model; } + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + /** * 预载入关联查询 * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void + * @throws Exception */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { $morphKey = $this->morphKey; $morphType = $this->morphType; @@ -95,6 +137,8 @@ class MorphTo extends Relation } if (!empty($range)) { + // 关联属性名 + $attr = Loader::parseName($relation); foreach ($range as $key => $val) { // 多态类型映射 $model = $this->parseModel($key); @@ -107,10 +151,12 @@ class MorphTo extends Relation } foreach ($resultSet as $result) { if ($key == $result->$morphType) { + // 关联模型 if (!isset($data[$result->$morphKey])) { - $data[$result->$morphKey] = []; + throw new Exception('relation data not exists :' . $this->model); + } else { + $result->setAttr($attr, $data[$result->$morphKey]); } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$morphKey], $class)); } } } @@ -120,14 +166,13 @@ class MorphTo extends Relation /** * 预载入关联查询 * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { $morphKey = $this->morphKey; $morphType = $this->morphType; @@ -139,20 +184,21 @@ class MorphTo extends Relation /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 * @return integer */ public function relationCount($result, $closure) - {} + { + } /** * 多态MorphTo 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 + * @access public + * @param object $model 关联模型对象 + * @param string $relation 关联名 + * @param $result + * @param string $subRelation 子关联 * @return void */ protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') @@ -163,7 +209,7 @@ class MorphTo extends Relation if ($data) { $data->isUpdate(true); } - $result->setAttr($relation, $data ?: null); + $result->setAttr(Loader::parseName($relation), $data ?: null); } /** diff --git a/library/think/model/relation/OneToOne.php b/library/think/model/relation/OneToOne.php index ef21bee0..18b92225 100644 --- a/library/think/model/relation/OneToOne.php +++ b/library/think/model/relation/OneToOne.php @@ -17,27 +17,46 @@ use think\Loader; use think\Model; use think\model\Relation; +/** + * Class OneToOne + * @package think\model\relation + * + */ abstract class OneToOne extends Relation { - // 预载入方式 - protected $eagerlyType = 0; + // 预载入方式 0 -JOIN 1 -IN + protected $eagerlyType = 1; + // 当前关联的JOIN类型 + protected $joinType; // 要绑定的属性 protected $bindAttr = []; + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType($type) + { + $this->joinType = $type; + return $this; + } + /** * 预载入关联查询(JOIN方式) * @access public - * @param Query $query 查询对象 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param \Closure $closure 闭包条件 - * @param bool $first + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包条件 + * @param bool $first * @return void */ public function eagerly(Query $query, $relation, $subRelation, $closure, $first) { $name = Loader::parseName(basename(str_replace('\\', '/', $query->getModel()))); - $alias = isset($this->alias[$name]) ? $this->alias[$name] : $name; + $alias = $name; if ($first) { $table = $query->getTable(); $query->table([$table => $alias]); @@ -52,8 +71,7 @@ abstract class OneToOne extends Relation // 预载入封装 $joinTable = $this->query->getTable(); - $joinName = Loader::parseName(basename(str_replace('\\', '/', $this->model))); - $joinAlias = isset($this->alias[$joinName]) ? $this->alias[$joinName] : $relation; + $joinAlias = $relation; $query->via($joinAlias); if ($this instanceof BelongsTo) { @@ -64,9 +82,8 @@ abstract class OneToOne extends Relation if ($closure) { // 执行闭包查询 - call_user_func_array($closure, [ & $query]); - //指定获取关联的字段 - //需要在 回调中 调方法 withField 方法,如 + call_user_func_array($closure, [& $query]); + // 使用withField指定获取关联的字段,如 // $query->where(['id'=>1])->withField('id,name'); if ($query->getOptions('with_field')) { $field = $query->getOptions('with_field'); @@ -80,21 +97,40 @@ abstract class OneToOne extends Relation $query->field($field, false, $joinTable, $joinAlias, $relation . '__'); } + /** + * 预载入关联查询(数据集) + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据) + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); + /** * 预载入关联查询(数据集) * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) { if (1 == $this->eagerlyType) { // IN查询 - $this->eagerlySet($resultSet, $relation, $subRelation, $closure, $class); + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); } else { // 模型关联组装 foreach ($resultSet as $result) { @@ -106,18 +142,17 @@ abstract class OneToOne extends Relation /** * 预载入关联查询(数据) * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @param string $class 数据集对象名 为空表示数组 + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 * @return void */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + public function eagerlyResult(&$result, $relation, $subRelation, $closure) { if (1 == $this->eagerlyType) { // IN查询 - $this->eagerlyOne($result, $relation, $subRelation, $closure, $class); + $this->eagerlyOne($result, $relation, $subRelation, $closure); } else { // 模型关联组装 $this->match($this->model, $relation, $result); @@ -127,7 +162,7 @@ abstract class OneToOne extends Relation /** * 保存(新增)当前关联数据对象 * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 * @return integer */ public function save($data) @@ -144,8 +179,8 @@ abstract class OneToOne extends Relation /** * 设置预载入方式 * @access public - * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 - * @return this + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @return $this */ public function setEagerlyType($type) { @@ -167,8 +202,8 @@ abstract class OneToOne extends Relation /** * 绑定关联表的属性到父模型属性 * @access public - * @param mixed $attr 要绑定的属性列表 - * @return this + * @param mixed $attr 要绑定的属性列表 + * @return $this */ public function bind($attr) { @@ -182,19 +217,20 @@ abstract class OneToOne extends Relation /** * 关联统计 * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 * @return integer */ public function relationCount($result, $closure) - {} + { + } /** * 一对一 关联模型预查询拼装 * @access public - * @param string $model 模型名称 - * @param string $relation 关联名 - * @param Model $result 模型对象实例 + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 * @return void */ protected function match($model, $relation, &$result) @@ -215,16 +251,17 @@ abstract class OneToOne extends Relation $this->bindAttr($relationModel, $result, $this->bindAttr); } } - $result->setAttr($relation, !isset($relationModel) ? null : $relationModel->isUpdate(true)); + $result->setAttr(Loader::parseName($relation), !isset($relationModel) ? null : $relationModel->isUpdate(true)); } /** * 绑定关联属性到父模型 * @access protected - * @param Model $model 关联模型对象 - * @param Model $result 父模型对象 - * @param array $bindAttr 绑定属性 + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 + * @param array $bindAttr 绑定属性 * @return void + * @throws Exception */ protected function bindAttr($model, &$result, $bindAttr) { @@ -241,26 +278,29 @@ abstract class OneToOne extends Relation /** * 一对一 关联模型预查询(IN方式) * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $key 关联键名 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool $closure + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure * @return array */ protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) { // 预载入关联查询 支持嵌套预载入 if ($closure) { - call_user_func_array($closure, [ & $model]); + call_user_func_array($closure, [& $model]); + if ($field = $model->getOptions('with_field')) { + $model->field($field)->removeOption('with_field'); + } } $list = $model->where($where)->with($subRelation)->select(); // 组装模型数据 $data = []; foreach ($list as $set) { - $data[$set->$key][] = $set; + $data[$set->$key] = $set; } return $data; } diff --git a/library/think/paginator/Collection.php b/library/think/paginator/Collection.php deleted file mode 100644 index 0c877388..00000000 --- a/library/think/paginator/Collection.php +++ /dev/null @@ -1,74 +0,0 @@ - -// +---------------------------------------------------------------------- - -namespace think\paginator; - -use Exception; -use think\Paginator; - -/** - * Class Collection - * @package think\paginator - * @method integer total() - * @method integer listRows() - * @method integer currentPage() - * @method string render() - * @method Paginator fragment($fragment) - * @method Paginator appends($key, $value) - * @method integer lastPage() - * @method boolean hasPages() - */ -class Collection extends \think\Collection -{ - - /** @var Paginator */ - protected $paginator; - - public function __construct($items = [], Paginator $paginator = null) - { - $this->paginator = $paginator; - parent::__construct($items); - } - - public static function make($items = [], Paginator $paginator = null) - { - return new static($items, $paginator); - } - - public function toArray() - { - if ($this->paginator) { - try { - $total = $this->total(); - } catch (Exception $e) { - $total = null; - } - - return [ - 'total' => $total, - 'per_page' => $this->listRows(), - 'current_page' => $this->currentPage(), - 'data' => parent::toArray() - ]; - } else { - return parent::toArray(); - } - } - - public function __call($method, $args) - { - if ($this->paginator && method_exists($this->paginator, $method)) { - return call_user_func_array([$this->paginator, $method], $args); - } else { - throw new Exception('method not exists:' . __CLASS__ . '->' . $method); - } - } -} diff --git a/library/think/template/taglib/Cx.php b/library/think/template/taglib/Cx.php index 1a23c764..af7a54c8 100644 --- a/library/think/template/taglib/Cx.php +++ b/library/think/template/taglib/Cx.php @@ -98,7 +98,7 @@ class Cx extends Taglib $name = $this->autoBuildVar($name); } - $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection): $' . $key . ' = 0;'; + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; // 设置了输出数组长度 if (0 != $offset || 'null' != $length) { $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; @@ -158,7 +158,7 @@ class Cx extends Taglib } else { $name = $this->autoBuildVar($name); } - $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection): '; + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; // 设置了输出数组长度 if (0 != $offset || 'null' != $length) { if (!isset($var)) { @@ -431,7 +431,7 @@ class Cx extends Taglib { $name = $tag['name']; $name = $this->autoBuildVar($name); - $parseStr = 'isEmpty())): ?>' . $content . ''; + $parseStr = 'isEmpty())): ?>' . $content . ''; return $parseStr; } @@ -448,7 +448,7 @@ class Cx extends Taglib { $name = $tag['name']; $name = $this->autoBuildVar($name); - $parseStr = 'isEmpty()))): ?>' . $content . ''; + $parseStr = 'isEmpty()))): ?>' . $content . ''; return $parseStr; } diff --git a/library/traits/controller/Jump.php b/library/traits/controller/Jump.php index 51f4281f..6e6f2dec 100644 --- a/library/traits/controller/Jump.php +++ b/library/traits/controller/Jump.php @@ -131,16 +131,17 @@ trait Jump * @param string $url 跳转的URL表达式 * @param array|integer $params 其它URL参数 * @param integer $code http code + * @param array $with 隐式传参 * @return void */ - protected function redirect($url, $params = [], $code = 302) + protected function redirect($url, $params = [], $code = 302, $with = []) { $response = new Redirect($url); if (is_integer($params)) { $code = $params; $params = []; } - $response->code($code)->params($params); + $response->code($code)->params($params)->with($with); throw new HttpResponseException($response); } diff --git a/library/traits/model/SoftDelete.php b/library/traits/model/SoftDelete.php index 1a69c2c5..2b97ff72 100644 --- a/library/traits/model/SoftDelete.php +++ b/library/traits/model/SoftDelete.php @@ -42,7 +42,7 @@ trait SoftDelete { $model = new static(); $field = $model->getDeleteTimeField(true); - return $model->db(false)->where($field, 'exp', 'is not null'); + return $model->db(false)->whereNotNull($field); } /** @@ -63,7 +63,7 @@ trait SoftDelete $this->data[$name] = $this->autoWriteTimestamp($name); $result = $this->isUpdate()->save(); } else { - $result = $this->db()->delete($this->data); + $result = $this->db(false)->delete($this->data); } $this->trigger('after_delete', $this); @@ -129,7 +129,7 @@ trait SoftDelete protected function base($query) { $field = $this->getDeleteTimeField(true); - $query->where($field, 'null'); + $query->whereNull($field); } /** @@ -142,7 +142,7 @@ trait SoftDelete { $field = isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; if (!strpos($field, '.')) { - $field = $this->db(false)->getTable() . '.' . $field; + $field = '__TABLE__.' . $field; } if (!$read && strpos($field, '.')) { $array = explode('.', $field); diff --git a/tests/thinkphp/library/think/dbTest.php b/tests/thinkphp/library/think/dbTest.php index 4d3d16d5..515f9424 100644 --- a/tests/thinkphp/library/think/dbTest.php +++ b/tests/thinkphp/library/think/dbTest.php @@ -140,7 +140,7 @@ EOF; public function testExecute() { $config = $this->getConfig(); - $sql = $this->getCreateTableSql(); + $sql = $this->getCreateTableSql(); foreach ($sql as $one) { Db::connect($config)->execute($one); } @@ -151,7 +151,7 @@ EOF; public function testQuery() { $config = $this->getConfig(); - $sql = $this->getCreateTableSql(); + $sql = $this->getCreateTableSql(); Db::connect($config)->batchQuery($sql); $tableQueryResult = Db::connect($config)->query("show tables;"); @@ -165,7 +165,7 @@ EOF; public function testBatchQuery() { $config = $this->getConfig(); - $sql = $this->getCreateTableSql(); + $sql = $this->getCreateTableSql(); Db::connect($config)->batchQuery($sql); $tableNum = Db::connect($config)->execute("show tables;"); @@ -174,28 +174,28 @@ EOF; public function testTable() { - $config = $this->getConfig(); + $config = $this->getConfig(); $tableName = 'tp_user'; - $result = Db::connect($config)->table($tableName); + $result = Db::connect($config)->table($tableName); $this->assertEquals($tableName, $result->getOptions()['table']); } public function testName() { - $config = $this->getConfig(); + $config = $this->getConfig(); $tableName = 'user'; - $result = Db::connect($config)->name($tableName); + $result = Db::connect($config)->name($tableName); $this->assertEquals($config['prefix'] . $tableName, $result->getOptions()['table']); } public function testInsert() { $config = $this->getConfig(); - $data = [ + $data = [ 'username' => 'chunice', 'password' => md5('chunice'), 'status' => 1, - 'create_time' => time() + 'create_time' => time(), ]; $result = Db::connect($config)->name('user')->insert($data); $this->assertEquals(1, $result); @@ -204,11 +204,11 @@ EOF; public function testUpdate() { $config = $this->getConfig(); - $data = [ + $data = [ 'username' => 'chunice_update', 'password' => md5('chunice'), 'status' => 1, - 'create_time' => time() + 'create_time' => time(), ]; $result = Db::connect($config)->name('user')->where('username', 'chunice')->update($data); $this->assertEquals(1, $result); @@ -216,7 +216,7 @@ EOF; public function testFind() { - $config = $this->getConfig(); + $config = $this->getConfig(); $mustFind = Db::connect($config)->name('user')->where('username', 'chunice_update')->find(); $this->assertNotEmpty($mustFind); $mustNotFind = Db::connect($config)->name('user')->where('username', 'chunice')->find(); @@ -229,7 +229,7 @@ EOF; $data = [ ['username' => 'foo', 'password' => md5('foo'), 'status' => 1, 'create_time' => time()], - ['username' => 'bar', 'password' => md5('bar'), 'status' => 1, 'create_time' => time()] + ['username' => 'bar', 'password' => md5('bar'), 'status' => 1, 'create_time' => time()], ]; $insertNum = Db::connect($config)->name('user')->insertAll($data); @@ -238,7 +238,7 @@ EOF; public function testSelect() { - $config = $this->getConfig(); + $config = $this->getConfig(); $mustFound = Db::connect($config)->name('user')->where('status', 1)->select(); $this->assertNotEmpty($mustFound); $mustNotFound = Db::connect($config)->name('user')->where('status', 0)->select(); @@ -247,7 +247,7 @@ EOF; public function testValue() { - $config = $this->getConfig(); + $config = $this->getConfig(); $username = Db::connect($config)->name('user')->where('id', 1)->value('username'); $this->assertEquals('chunice_update', $username); $usernameNull = Db::connect($config)->name('user')->where('id', 0)->value('username'); @@ -256,7 +256,7 @@ EOF; public function testColumn() { - $config = $this->getConfig(); + $config = $this->getConfig(); $username = Db::connect($config)->name('user')->where('status', 1)->column('username'); $this->assertNotEmpty($username); $usernameNull = Db::connect($config)->name('user')->where('status', 0)->column('username'); @@ -267,13 +267,13 @@ EOF; public function testInsertGetId() { $config = $this->getConfig(); - $id = Db::connect($config)->name('user')->order('id', 'desc')->value('id'); + $id = Db::connect($config)->name('user')->order('id', 'desc')->value('id'); $data = [ 'username' => uniqid(), 'password' => md5('chunice'), 'status' => 1, - 'create_time' => time() + 'create_time' => time(), ]; $lastId = Db::connect($config)->name('user')->insertGetId($data); $this->assertEquals($id + 1, $lastId); @@ -283,11 +283,11 @@ EOF; public function testGetLastInsId() { $config = $this->getConfig(); - $data = [ + $data = [ 'username' => uniqid(), 'password' => md5('chunice'), 'status' => 1, - 'create_time' => time() + 'create_time' => time(), ]; $lastId = Db::connect($config)->name('user')->insertGetId($data); @@ -308,7 +308,7 @@ EOF; public function testSetInc() { - $config = $this->getConfig(); + $config = $this->getConfig(); $originCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); Db::connect($config)->name('user')->where('id', 1)->setInc('create_time'); $newCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); @@ -318,7 +318,7 @@ EOF; public function testSetDec() { - $config = $this->getConfig(); + $config = $this->getConfig(); $originCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); Db::connect($config)->name('user')->where('id', 1)->setDec('create_time'); $newCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); @@ -341,12 +341,12 @@ EOF; public function testCache() { $config = $this->getConfig(); - $result = Db::connect($config)->name('user')->where('id', 1)->cache('key', 60)->select(); - $cache = \think\Cache::get('key'); + $result = Db::connect($config)->name('user')->where('id', 1)->cache('key', 60)->find(); + $cache = \think\Cache::get('key'); $this->assertEquals($result, $cache); - $updateCache = Db::connect($config)->name('user')->cache('key')->find(2); - $this->assertNotEquals($cache, $updateCache); + $updateCache = Db::connect($config)->name('user')->cache('key')->find(1); + $this->assertEquals($cache, $updateCache); } } diff --git a/tests/thinkphp/library/think/template/taglib/cxTest.php b/tests/thinkphp/library/think/template/taglib/cxTest.php index c1c67413..8aee392f 100644 --- a/tests/thinkphp/library/think/template/taglib/cxTest.php +++ b/tests/thinkphp/library/think/template/taglib/cxTest.php @@ -47,7 +47,7 @@ EOF; {/volist} EOF; $data = <<\$vo): \$mod = (\$key % 2 );++\$key;?> +\$vo): \$mod = (\$key % 2 );++\$key;?> EOF; @@ -88,7 +88,7 @@ EOF; {/foreach} EOF; $data = <<\$val): ?> +\$val): ?> EOF; @@ -389,7 +389,7 @@ default {/empty} EOF; $data = <<isEmpty())): ?> +isEmpty())): ?> default EOF; @@ -402,7 +402,7 @@ default {/notempty} EOF; $data = <<isEmpty()))): ?> +isEmpty()))): ?> default EOF; @@ -538,13 +538,13 @@ EOF; public function testUrl() { $template = new template(); - $content = <<display($content); $this->expectOutputString(\think\Url::build('Index/index')); } - + public function testFunction() { $template = new template(); diff --git a/tests/thinkphp/library/think/urlTest.php b/tests/thinkphp/library/think/urlTest.php index acb43365..1cb64084 100644 --- a/tests/thinkphp/library/think/urlTest.php +++ b/tests/thinkphp/library/think/urlTest.php @@ -88,7 +88,7 @@ class urlTest extends \PHPUnit_Framework_TestCase public function testBuildNameRoute() { Route::get(['name', 'blog/:id'], 'index/blog'); - $this->assertEquals([['blog/:id', ['id' => 1], null]], Route::name('name')); + $this->assertEquals([['blog/:id', ['id' => 1], null, null]], Route::name('name')); Config::set('url_html_suffix', 'shtml'); $this->assertEquals('/blog/10.shtml', Url::build('name?id=10')); } diff --git a/tests/thinkphp/library/think/validateTest.php b/tests/thinkphp/library/think/validateTest.php index 4d7819b5..081a6f2b 100644 --- a/tests/thinkphp/library/think/validateTest.php +++ b/tests/thinkphp/library/think/validateTest.php @@ -88,7 +88,7 @@ class validateTest extends \PHPUnit_Framework_TestCase 'date' => '16-3-8', 'ok' => 'yes', 'value' => 100, - 'bool' => 'true', + 'bool' => true, 'title' => '流年ThinkPHP', 'city' => '上海', 'nickname' => '流年ThinkPHP_2016',