thinkphp-wechat/thinkphp/library/think/model/concern/RelationShip.php

671 lines
20 KiB
PHP
Raw Permalink Normal View History

2021-12-24 16:40:05 +08:00
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\model\concern;
use think\Collection;
use think\db\Query;
use think\Loader;
use think\Model;
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\MorphOne;
use think\model\relation\MorphTo;
/**
* 模型关联处理
*/
trait RelationShip
{
/**
* 父关联模型对象
* @var object
*/
private $parent;
/**
* 模型关联数据
* @var array
*/
private $relation = [];
/**
* 关联写入定义信息
* @var array
*/
private $together;
/**
* 关联自动写入信息
* @var array
*/
protected $relationWrite;
/**
* 设置父关联对象
* @access public
* @param Model $model 模型对象
* @return $this
*/
public function setParent($model)
{
$this->parent = $model;
return $this;
}
/**
* 获取父关联对象
* @access public
* @return Model
*/
public function getParent()
{
return $this->parent;
}
/**
* 获取当前模型的关联模型数据
* @access public
* @param string $name 关联方法名
* @return mixed
*/
public function getRelation($name = null)
{
if (is_null($name)) {
return $this->relation;
} elseif (array_key_exists($name, $this->relation)) {
return $this->relation[$name];
}
return;
}
/**
* 设置关联数据对象值
* @access public
* @param string $name 属性名
* @param mixed $value 属性值
* @param array $data 数据
* @return $this
*/
public function setRelation($name, $value, $data = [])
{
// 检测修改器
$method = 'set' . Loader::parseName($name, 1) . 'Attr';
if (method_exists($this, $method)) {
$value = $this->$method($value, array_merge($this->data, $data));
}
$this->relation[$name] = $value;
return $this;
}
/**
* 关联数据写入
* @access public
* @param array|string $relation 关联
* @return $this
*/
public function together($relation)
{
if (is_string($relation)) {
$relation = explode(',', $relation);
}
$this->together = $relation;
$this->checkAutoRelationWrite();
return $this;
}
/**
* 根据关联条件查询当前模型
* @access public
* @param string $relation 关联方法名
* @param mixed $operator 比较操作符
* @param integer $count 个数
* @param string $id 关联表的统计字段
* @param string $joinType JOIN类型
* @return Query
*/
public static function has($relation, $operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
{
$relation = (new static())->$relation();
if (is_array($operator) || $operator instanceof \Closure) {
return $relation->hasWhere($operator);
}
return $relation->has($operator, $count, $id, $joinType);
}
/**
* 根据关联条件查询当前模型
* @access public
* @param string $relation 关联方法名
* @param mixed $where 查询条件(数组或者闭包)
* @param mixed $fields 字段
* @return Query
*/
public static function hasWhere($relation, $where = [], $fields = '*')
{
return (new static())->$relation()->hasWhere($where, $fields);
}
/**
* 查询当前模型的关联数据
* @access public
* @param string|array $relations 关联名
* @param array $withRelationAttr 关联获取器
* @return $this
*/
public function relationQuery($relations, $withRelationAttr = [])
{
if (is_string($relations)) {
$relations = explode(',', $relations);
}
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);
$relationName = Loader::parseName($relation);
$relationResult = $this->$method();
if (isset($withRelationAttr[$relationName])) {
$relationResult->getQuery()->withAttr($withRelationAttr[$relationName]);
}
$this->relation[$relation] = $relationResult->getRelation($subRelation, $closure);
}
return $this;
}
/**
* 预载入关联查询 返回数据集
* @access public
* @param array $resultSet 数据集
* @param string $relation 关联名
* @param array $withRelationAttr 关联获取器
* @param bool $join 是否为JOIN方式
* @return array
*/
public function eagerlyResultSet(&$resultSet, $relation, $withRelationAttr = [], $join = false)
{
$relations = is_string($relation) ? explode(',', $relation) : $relation;
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);
}
$relation = Loader::parseName($relation, 1, false);
$relationName = Loader::parseName($relation);
$relationResult = $this->$relation();
if (isset($withRelationAttr[$relationName])) {
$relationResult->getQuery()->withAttr($withRelationAttr[$relationName]);
}
$relationResult->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $join);
}
}
/**
* 预载入关联查询 返回模型对象
* @access public
* @param Model $result 数据对象
* @param string $relation 关联名
* @param array $withRelationAttr 关联获取器
* @param bool $join 是否为JOIN方式
* @return Model
*/
public function eagerlyResult(&$result, $relation, $withRelationAttr = [], $join = false)
{
$relations = is_string($relation) ? explode(',', $relation) : $relation;
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);
}
$relation = Loader::parseName($relation, 1, false);
$relationName = Loader::parseName($relation);
$relationResult = $this->$relation();
if (isset($withRelationAttr[$relationName])) {
$relationResult->getQuery()->withAttr($withRelationAttr[$relationName]);
}
$relationResult->eagerlyResult($result, $relation, $subRelation, $closure, $join);
}
}
/**
* 关联统计
* @access public
* @param Model $result 数据对象
* @param array $relations 关联名
* @param string $aggregate 聚合查询方法
* @param string $field 字段
* @return void
*/
public function relationCount(&$result, $relations, $aggregate = 'sum', $field = '*')
{
foreach ($relations as $key => $relation) {
$closure = $name = null;
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, $aggregate, $field, $name);
if (empty($name)) {
$name = Loader::parseName($relation) . '_' . $aggregate;
}
$result->setAttr($name, $count);
}
}
/**
* HAS ONE 关联定义
* @access public
* @param string $model 模型名
* @param string $foreignKey 关联外键
* @param string $localKey 当前主键
* @return HasOne
*/
public function hasOne($model, $foreignKey = '', $localKey = '')
{
// 记录当前关联信息
$model = $this->parseModel($model);
$localKey = $localKey ?: $this->getPk();
$foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
return new HasOne($this, $model, $foreignKey, $localKey);
}
/**
* BELONGS TO 关联定义
* @access public
* @param string $model 模型名
* @param string $foreignKey 关联外键
* @param string $localKey 关联主键
* @return BelongsTo
*/
public function belongsTo($model, $foreignKey = '', $localKey = '')
{
// 记录当前关联信息
$model = $this->parseModel($model);
$foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName());
$localKey = $localKey ?: (new $model)->getPk();
$trace = debug_backtrace(false, 2);
$relation = Loader::parseName($trace[1]['function']);
return new BelongsTo($this, $model, $foreignKey, $localKey, $relation);
}
/**
* HAS MANY 关联定义
* @access public
* @param string $model 模型名
* @param string $foreignKey 关联外键
* @param string $localKey 当前主键
* @return HasMany
*/
public function hasMany($model, $foreignKey = '', $localKey = '')
{
// 记录当前关联信息
$model = $this->parseModel($model);
$localKey = $localKey ?: $this->getPk();
$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 $foreignKey 关联外键
* @param string $throughKey 关联外键
* @param string $localKey 当前主键
* @return HasManyThrough
*/
public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '')
{
// 记录当前关联信息
$model = $this->parseModel($model);
$through = $this->parseModel($through);
$localKey = $localKey ?: $this->getPk();
$foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
$throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName());
return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey);
}
/**
* BELONGS TO MANY 关联定义
* @access public
* @param string $model 模型名
* @param string $table 中间表名
* @param string $foreignKey 关联外键
* @param string $localKey 当前模型关联键
* @return BelongsToMany
*/
public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '')
{
// 记录当前关联信息
$model = $this->parseModel($model);
$name = Loader::parseName(basename(str_replace('\\', '/', $model)));
$table = $table ?: Loader::parseName($this->name) . '_' . $name;
$foreignKey = $foreignKey ?: $name . '_id';
$localKey = $localKey ?: $this->getForeignKey($this->name);
return new BelongsToMany($this, $model, $table, $foreignKey, $localKey);
}
/**
* MORPH One 关联定义
* @access public
* @param string $model 模型名
* @param string|array $morph 多态字段信息
* @param string $type 多态类型
* @return MorphOne
*/
public function morphOne($model, $morph = null, $type = '')
{
// 记录当前关联信息
$model = $this->parseModel($model);
if (is_null($morph)) {
$trace = debug_backtrace(false, 2);
$morph = Loader::parseName($trace[1]['function']);
}
if (is_array($morph)) {
list($morphType, $foreignKey) = $morph;
} else {
$morphType = $morph . '_type';
$foreignKey = $morph . '_id';
}
$type = $type ?: get_class($this);
return new MorphOne($this, $model, $foreignKey, $morphType, $type);
}
/**
* MORPH MANY 关联定义
* @access public
* @param string $model 模型名
* @param string|array $morph 多态字段信息
* @param string $type 多态类型
* @return MorphMany
*/
public function morphMany($model, $morph = null, $type = '')
{
// 记录当前关联信息
$model = $this->parseModel($model);
if (is_null($morph)) {
$trace = debug_backtrace(false, 2);
$morph = Loader::parseName($trace[1]['function']);
}
$type = $type ?: get_class($this);
if (is_array($morph)) {
list($morphType, $foreignKey) = $morph;
} else {
$morphType = $morph . '_type';
$foreignKey = $morph . '_id';
}
return new MorphMany($this, $model, $foreignKey, $morphType, $type);
}
/**
* MORPH TO 关联定义
* @access public
* @param string|array $morph 多态字段信息
* @param array $alias 多态别名定义
* @return MorphTo
*/
public function morphTo($morph = null, $alias = [])
{
$trace = debug_backtrace(false, 2);
$relation = Loader::parseName($trace[1]['function']);
if (is_null($morph)) {
$morph = $relation;
}
// 记录当前关联信息
if (is_array($morph)) {
list($morphType, $foreignKey) = $morph;
} else {
$morphType = $morph . '_type';
$foreignKey = $morph . '_id';
}
return new MorphTo($this, $morphType, $foreignKey, $alias, $relation);
}
/**
* 解析模型的完整命名空间
* @access protected
* @param string $model 模型名(或者完整类名)
* @return string
*/
protected function parseModel($model)
{
if (false === strpos($model, '\\')) {
$path = explode('\\', static::class);
array_pop($path);
array_push($path, Loader::parseName($model, 1));
$model = implode('\\', $path);
}
return $model;
}
/**
* 获取模型的默认外键名
* @access protected
* @param string $name 模型名
* @return string
*/
protected function getForeignKey($name)
{
if (strpos($name, '\\')) {
$name = basename(str_replace('\\', '/', $name));
}
return Loader::parseName($name) . '_id';
}
/**
* 检查属性是否为关联属性 如果是则返回关联方法名
* @access protected
* @param string $attr 关联属性名
* @return string|false
*/
protected function isRelationAttr($attr)
{
$relation = Loader::parseName($attr, 1, false);
if (method_exists($this, $relation) && !method_exists('think\Model', $relation)) {
return $relation;
}
return false;
}
/**
* 智能获取关联模型数据
* @access protected
* @param Relation $modelRelation 模型关联对象
* @return mixed
*/
protected function getRelationData(Relation $modelRelation)
{
if ($this->parent && !$modelRelation->isSelfRelation() && get_class($this->parent) == get_class($modelRelation->getModel())) {
$value = $this->parent;
} else {
// 获取关联数据
$value = $modelRelation->getRelation();
}
return $value;
}
/**
* 关联数据自动写入检查
* @access protected
* @return void
*/
protected function checkAutoRelationWrite()
{
foreach ($this->together as $key => $name) {
if (is_array($name)) {
if (key($name) === 0) {
$this->relationWrite[$key] = [];
// 绑定关联属性
foreach ((array) $name as $val) {
if (isset($this->data[$val])) {
$this->relationWrite[$key][$val] = $this->data[$val];
}
}
} else {
// 直接传入关联数据
$this->relationWrite[$key] = $name;
}
} elseif (isset($this->relation[$name])) {
$this->relationWrite[$name] = $this->relation[$name];
} elseif (isset($this->data[$name])) {
$this->relationWrite[$name] = $this->data[$name];
unset($this->data[$name]);
}
}
}
/**
* 自动关联数据更新(针对一对一关联)
* @access protected
* @return void
*/
protected function autoRelationUpdate()
{
foreach ($this->relationWrite as $name => $val) {
if ($val instanceof Model) {
$val->isUpdate()->save();
} else {
$model = $this->getRelation($name);
if ($model instanceof Model) {
$model->isUpdate()->save($val);
}
}
}
}
/**
* 自动关联数据写入(针对一对一关联)
* @access protected
* @return void
*/
protected function autoRelationInsert()
{
foreach ($this->relationWrite as $name => $val) {
$method = Loader::parseName($name, 1, false);
$this->$method()->save($val);
}
}
/**
* 自动关联数据删除(支持一对一及一对多关联)
* @access protected
* @return void
*/
protected function autoRelationDelete()
{
foreach ($this->relationWrite as $key => $name) {
$name = is_numeric($key) ? $name : $key;
$result = $this->getRelation($name);
if ($result instanceof Model) {
$result->delete();
} elseif ($result instanceof Collection) {
foreach ($result as $model) {
$model->delete();
}
}
}
}
}