diff --git a/base.php b/base.php index 5bd1b405..dc43b26b 100644 --- a/base.php +++ b/base.php @@ -9,7 +9,7 @@ // | Author: liu21st // +---------------------------------------------------------------------- -define('THINK_VERSION', '5.0.3'); +define('THINK_VERSION', '5.0.4beta'); define('THINK_START_TIME', microtime(true)); define('THINK_START_MEM', memory_get_usage()); define('EXT', '.php'); diff --git a/library/think/Model.php b/library/think/Model.php index 0376f8d6..0a3a35f5 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -20,6 +20,13 @@ use think\Exception; use think\Exception\ValidateException; use think\Loader; use think\model\Relation; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +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; /** @@ -186,26 +193,6 @@ abstract class Model implements \JsonSerializable, \ArrayAccess return self::$links[$model]; } - /** - * 获取关联模型实例 - * @access protected - * @param string|array $relation 关联查询 - * @return Relation|Query - */ - protected function relation($relation = null) - { - if (!is_null($relation)) { - // 执行关联查询 - return $this->db()->relation($relation); - } - - // 获取关联对象实例 - if (is_null($this->relation)) { - $this->relation = new Relation($this); - } - return $this->relation; - } - /** * 初始化模型 * @access protected @@ -421,9 +408,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $value = $this->readTransform($value, $this->type[$name]); } elseif ($notFound) { $method = Loader::parseName($name, 1); - if (method_exists($this, $method) && !method_exists('\think\Model', $method)) { + if (method_exists($this, $method) && $this->$method() instanceof Relation) { // 不存在该字段 获取关联数据 - $value = $this->relation()->getRelation($method); + $value = $this->$method()->getRelation(); // 保存关联对象值 $this->data[$name] = $value; } else { @@ -1157,18 +1144,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess public static function has($relation, $operator = '>=', $count = 1, $id = '*') { $model = new static(); - $info = $model->$relation()->getRelationInfo(); - $table = $info['model']::getTable(); - switch ($info['type']) { - case Relation::HAS_MANY: - return $model->db()->alias('a') - ->join($table . ' b', 'a.' . $info['localKey'] . '=b.' . $info['foreignKey'], $info['joinType']) - ->group('b.' . $info['foreignKey']) - ->having('count(' . $id . ')' . $operator . $count); - case Relation::HAS_MANY_THROUGH: // TODO - default: - return $model; - } + return $model->$relation()->has($model, $operator, $count, $id); } /** @@ -1181,27 +1157,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess public static function hasWhere($relation, $where = []) { $model = new static(); - $info = $model->$relation()->getRelationInfo(); - switch ($info['type']) { - case Relation::HAS_ONE: - case Relation::HAS_MANY: - $table = $info['model']::getTable(); - if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where['b.' . $key] = $val; - unset($where[$key]); - } - } - } - return $model->db()->alias('a') - ->field('a.*') - ->join($table . ' b', 'a.' . $info['localKey'] . '=b.' . $info['foreignKey'], $info['joinType']) - ->where($where); - case Relation::HAS_MANY_THROUGH: // TODO - default: - return $model; - } + return $model->$relation()->hasWhere($model, $where); } /** @@ -1232,9 +1188,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess if (is_string($relations)) { $relations = explode(',', $relations); } - $this->relation(); + foreach ($relations as $relation) { - $this->data[$relation] = $this->relation->getRelation($relation); + $this->data[$relation] = $this->$relation()->getRelation(); } return $this; } @@ -1246,9 +1202,21 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param string $relation 关联名 * @return array */ - public function eagerlyResultSet($resultSet, $relation) + public function eagerlyResultSet(&$resultSet, $relation, $class = '') { - return $this->relation()->eagerlyResultSet($resultSet, $relation); + $relations = is_string($relation) ? explode(',', $relation) : $relation; + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation); + } + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $class); + } } /** @@ -1258,9 +1226,22 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param string $relation 关联名 * @return Model */ - public function eagerlyResult($result, $relation) + public function eagerlyResult(&$result, $relation, $class = '') { - return $this->relation()->eagerlyResult($result, $relation); + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation); + } + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure, $class); + } } /** @@ -1279,7 +1260,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $model = $this->parseModel($model); $localKey = $localKey ?: $this->getPk(); $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - return $this->relation()->hasOne($model, $foreignKey, $localKey, $alias, $joinType); + return new HasOne($this, $model, $foreignKey, $localKey, $alias, $joinType); } /** @@ -1298,7 +1279,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $model = $this->parseModel($model); $foreignKey = $foreignKey ?: Loader::parseName(basename(str_replace('\\', '/', $model))) . '_id'; $otherKey = $otherKey ?: (new $model)->getPk(); - return $this->relation()->belongsTo($model, $foreignKey, $otherKey, $alias, $joinType); + return new BelongsTo($this, $model, $foreignKey, $otherKey, $alias, $joinType); } /** @@ -1316,7 +1297,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $model = $this->parseModel($model); $localKey = $localKey ?: $this->getPk(); $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; - return $this->relation()->hasMany($model, $foreignKey, $localKey, $alias); + return new HasMany($this, $model, $foreignKey, $localKey, $alias); } /** @@ -1339,7 +1320,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id'; $name = Loader::parseName(basename(str_replace('\\', '/', $through))); $throughKey = $throughKey ?: $name . '_id'; - return $this->relation()->hasManyThrough($model, $through, $foreignKey, $throughKey, $localKey, $alias); + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $alias); } /** @@ -1360,7 +1341,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $table = $table ?: $this->db()->getTable(Loader::parseName($this->name) . '_' . $name); $foreignKey = $foreignKey ?: $name . '_id'; $localKey = $localKey ?: Loader::parseName($this->name) . '_id'; - return $this->relation()->belongsToMany($model, $table, $foreignKey, $localKey, $alias); + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey, $alias); } /** @@ -1371,18 +1352,22 @@ abstract class Model implements \JsonSerializable, \ArrayAccess * @param string $type 多态类型 * @return Relation */ - public function morphMany($model, $morph, $type = '') + public function morphMany($model, $morph = null, $type = '') { // 记录当前关联信息 $model = $this->parseModel($model); - $type = $type ?: Loader::parseName($this->name); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: Loader::parseName($this->name); if (is_array($morph)) { list($morphType, $foreignKey) = $morph; } else { $morphType = $morph . '_type'; $foreignKey = $morph . '_id'; } - return $this->relation()->morphMany($model, $foreignKey, $morphType, $type); + return new MorphMany($this, $model, $foreignKey, $morphType, $type); } /** @@ -1405,7 +1390,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess $morphType = $morph . '_type'; $foreignKey = $morph . '_id'; } - return $this->relation()->morphTo($morphType, $foreignKey, $alias); + return new MorphTo($this, $morphType, $foreignKey, $alias); } public function __call($method, $args) diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 39ab03a9..b6ad8ff8 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -27,6 +27,8 @@ use think\exception\PDOException; use think\Loader; use think\Model; use think\model\Relation; +use think\model\relation\BelongsTo; +use think\model\relation\HasOne; use think\Paginator; class Query @@ -114,6 +116,16 @@ class Query return $this; } + /** + * 获取当前的模型对象名 + * @access public + * @return Connection + */ + public function getModel() + { + return $this->model; + } + /** * 指定默认的数据表名(不含前缀) * @access public @@ -976,6 +988,20 @@ class Query return $this; } + /** + * 去除某个查询参数 + * @access public + * @param string $option 参数名 + * @return $this + */ + public function removeOption($option) + { + if (isset($this->options[$option])) { + unset($this->options[$option]); + } + return $this; + } + /** * 指定查询数量 * @access public @@ -1601,13 +1627,14 @@ class Query $with = explode(',', $with); } - $i = 0; + $first = true; $currentModel = $this->model; /** @var Model $class */ $class = new $currentModel; foreach ($with as $key => $relation) { - $closure = false; + $subRelation = ''; + $closure = false; if ($relation instanceof \Closure) { // 支持闭包查询过滤关联条件 $closure = $relation; @@ -1620,48 +1647,9 @@ class Query /** @var Relation $model */ $model = $class->$relation(); - $info = $model->getRelationInfo(); - if (in_array($info['type'], [Relation::HAS_ONE, Relation::BELONGS_TO])) { - if (0 == $i) { - $name = Loader::parseName(basename(str_replace('\\', '/', $currentModel))); - $table = $this->getTable(); - $alias = isset($info['alias'][$name]) ? $info['alias'][$name] : $name; - $this->table([$table => $alias]); - if (isset($this->options['field'])) { - $field = $this->options['field']; - unset($this->options['field']); - } else { - $field = true; - } - $this->field($field, false, $table, $alias); - } - // 预载入封装 - $joinTable = $model->getTable(); - $joinName = Loader::parseName(basename(str_replace('\\', '/', $info['model']))); - $joinAlias = isset($info['alias'][$joinName]) ? $info['alias'][$joinName] : $relation; - $this->via($joinAlias); - - if (Relation::HAS_ONE == $info['type']) { - $this->join($joinTable . ' ' . $joinAlias, $alias . '.' . $info['localKey'] . '=' . $joinAlias . '.' . $info['foreignKey'], $info['joinType']); - } else { - $this->join($joinTable . ' ' . $joinAlias, $alias . '.' . $info['foreignKey'] . '=' . $joinAlias . '.' . $info['localKey'], $info['joinType']); - } - - if ($closure) { - // 执行闭包查询 - call_user_func_array($closure, [ & $this]); - //指定获取关联的字段 - //需要在 回调中 调方法 withField 方法,如 - // $query->where(['id'=>1])->withField('id,name'); - if (!empty($this->options['with_field'])) { - $field = $this->options['with_field']; - unset($this->options['with_field']); - } - } elseif (isset($info['option']['field'])) { - $field = $info['option']['field']; - } - $this->field($field, false, $joinTable, $joinAlias, $relation . '__'); - $i++; + if ($model instanceof HasOne || $model instanceof BelongsTo) { + $model->eagerly($this, $relation, $subRelation, $closure, $first); + $first = false; } elseif ($closure) { $with[$key] = $closure; } diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index fc420930..d7938836 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -11,34 +11,14 @@ namespace think\model; -use think\Db; -use think\Exception; -use think\Loader; -use think\Model; -use think\model\Pivot; - -class Relation +abstract class Relation { - const HAS_ONE = 1; - const HAS_MANY = 2; - const BELONGS_TO = 3; - const BELONGS_TO_MANY = 4; - const HAS_MANY_THROUGH = 5; - const MORPH_TO = 6; - const MORPH_MANY = 7; - // 父模型对象 protected $parent; /** @var Model 当前关联的模型类 */ protected $model; - // 中间表模型 - protected $middle; - // 当前关联类型 - protected $type; // 关联表外键 protected $foreignKey; - // 中间关联表外键 - protected $throughKey; // 关联表主键 protected $localKey; // 数据表别名 @@ -52,16 +32,6 @@ class Relation // 关联查询参数 protected $option; - /** - * 架构函数 - * @access public - * @param Model $model 上级模型对象 - */ - public function __construct(Model $model) - { - $this->parent = $model; - } - /** * 获取关联的所属模型 * @access public @@ -80,262 +50,6 @@ class Relation return $this->query; } - /** - * 解析模型的完整命名空间 - * @access public - * @param string $model 模型名(或者完整类名) - * @return string - */ - protected function parseModel($model) - { - if (isset($this->alias[$model])) { - $model = $this->alias[$model]; - } - if (false === strpos($model, '\\')) { - $path = explode('\\', get_class($this->parent)); - array_pop($path); - array_push($path, Loader::parseName($model, 1)); - $model = implode('\\', $path); - } - return $model; - } - - /** - * 获取当前关联信息 - * @access public - * @param string $name 关联信息 - * @return array|string|integer - */ - public function getRelationInfo($name = '') - { - $info = [ - 'type' => $this->type, - 'model' => $this->model, - 'middle' => $this->middle, - 'foreignKey' => $this->foreignKey, - 'localKey' => $this->localKey, - 'alias' => $this->alias, - 'joinType' => $this->joinType, - 'option' => $this->option, - ]; - return $name ? $info[$name] : $info; - } - - // 获取关联数据 - public function getRelation($name) - { - // 执行关联定义方法 - $relation = $this->parent->$name(); - $foreignKey = $this->foreignKey; - $localKey = $this->localKey; - $middle = $this->middle; - - // 判断关联类型执行查询 - switch ($this->type) { - case self::HAS_ONE: - $result = $relation->where($foreignKey, $this->parent->$localKey)->find(); - break; - case self::BELONGS_TO: - $result = $relation->where($localKey, $this->parent->$foreignKey)->find(); - break; - case self::HAS_MANY: - $result = $relation->select(); - break; - case self::HAS_MANY_THROUGH: - $result = $relation->select(); - break; - case self::BELONGS_TO_MANY: - // 关联查询 - $pk = $this->parent->getPk(); - $condition['pivot.' . $localKey] = $this->parent->$pk; - $result = $this->belongsToManyQuery($relation->getQuery(), $middle, $foreignKey, $localKey, $condition)->select(); - foreach ($result as $set) { - $pivot = []; - foreach ($set->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ('pivot' == $name) { - $pivot[$attr] = $val; - unset($set->$key); - } - } - } - $set->pivot = new Pivot($pivot, $this->middle); - } - break; - case self::MORPH_MANY: - $result = $relation->select(); - break; - case self::MORPH_TO: - // 多态模型 - $model = $this->parseModel($this->parent->$middle); - // 主键数据 - $pk = $this->parent->$foreignKey; - $result = (new $model)->find($pk); - break; - default: - // 直接返回 - $result = $relation; - } - return $result; - } - - /** - * 预载入关联查询 返回数据集 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 关联名 - * @param string $class 数据集对象名 为空表示数组 - * @return array - */ - public function eagerlyResultSet($resultSet, $relation, $class = '') - { - /** @var array $relations */ - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation); - } - // 执行关联方法 - $model = $this->parent->$relation(); - // 获取关联信息 - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - $middle = $this->middle; - switch ($this->type) { - case self::HAS_ONE: - case self::BELONGS_TO: - foreach ($resultSet as $result) { - // 模型关联组装 - $this->match($this->model, $relation, $result); - } - break; - case self::HAS_MANY: - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (isset($result->$localKey)) { - $range[] = $result->$localKey; - } - } - - if (!empty($range)) { - $this->where[$foreignKey] = ['in', $range]; - $data = $this->eagerlyOneToMany($model, [ - $foreignKey => [ - 'in', - $range, - ], - ], $relation, $subRelation, $closure); - - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$localKey], $class)); - } - } - break; - case self::BELONGS_TO_MANY: - $pk = $resultSet[0]->getPk(); - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (isset($result->$pk)) { - $range[] = $result->$pk; - } - } - - if (!empty($range)) { - // 查询关联数据 - $data = $this->eagerlyManyToMany($model, [ - 'pivot.' . $localKey => [ - 'in', - $range, - ], - ], $relation, $subRelation); - - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$pk])) { - $data[$result->$pk] = []; - } - - $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); - } - } - break; - case self::MORPH_MANY: - $range = []; - foreach ($resultSet as $result) { - $pk = $result->getPk(); - // 获取关联外键列表 - if (isset($result->$pk)) { - $range[] = $result->$pk; - } - } - - if (!empty($range)) { - $this->where[$foreignKey] = ['in', $range]; - $this->where[$localKey] = $middle; - $data = $this->eagerlyMorphToMany($model, [ - $foreignKey => ['in', $range], - $localKey => $middle, - ], $relation, $subRelation, $closure); - - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$pk])) { - $data[$result->$pk] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); - } - } - break; - case self::MORPH_TO: - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (!empty($result->$foreignKey)) { - $range[$result->$middle][] = $result->$foreignKey; - } - } - - if (!empty($range)) { - foreach ($range as $key => $val) { - // 多态类型映射 - $model = $this->parseModel($key); - $obj = new $model; - $pk = $obj->getPk(); - $list = $obj->all($val, $subRelation); - $data = []; - foreach ($list as $k => $vo) { - $data[$vo->$pk] = $vo; - } - foreach ($resultSet as $result) { - if ($key == $result->$middle) { - if (!isset($data[$result->$foreignKey])) { - $data[$result->$foreignKey] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$foreignKey], $class)); - } - } - } - } - break; - } - } - return $resultSet; - } - /** * 封装关联数据集 * @access public @@ -348,212 +62,6 @@ class Relation return $class ? new $class($resultSet) : $resultSet; } - /** - * 预载入关联查询 返回模型对象 - * @access public - * @param Model $result 数据对象 - * @param string $relation 关联名 - * @param string $class 数据集对象名 为空表示数组 - * @return Model - */ - public function eagerlyResult($result, $relation, $class = '') - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation); - } - // 执行关联方法 - $model = $this->parent->$relation(); - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - $middle = $this->middle; - switch ($this->type) { - case self::HAS_ONE: - case self::BELONGS_TO: - // 模型关联组装 - $this->match($this->model, $relation, $result); - break; - case self::HAS_MANY: - if (isset($result->$localKey)) { - $data = $this->eagerlyOneToMany($model, [$foreignKey => $result->$localKey], $relation, $subRelation, $closure); - // 关联数据封装 - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$result->$localKey], $class)); - } - break; - case self::BELONGS_TO_MANY: - $pk = $result->getPk(); - if (isset($result->$pk)) { - $pk = $result->$pk; - // 查询管理数据 - $data = $this->eagerlyManyToMany($model, ['pivot.' . $localKey => $pk], $relation, $subRelation); - - // 关联数据封装 - if (!isset($data[$pk])) { - $data[$pk] = []; - } - $result->setAttr($relation, $this->resultSetBuild($data[$pk], $class)); - } - break; - case self::MORPH_MANY: - $pk = $result->getPk(); - if (isset($result->$pk)) { - $data = $this->eagerlyMorphToMany($model, [$foreignKey => $result->$pk, $localKey => $middle], $relation, $subRelation, $closure); - $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); - } - break; - case self::MORPH_TO: - // 多态类型映射 - $model = $this->parseModel($result->{$this->middle}); - $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); - break; - - } - } - return $result; - } - - /** - * 一对一 关联模型预查询拼装 - * @access public - * @param string $model 模型名称 - * @param string $relation 关联名 - * @param Model $result 模型对象实例 - * @return void - */ - protected function match($model, $relation, &$result) - { - // 重新组装模型数据 - foreach ($result->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ($name == $relation) { - $list[$name][$attr] = $val; - unset($result->$key); - } - } - } - - $result->setAttr($relation, !isset($list[$relation]) ? null : (new $model($list[$relation]))->isUpdate(true)); - } - - /** - * 一对多 关联模型预查询 - * @access public - * @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) - { - $foreignKey = $this->foreignKey; - // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $model]); - } - $list = $model->where($where)->with($subRelation)->select(); - - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $data[$set->$foreignKey][] = $set; - } - return $data; - } - - /** - * 多对多 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @return array - */ - protected function eagerlyManyToMany($model, $where, $relation, $subRelation = '') - { - $foreignKey = $this->foreignKey; - $localKey = $this->localKey; - // 预载入关联查询 支持嵌套预载入 - $list = $this->belongsToManyQuery($model->getQuery(), $this->middle, $foreignKey, $localKey, $where)->with($subRelation)->select(); - - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $pivot = []; - foreach ($set->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ('pivot' == $name) { - $pivot[$attr] = $val; - unset($set->$key); - } - } - } - $set->pivot = new Pivot($pivot, $this->middle); - $data[$pivot[$localKey]][] = $set; - } - return $data; - } - - /** - * 多态MorphTo 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @return array - */ - protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') - { - // 预载入关联查询 支持嵌套预载入 - $pk = $this->parent->{$this->foreignKey}; - $data = (new $model)->with($subRelation)->find($pk); - if ($data) { - $data->isUpdate(true); - } - $result->setAttr($relation, $data ?: null); - } - - /** - * 多态一对多 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @return array - */ - protected function eagerlyMorphToMany($model, $where, $relation, $subRelation = '', $closure = false) - { - // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $model]); - } - $list = $model->getQuery()->where($where)->with($subRelation)->select(); - $foreignKey = $this->foreignKey; - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $data[$set->$foreignKey][] = $set; - } - return $data; - } - /** * 设置当前关联定义的数据表别名 * @access public @@ -566,363 +74,19 @@ class Relation return $this; } - /** - * HAS ONE 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 - * @return $this - */ - public function hasOne($model, $foreignKey, $localKey, $alias = [], $joinType = 'INNER') - { - $this->type = self::HAS_ONE; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->alias = $alias; - $this->joinType = $joinType; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * BELONGS TO 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $otherKey 关联主键 - * @param array $alias 别名定义 - * @param string $joinType JOIN类型 - * @return $this - */ - public function belongsTo($model, $foreignKey, $otherKey, $alias = [], $joinType = 'INNER') - { - // 记录当前关联信息 - $this->type = self::BELONGS_TO; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $otherKey; - $this->alias = $alias; - $this->joinType = $joinType; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * HAS MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @return $this - */ - public function hasMany($model, $foreignKey, $localKey, $alias) - { - // 记录当前关联信息 - $this->type = self::HAS_MANY; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->alias = $alias; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * HAS MANY 远程关联定义 - * @access public - * @param string $model 模型名 - * @param string $through 中间模型名 - * @param string $firstkey 关联外键 - * @param string $secondKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义 - * @return $this - */ - public function hasManyThrough($model, $through, $foreignKey, $throughKey, $localKey, $alias) - { - // 记录当前关联信息 - $this->type = self::HAS_MANY_THROUGH; - $this->model = $model; - $this->middle = $through; - $this->foreignKey = $foreignKey; - $this->throughKey = $throughKey; - $this->localKey = $localKey; - $this->alias = $alias; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * BELONGS TO MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $table 中间表名 - * @param string $foreignKey 关联模型外键 - * @param string $localKey 当前模型关联键 - * @param array $alias 别名定义 - * @return $this - */ - public function belongsToMany($model, $table, $foreignKey, $localKey, $alias) - { - // 记录当前关联信息 - $this->type = self::BELONGS_TO_MANY; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->middle = $table; - $this->alias = $alias; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * MORPH_MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $id 关联外键 - * @param string $morphType 多态字段名 - * @param string $type 多态类型 - * @return $this - */ - public function morphMany($model, $foreignKey, $morphType, $type) - { - // 记录当前关联信息 - $this->type = self::MORPH_MANY; - $this->model = $model; - $this->middle = $type; - $this->foreignKey = $foreignKey; - $this->localKey = $morphType; - $this->query = (new $model)->db(); - // 返回关联的模型对象 - return $this; - } - - /** - * MORPH_TO 关联定义 - * @access public - * @param string $morphType 多态字段名 - * @param string $foreignKey 外键名 - * @param array $alias 多态别名定义 - * @return $this - */ - public function morphTo($morphType, $foreignKey, $alias) - { - // 记录当前关联信息 - $this->type = self::MORPH_TO; - $this->middle = $morphType; - $this->foreignKey = $foreignKey; - $this->alias = $alias; - // 返回关联的模型对象 - return $this; - } - - /** - * BELONGS TO MANY 关联查询 - * @access public - * @param object $model 关联模型对象 - * @param string $table 中间表名 - * @param string $foreignKey 关联模型关联键 - * @param string $localKey 当前模型关联键 - * @param array $condition 关联查询条件 - * @return \think\db\Query|string - */ - protected function belongsToManyQuery($model, $table, $foreignKey, $localKey, $condition = []) - { - // 关联查询封装 - $tableName = $model->getTable(); - $relationFk = $model->getPk(); - return $model->field($tableName . '.*') - ->field(true, false, $table, 'pivot', 'pivot__') - ->join($table . ' pivot', 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) - ->where($condition); - } - - /** - * 保存(新增)当前关联数据对象 - * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return integer - */ - public function save($data, array $pivot = []) - { - // 判断关联类型 - switch ($this->type) { - case self::HAS_ONE: - case self::BELONGS_TO: - case self::HAS_MANY: - if ($data instanceof Model) { - $data = $data->getData(); - } - // 保存关联表数据 - $data[$this->foreignKey] = $this->parent->{$this->localKey}; - $model = new $this->model; - return $model->save($data); - case self::BELONGS_TO_MANY: - // 保存关联表/中间表数据 - return $this->attach($data, $pivot); - } - } - - /** - * 批量保存当前关联数据对象 - * @access public - * @param array $dataSet 数据集 - * @param array $pivot 中间表额外数据 - * @return integer - */ - public function saveAll(array $dataSet, array $pivot = []) - { - $result = false; - foreach ($dataSet as $key => $data) { - // 判断关联类型 - switch ($this->type) { - case self::HAS_MANY: - $data[$this->foreignKey] = $this->parent->{$this->localKey}; - $result = $this->save($data); - break; - case self::BELONGS_TO_MANY: - // TODO - $result = $this->attach($data, !empty($pivot) ? $pivot[$key] : []); - break; - } - } - return $result; - } - - /** - * 附加关联的一个中间表数据 - * @access public - * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return integer - */ - public function attach($data, $pivot = []) - { - if (is_array($data)) { - // 保存关联表数据 - $model = new $this->model; - $model->save($data); - $id = $model->getLastInsID(); - } elseif (is_numeric($data) || is_string($data)) { - // 根据关联表主键直接写入中间表 - $id = $data; - } elseif ($data instanceof Model) { - // 根据关联表主键直接写入中间表 - $relationFk = $data->getPk(); - $id = $data->$relationFk; - } - - if ($id) { - // 保存中间表数据 - $pk = $this->parent->getPk(); - $pivot[$this->localKey] = $this->parent->$pk; - $pivot[$this->foreignKey] = $id; - $query = clone $this->parent->db(); - return $query->table($this->middle)->insert($pivot); - } else { - throw new Exception('miss relation data'); - } - } - - /** - * 解除关联的一个中间表数据 - * @access public - * @param integer|array $data 数据 可以使用关联对象的主键 - * @param bool $relationDel 是否同时删除关联表数据 - * @return integer - */ - public function detach($data, $relationDel = false) - { - if (is_array($data)) { - $id = $data; - } elseif (is_numeric($data) || is_string($data)) { - // 根据关联表主键直接写入中间表 - $id = $data; - } elseif ($data instanceof Model) { - // 根据关联表主键直接写入中间表 - $relationFk = $data->getPk(); - $id = $data->$relationFk; - } - // 删除中间表数据 - $pk = $this->parent->getPk(); - $pivot[$this->localKey] = $this->parent->$pk; - if (isset($id)) { - $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; - } - $query = clone $this->parent->db(); - $query->table($this->middle)->where($pivot)->delete(); - - // 删除关联表数据 - if (isset($id) && $relationDel) { - $model = $this->model; - $model::destroy($id); - } - } - public function __call($method, $args) { - static $baseQuery = []; if ($this->query) { - if (empty($baseQuery[$this->type])) { - $baseQuery[$this->type] = true; - switch ($this->type) { - case self::HAS_MANY: - if (isset($this->where)) { - $this->query->where($this->where); - } elseif (isset($this->parent->{$this->localKey})) { - // 关联查询带入关联条件 - $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); - } - break; - case self::HAS_MANY_THROUGH: - $through = $this->middle; - $model = $this->model; - $alias = Loader::parseName(basename(str_replace('\\', '/', $model))); - $throughTable = $through::getTable(); - $pk = (new $this->model)->getPk(); - $throughKey = $this->throughKey; - $modelTable = $this->parent->getTable(); - $this->query->field($alias . '.*')->alias($alias) - ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) - ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) - ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); - break; - case self::BELONGS_TO_MANY: - $pk = $this->parent->getPk(); - $this->query->join($this->middle . ' pivot', 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); - break; - case self::MORPH_MANY: - $pk = $this->parent->getPk(); - $map[$this->foreignKey] = $this->parent->$pk; - $map[$this->localKey] = $this->middle; - $this->query->where($map); - break; - } - } - $result = call_user_func_array([$this->query, $method], $args); if ($result instanceof \think\db\Query) { $this->option = $result->getOptions(); return $this; } else { $this->option = []; - $baseQuery = false; return $result; } } else { throw new Exception('method not exists:' . __CLASS__ . '->' . $method); } } - } diff --git a/library/think/model/relation/BelongsTo.php b/library/think/model/relation/BelongsTo.php new file mode 100644 index 00000000..ae5c400e --- /dev/null +++ b/library/think/model/relation/BelongsTo.php @@ -0,0 +1,171 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class BelongsTo extends Relation +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $otherKey 关联主键 + * @param array $alias 别名定义 + * @param string $joinType JOIN类型 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $alias = [], $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(); + } + + // 动态获取关联数据 + public function getRelation() + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + return $this->query->where($localKey, $this->parent->$foreignKey)->find(); + } + + /** + * 预载入关联查询 + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @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; + if ($first) { + + $table = $query->getTable(); + $query->table([$table => $alias]); + if ($query->getOptions('field')) { + $field = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $field = true; + } + $query->field($field, false, $table, $alias); + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinName = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $joinAlias = isset($this->alias[$joinName]) ? $this->alias[$joinName] : $relation; + $query->via($joinAlias); + + $query->join($joinTable . ' ' . $joinAlias, $alias . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); + + if ($closure) { + // 执行闭包查询 + call_user_func_array($closure, [ & $query]); + //指定获取关联的字段 + //需要在 回调中 调方法 withField 方法,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + } elseif (isset($this->option['field'])) { + $field = $this->option['field']; + } else { + $field = true; + } + $query->field($field, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation) + { + foreach ($resultSet as $result) { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return Model + */ + public function eagerlyResult(&$result, $relation) + { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + $result->setAttr($relation, !isset($list[$relation]) ? null : (new $model($list[$relation]))->isUpdate(true)); + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return integer + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + $model = new $this->model; + return $model->save($data); + } +} diff --git a/library/think/model/relation/BelongsToMany.php b/library/think/model/relation/BelongsToMany.php new file mode 100644 index 00000000..59220aee --- /dev/null +++ b/library/think/model/relation/BelongsToMany.php @@ -0,0 +1,330 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Model; +use think\model\Pivot; +use think\model\Relation; + +class BelongsToMany extends Relation +{ + // 中间表模型 + protected $middle; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + * @param array $alias 别名定义 + */ + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey, $alias = []) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->middle = $table; + $this->alias = $alias; + $this->query = (new $model)->db(); + } + + // 动态获取关联数据 + public function getRelation() + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + $middle = $this->middle; + // 关联查询 + $pk = $this->parent->getPk(); + $condition['pivot.' . $localKey] = $this->parent->$pk; + $result = $this->belongsToManyQuery($middle, $foreignKey, $localKey, $condition)->select(); + foreach ($result as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->pivot = new Pivot($pivot, $this->middle); + } + return $result; + } + + /** + * 预载入关联查询 + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param bool $first 是否需要使用基础表 + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $pk = $resultSet[0]->getPk(); + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + 'pivot.' . $localKey => [ + 'in', + $range, + ], + ], $relation, $subRelation); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); + } + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return Model + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany(['pivot.' . $localKey => $pk], $relation, $subRelation); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + $result->setAttr($relation, $this->resultSetBuild($data[$pk], $class)); + } + } + + /** + * 多对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @return array + */ + protected function eagerlyManyToMany($where, $relation, $subRelation = '') + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->middle, $foreignKey, $localKey, $where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->pivot = new Pivot($pivot, $this->middle); + $data[$pivot[$localKey]][] = $set; + } + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access public + * @param string $table 中间表名 + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return \think\db\Query|string + */ + protected function belongsToManyQuery($table, $foreignKey, $localKey, $condition = []) + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $relationFk = $this->query->getPk(); + return $this->query->field($tableName . '.*') + ->field(true, false, $table, 'pivot', 'pivot__') + ->join($table . ' pivot', 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return integer + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @return integer + */ + public function saveAll(array $dataSet, array $pivot = []) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->attach($data, !empty($pivot) ? $pivot[$key] : []); + } + return $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return integer + */ + public function attach($data, $pivot = []) + { + if (is_array($data)) { + // 保存关联表数据 + $model = new $this->model; + $model->save($data); + $id = $model->getLastInsID(); + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if ($id) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $pivot[$this->foreignKey] = $id; + $query = clone $this->parent->db(); + return $query->table($this->middle)->insert($pivot); + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data, $relationDel = false) + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + if (isset($id)) { + $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; + } + $query = clone $this->parent->db(); + $query->table($this->middle)->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + } + + public function __call($method, $args) + { + static $baseQuery = false; + if ($this->query) { + if (empty($baseQuery)) { + $baseQuery = true; + $pk = $this->parent->getPk(); + $this->query->join($this->middle . ' pivot', 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + } + + $result = call_user_func_array([$this->query, $method], $args); + if ($result instanceof \think\db\Query) { + $this->option = $result->getOptions(); + return $this; + } else { + $this->option = []; + $baseQuery = false; + return $result; + } + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/library/think/model/relation/HasMany.php b/library/think/model/relation/HasMany.php new file mode 100644 index 00000000..5c7d129a --- /dev/null +++ b/library/think/model/relation/HasMany.php @@ -0,0 +1,251 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Model; +use think\model\Relation; + +class HasMany extends Relation +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param array $alias 别名定义 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $alias = []) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->alias = $alias; + $this->query = (new $model)->db(); + } + + // 动态获取关联数据 + public function getRelation() + { + return $this->select(); + } + + /** + * 预载入关联查询 + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param bool $first 是否需要使用基础表 + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->where[$foreignKey] = ['in', $range]; + $data = $this->eagerlyOneToMany($this, [ + $foreignKey => [ + 'in', + $range, + ], + ], $relation, $subRelation, $closure); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + $result->setAttr($relation, $this->resultSetBuild($data[$result->$localKey], $class)); + } + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return Model + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + if (isset($result->$localKey)) { + $data = $this->eagerlyOneToMany($this, [$foreignKey => $result->$localKey], $relation, $subRelation, $closure); + // 关联数据封装 + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + $result->setAttr($relation, $this->resultSetBuild($data[$result->$localKey], $class)); + } + } + + /** + * 一对多 关联模型预查询 + * @access public + * @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) + { + $foreignKey = $this->foreignKey; + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + } + $list = $model->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$foreignKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return integer + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + $model = new $this->model; + return $model->save($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + $result = $this->save($data); + } + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param Model $model 模型对象 + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Query + */ + public static function has($model, $operator = '>=', $count = 1, $id = '*') + { + $table = $this->query->getTable(); + return $model->db()->alias('a') + ->join($table . ' b', 'a.' . $this->localKey . '=b.' . $this->foreignKey, $this->joinType) + ->group('b.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param Model $model 模型对象 + * @param mixed $where 查询条件(数组或者闭包) + * @return Query + */ + public static function hasWhere($model, $where = []) + { + $table = $this->query->getTable(); + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where['b.' . $key] = $val; + unset($where[$key]); + } + } + } + return $model->db()->alias('a') + ->field('a.*') + ->join($table . ' b', 'a.' . $this->localKey . '=b.' . $this->foreignKey, $this->joinType) + ->where($where); + } + + public function __call($method, $args) + { + static $baseQuery = false; + if ($this->query) { + if (empty($baseQuery)) { + $baseQuery = true; + if (isset($this->where)) { + $this->query->where($this->where); + } elseif (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); + } + } + + $result = call_user_func_array([$this->query, $method], $args); + if ($result instanceof \think\db\Query) { + $this->option = $result->getOptions(); + return $this; + } else { + $this->option = []; + $baseQuery = false; + return $result; + } + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/library/think/model/relation/HasManyThrough.php b/library/think/model/relation/HasManyThrough.php new file mode 100644 index 00000000..8f76f573 --- /dev/null +++ b/library/think/model/relation/HasManyThrough.php @@ -0,0 +1,124 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Model; +use think\model\Relation; + +class HasManyThrough extends Relation +{ + // 中间关联表外键 + protected $throughKey; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $firstkey 关联外键 + * @param string $secondKey 关联外键 + * @param string $localKey 关联主键 + * @param array $alias 别名定义 + */ + public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey, $alias = []) + { + $this->parent = $parent; + $this->model = $model; + $this->middle = $through; + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->alias = $alias; + $this->query = (new $model)->db(); + } + + // 动态获取关联数据 + public function getRelation() + { + return $this->select(); + } + + /** + * 预载入关联查询 + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param bool $first 是否需要使用基础表 + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + { + + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return Model + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + { + + } + + public function __call($method, $args) + { + static $baseQuery = false; + if ($this->query) { + if (empty($baseQuery)) { + $baseQuery = true; + $through = $this->middle; + $model = $this->model; + $alias = Loader::parseName(basename(str_replace('\\', '/', $model))); + $throughTable = $through::getTable(); + $pk = (new $this->model)->getPk(); + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $this->query->field($alias . '.*')->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + } + + $result = call_user_func_array([$this->query, $method], $args); + if ($result instanceof \think\db\Query) { + $this->option = $result->getOptions(); + return $this; + } else { + $this->option = []; + $baseQuery = false; + return $result; + } + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/library/think/model/relation/HasOne.php b/library/think/model/relation/HasOne.php new file mode 100644 index 00000000..c7b71c53 --- /dev/null +++ b/library/think/model/relation/HasOne.php @@ -0,0 +1,174 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasOne extends Relation +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @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') + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->alias = $alias; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + } + + // 动态获取关联数据 + public function getRelation() + { + // 执行关联定义方法 + $localKey = $this->localKey; + + // 判断关联类型执行查询 + return $this->query->where($this->foreignKey, $this->parent->$localKey)->find(); + } + + /** + * 预载入关联查询 + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @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; + if ($first) { + $table = $query->getTable(); + $query->table([$table => $alias]); + if ($query->getOptions('field')) { + $field = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $field = true; + } + $query->field($field, false, $table, $alias); + + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinName = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $joinAlias = isset($this->alias[$joinName]) ? $this->alias[$joinName] : $relation; + $query->via($joinAlias); + + $query->join($joinTable . ' ' . $joinAlias, $alias . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); + + if ($closure) { + // 执行闭包查询 + call_user_func_array($closure, [ & $query]); + //指定获取关联的字段 + //需要在 回调中 调方法 withField 方法,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + $closure = null; + } elseif (isset($this->option['field'])) { + $field = $this->option['field']; + } else { + $field = true; + } + $query->field($field, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation) + { + foreach ($resultSet as $result) { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return Model + */ + public function eagerlyResult(&$result, $relation) + { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + $result->setAttr($relation, !isset($list[$relation]) ? null : (new $model($list[$relation]))->isUpdate(true)); + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return integer + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + $model = new $this->model; + return $model->save($data); + } +} diff --git a/library/think/model/relation/MorphMany.php b/library/think/model/relation/MorphMany.php new file mode 100644 index 00000000..9d6371ba --- /dev/null +++ b/library/think/model/relation/MorphMany.php @@ -0,0 +1,175 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Model; +use think\model\Relation; + +class MorphMany extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + // 动态获取关联数据 + public function getRelation() + { + return $this->select(); + } + + /** + * 预载入关联查询 + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param bool $first 是否需要使用基础表 + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $this->where[$morphKey] = ['in', $range]; + $this->where[$morphType] = $type; + $data = $this->eagerlyMorphToMany([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); + } + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return Model + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $pk = $result->getPk(); + if (isset($result->$pk)) { + $data = $this->eagerlyMorphToMany([$morphKey => $result->$pk, $morphType => $type], $relation, $subRelation, $closure); + $result->setAttr($relation, $this->resultSetBuild($data[$result->$pk], $class)); + } + } + + /** + * 多态一对多 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @return array + */ + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + public function __call($method, $args) + { + static $baseQuery = false; + if ($this->query) { + if (empty($baseQuery)) { + $baseQuery = true; + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + } + + $result = call_user_func_array([$this->query, $method], $args); + if ($result instanceof \think\db\Query) { + $this->option = $result->getOptions(); + return $this; + } else { + $this->option = []; + $baseQuery = false; + return $result; + } + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/library/think/model/relation/MorphTo.php b/library/think/model/relation/MorphTo.php new file mode 100644 index 00000000..0d55d2e4 --- /dev/null +++ b/library/think/model/relation/MorphTo.php @@ -0,0 +1,166 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphTo extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + */ + public function __construct(Model $parent, $morphType, $morphKey, $alias = []) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + } + + // 动态获取关联数据 + public function getRelation() + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + // 主键数据 + $pk = $this->parent->$morphKey; + return (new $model)->find($pk); + } + + /** + * 解析模型的完整命名空间 + * @access public + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 预载入关联查询 + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param bool $first 是否需要使用基础表 + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->all($val, $subRelation); + $data = []; + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + if (!isset($data[$result->$morphKey])) { + $data[$result->$morphKey] = []; + } + $result->setAttr($relation, $this->resultSetBuild($data[$result->$morphKey], $class)); + } + } + } + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @param string $class 数据集对象名 为空表示数组 + * @return Model + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + } + + /** + * 多态MorphTo 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @return array + */ + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation)->find($pk); + if ($data) { + $data->isUpdate(true); + } + $result->setAttr($relation, $data ?: null); + } + +}