添加网站文件
This commit is contained in:
102
thinkphp/library/think/model/Collection.php
Normal file
102
thinkphp/library/think/model/Collection.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?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: zhangyajun <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
{
|
||||
if (!$this->isEmpty()) {
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要输出的属性
|
||||
* @access public
|
||||
* @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 && $model->append($append, $override);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据字段获取器
|
||||
* @access public
|
||||
* @param string|array $name 字段名
|
||||
* @param callable $callback 闭包获取器
|
||||
* @return $this
|
||||
*/
|
||||
public function withAttr($name, $callback = null)
|
||||
{
|
||||
$this->each(function ($model) use ($name, $callback) {
|
||||
/** @var Model $model */
|
||||
$model && $model->withAttribute($name, $callback);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
42
thinkphp/library/think/model/Pivot.php
Normal file
42
thinkphp/library/think/model/Pivot.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Pivot extends Model
|
||||
{
|
||||
|
||||
/** @var Model */
|
||||
public $parent;
|
||||
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
* @param array|object $data 数据
|
||||
* @param Model $parent 上级模型
|
||||
* @param string $table 中间数据表名
|
||||
*/
|
||||
public function __construct($data = [], Model $parent = null, $table = '')
|
||||
{
|
||||
$this->parent = $parent;
|
||||
|
||||
if (is_null($this->name)) {
|
||||
$this->name = $table;
|
||||
}
|
||||
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
}
|
||||
187
thinkphp/library/think/model/Relation.php
Normal file
187
thinkphp/library/think/model/Relation.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?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;
|
||||
|
||||
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 $baseQuery;
|
||||
// 是否为自关联
|
||||
protected $selfRelation;
|
||||
|
||||
/**
|
||||
* 获取关联的所属模型
|
||||
* @access public
|
||||
* @return Model
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的关联模型类的实例
|
||||
* @access public
|
||||
* @return Model
|
||||
*/
|
||||
public function getModel()
|
||||
{
|
||||
return $this->query->getModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的关联模型类的实例
|
||||
* @access public
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前关联为自关联
|
||||
* @access public
|
||||
* @param bool $self 是否自关联
|
||||
* @return $this
|
||||
*/
|
||||
public function selfRelation($self = true)
|
||||
{
|
||||
$this->selfRelation = $self;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前关联是否为自关联
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function isSelfRelation()
|
||||
{
|
||||
return $this->selfRelation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装关联数据集
|
||||
* @access public
|
||||
* @param array $resultSet 数据集
|
||||
* @return mixed
|
||||
*/
|
||||
protected function resultSetBuild($resultSet)
|
||||
{
|
||||
return (new $this->model)->toCollection($resultSet);
|
||||
}
|
||||
|
||||
protected function getQueryFields($model)
|
||||
{
|
||||
$fields = $this->query->getOptions('field');
|
||||
return $this->getRelationQueryFields($fields, $model);
|
||||
}
|
||||
|
||||
protected function getRelationQueryFields($fields, $model)
|
||||
{
|
||||
if ($fields) {
|
||||
|
||||
if (is_string($fields)) {
|
||||
$fields = explode(',', $fields);
|
||||
}
|
||||
|
||||
foreach ($fields as &$field) {
|
||||
if (false === strpos($field, '.')) {
|
||||
$field = $model . '.' . $field;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$fields = $model . '.*';
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
protected function getQueryWhere(&$where, $relation)
|
||||
{
|
||||
foreach ($where as $key => &$val) {
|
||||
if (is_string($key)) {
|
||||
$where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val];
|
||||
unset($where[$key]);
|
||||
} elseif (isset($val[0]) && false === strpos($val[0], '.')) {
|
||||
$val[0] = $relation . '.' . $val[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
* @access public
|
||||
* @param array $data 更新数据
|
||||
* @return integer|string
|
||||
*/
|
||||
public function update(array $data = [])
|
||||
{
|
||||
return $this->query->update($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除记录
|
||||
* @access public
|
||||
* @param mixed $data 表达式 true 表示强制删除
|
||||
* @return int
|
||||
* @throws Exception
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function delete($data = null)
|
||||
{
|
||||
return $this->query->delete($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次)
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery()
|
||||
{}
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if ($this->query) {
|
||||
// 执行基础查询
|
||||
$this->baseQuery();
|
||||
|
||||
$result = call_user_func_array([$this->query->getModel(), $method], $args);
|
||||
|
||||
return $result === $this->query && !in_array(strtolower($method), ['fetchsql', 'fetchpdo']) ? $this : $result;
|
||||
} else {
|
||||
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
|
||||
}
|
||||
}
|
||||
}
|
||||
656
thinkphp/library/think/model/concern/Attribute.php
Normal file
656
thinkphp/library/think/model/concern/Attribute.php
Normal file
@@ -0,0 +1,656 @@
|
||||
<?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 InvalidArgumentException;
|
||||
use think\db\Expression;
|
||||
use think\Exception;
|
||||
use think\Loader;
|
||||
use think\model\Relation;
|
||||
|
||||
trait Attribute
|
||||
{
|
||||
/**
|
||||
* 数据表主键 复合主键使用数组定义
|
||||
* @var string|array
|
||||
*/
|
||||
protected $pk = 'id';
|
||||
|
||||
/**
|
||||
* 数据表字段信息 留空则自动获取
|
||||
* @var array
|
||||
*/
|
||||
protected $field = [];
|
||||
|
||||
/**
|
||||
* JSON数据表字段
|
||||
* @var array
|
||||
*/
|
||||
protected $json = [];
|
||||
|
||||
/**
|
||||
* JSON数据取出是否需要转换为数组
|
||||
* @var bool
|
||||
*/
|
||||
protected $jsonAssoc = false;
|
||||
|
||||
/**
|
||||
* JSON数据表字段类型
|
||||
* @var array
|
||||
*/
|
||||
protected $jsonType = [];
|
||||
|
||||
/**
|
||||
* 数据表废弃字段
|
||||
* @var array
|
||||
*/
|
||||
protected $disuse = [];
|
||||
|
||||
/**
|
||||
* 数据表只读字段
|
||||
* @var array
|
||||
*/
|
||||
protected $readonly = [];
|
||||
|
||||
/**
|
||||
* 数据表字段类型
|
||||
* @var array
|
||||
*/
|
||||
protected $type = [];
|
||||
|
||||
/**
|
||||
* 当前模型数据
|
||||
* @var array
|
||||
*/
|
||||
private $data = [];
|
||||
|
||||
/**
|
||||
* 修改器执行记录
|
||||
* @var array
|
||||
*/
|
||||
private $set = [];
|
||||
|
||||
/**
|
||||
* 原始数据
|
||||
* @var array
|
||||
*/
|
||||
private $origin = [];
|
||||
|
||||
/**
|
||||
* 动态获取器
|
||||
* @var array
|
||||
*/
|
||||
private $withAttr = [];
|
||||
|
||||
/**
|
||||
* 获取模型对象的主键
|
||||
* @access public
|
||||
* @return string|array
|
||||
*/
|
||||
public function getPk()
|
||||
{
|
||||
return $this->pk;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断一个字段名是否为主键字段
|
||||
* @access public
|
||||
* @param string $key 名称
|
||||
* @return bool
|
||||
*/
|
||||
protected function isPk($key)
|
||||
{
|
||||
$pk = $this->getPk();
|
||||
if (is_string($pk) && $pk == $key) {
|
||||
return true;
|
||||
} elseif (is_array($pk) && in_array($key, $pk)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型对象的主键值
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
$pk = $this->getPk();
|
||||
if (is_string($pk) && array_key_exists($pk, $this->data)) {
|
||||
return $this->data[$pk];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置允许写入的字段
|
||||
* @access public
|
||||
* @param array|string|true $field 允许写入的字段 如果为true只允许写入数据表字段
|
||||
* @return $this
|
||||
*/
|
||||
public function allowField($field)
|
||||
{
|
||||
if (is_string($field)) {
|
||||
$field = explode(',', $field);
|
||||
}
|
||||
|
||||
$this->field = $field;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置只读字段
|
||||
* @access public
|
||||
* @param array|string $field 只读字段
|
||||
* @return $this
|
||||
*/
|
||||
public function readonly($field)
|
||||
{
|
||||
if (is_string($field)) {
|
||||
$field = explode(',', $field);
|
||||
}
|
||||
|
||||
$this->readonly = $field;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据对象值
|
||||
* @access public
|
||||
* @param mixed $data 数据或者属性名
|
||||
* @param mixed $value 值
|
||||
* @return $this
|
||||
*/
|
||||
public function data($data, $value = null)
|
||||
{
|
||||
if (is_string($data)) {
|
||||
$this->data[$data] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// 清空数据
|
||||
$this->data = [];
|
||||
|
||||
if (is_object($data)) {
|
||||
$data = get_object_vars($data);
|
||||
}
|
||||
|
||||
if ($this->disuse) {
|
||||
// 废弃字段
|
||||
foreach ((array) $this->disuse as $key) {
|
||||
if (array_key_exists($key, $data)) {
|
||||
unset($data[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (true === $value) {
|
||||
// 数据对象赋值
|
||||
foreach ($data as $key => $value) {
|
||||
$this->setAttr($key, $value, $data);
|
||||
}
|
||||
} elseif (is_array($value)) {
|
||||
foreach ($value as $name) {
|
||||
if (isset($data[$name])) {
|
||||
$this->data[$name] = $data[$name];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置数据对象值
|
||||
* @access public
|
||||
* @param mixed $data 数据
|
||||
* @param bool $set 是否需要进行数据处理
|
||||
* @return $this
|
||||
*/
|
||||
public function appendData($data, $set = false)
|
||||
{
|
||||
if ($set) {
|
||||
// 进行数据处理
|
||||
foreach ($data as $key => $value) {
|
||||
$this->setAttr($key, $value, $data);
|
||||
}
|
||||
} else {
|
||||
if (is_object($data)) {
|
||||
$data = get_object_vars($data);
|
||||
}
|
||||
|
||||
$this->data = array_merge($this->data, $data);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象原始数据 如果不存在指定字段返回null
|
||||
* @access public
|
||||
* @param string $name 字段名 留空获取全部
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOrigin($name = null)
|
||||
{
|
||||
if (is_null($name)) {
|
||||
return $this->origin;
|
||||
}
|
||||
return array_key_exists($name, $this->origin) ? $this->origin[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象原始数据 如果不存在指定字段返回false
|
||||
* @access public
|
||||
* @param string $name 字段名 留空获取全部
|
||||
* @return mixed
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function getData($name = null)
|
||||
{
|
||||
if (is_null($name)) {
|
||||
return $this->data;
|
||||
} elseif (array_key_exists($name, $this->data)) {
|
||||
return $this->data[$name];
|
||||
} elseif (array_key_exists($name, $this->relation)) {
|
||||
return $this->relation[$name];
|
||||
}
|
||||
throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取变化的数据 并排除只读数据
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getChangedData()
|
||||
{
|
||||
if ($this->force) {
|
||||
$data = $this->data;
|
||||
} else {
|
||||
$data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
|
||||
if ((empty($a) || empty($b)) && $a !== $b) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return is_object($a) || $a != $b ? 1 : 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($this->readonly)) {
|
||||
// 只读字段不允许更新
|
||||
foreach ($this->readonly as $key => $field) {
|
||||
if (isset($data[$field])) {
|
||||
unset($data[$field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改器 设置数据对象值
|
||||
* @access public
|
||||
* @param string $name 属性名
|
||||
* @param mixed $value 属性值
|
||||
* @param array $data 数据
|
||||
* @return void
|
||||
*/
|
||||
public function setAttr($name, $value, $data = [])
|
||||
{
|
||||
if (isset($this->set[$name])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
|
||||
// 自动写入的时间戳字段
|
||||
$value = $this->autoWriteTimestamp($name);
|
||||
} else {
|
||||
// 检测修改器
|
||||
$method = 'set' . Loader::parseName($name, 1) . 'Attr';
|
||||
|
||||
if (method_exists($this, $method)) {
|
||||
$origin = $this->data;
|
||||
$value = $this->$method($value, array_merge($this->data, $data));
|
||||
|
||||
$this->set[$name] = true;
|
||||
if (is_null($value) && $origin !== $this->data) {
|
||||
return;
|
||||
}
|
||||
} elseif (isset($this->type[$name])) {
|
||||
// 类型转换
|
||||
$value = $this->writeTransform($value, $this->type[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置数据对象属性
|
||||
$this->data[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要自动写入时间字段
|
||||
* @access public
|
||||
* @param bool $auto
|
||||
* @return $this
|
||||
*/
|
||||
public function isAutoWriteTimestamp($auto)
|
||||
{
|
||||
$this->autoWriteTimestamp = $auto;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动写入时间戳
|
||||
* @access protected
|
||||
* @param string $name 时间戳字段
|
||||
* @return mixed
|
||||
*/
|
||||
protected function autoWriteTimestamp($name)
|
||||
{
|
||||
if (isset($this->type[$name])) {
|
||||
$type = $this->type[$name];
|
||||
|
||||
if (strpos($type, ':')) {
|
||||
list($type, $param) = explode(':', $type, 2);
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'datetime':
|
||||
case 'date':
|
||||
$value = $this->formatDateTime('Y-m-d H:i:s.u');
|
||||
break;
|
||||
case 'timestamp':
|
||||
case 'integer':
|
||||
default:
|
||||
$value = time();
|
||||
break;
|
||||
}
|
||||
} elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
|
||||
'datetime',
|
||||
'date',
|
||||
'timestamp',
|
||||
])) {
|
||||
$value = $this->formatDateTime('Y-m-d H:i:s.u');
|
||||
} else {
|
||||
$value = time();
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据写入 类型转换
|
||||
* @access protected
|
||||
* @param mixed $value 值
|
||||
* @param string|array $type 要转换的类型
|
||||
* @return mixed
|
||||
*/
|
||||
protected function writeTransform($value, $type)
|
||||
{
|
||||
if (is_null($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value instanceof Expression) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_array($type)) {
|
||||
list($type, $param) = $type;
|
||||
} elseif (strpos($type, ':')) {
|
||||
list($type, $param) = explode(':', $type, 2);
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'integer':
|
||||
$value = (int) $value;
|
||||
break;
|
||||
case 'float':
|
||||
if (empty($param)) {
|
||||
$value = (float) $value;
|
||||
} else {
|
||||
$value = (float) number_format($value, $param, '.', '');
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
$value = (bool) $value;
|
||||
break;
|
||||
case 'timestamp':
|
||||
if (!is_numeric($value)) {
|
||||
$value = strtotime($value);
|
||||
}
|
||||
break;
|
||||
case 'datetime':
|
||||
$value = is_numeric($value) ? $value : strtotime($value);
|
||||
$value = $this->formatDateTime('Y-m-d H:i:s.u', $value);
|
||||
break;
|
||||
case 'object':
|
||||
if (is_object($value)) {
|
||||
$value = json_encode($value, JSON_FORCE_OBJECT);
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
$value = (array) $value;
|
||||
case 'json':
|
||||
$option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE;
|
||||
$value = json_encode($value, $option);
|
||||
break;
|
||||
case 'serialize':
|
||||
$value = serialize($value);
|
||||
break;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取器 获取数据对象的值
|
||||
* @access public
|
||||
* @param string $name 名称
|
||||
* @param array $item 数据
|
||||
* @return mixed
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function getAttr($name, &$item = null)
|
||||
{
|
||||
try {
|
||||
$notFound = false;
|
||||
$value = $this->getData($name);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$notFound = true;
|
||||
$value = null;
|
||||
}
|
||||
|
||||
// 检测属性获取器
|
||||
$fieldName = Loader::parseName($name);
|
||||
$method = 'get' . Loader::parseName($name, 1) . 'Attr';
|
||||
|
||||
if (isset($this->withAttr[$fieldName])) {
|
||||
if ($notFound && $relation = $this->isRelationAttr($name)) {
|
||||
$modelRelation = $this->$relation();
|
||||
$value = $this->getRelationData($modelRelation);
|
||||
}
|
||||
|
||||
$closure = $this->withAttr[$fieldName];
|
||||
$value = $closure($value, $this->data);
|
||||
} elseif (method_exists($this, $method)) {
|
||||
if ($notFound && $relation = $this->isRelationAttr($name)) {
|
||||
$modelRelation = $this->$relation();
|
||||
$value = $this->getRelationData($modelRelation);
|
||||
}
|
||||
|
||||
$value = $this->$method($value, $this->data);
|
||||
} elseif (isset($this->type[$name])) {
|
||||
// 类型转换
|
||||
$value = $this->readTransform($value, $this->type[$name]);
|
||||
} elseif ($this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
|
||||
if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
|
||||
'datetime',
|
||||
'date',
|
||||
'timestamp',
|
||||
])) {
|
||||
$value = $this->formatDateTime($this->dateFormat, $value);
|
||||
} else {
|
||||
$value = $this->formatDateTime($this->dateFormat, $value, true);
|
||||
}
|
||||
} elseif ($notFound) {
|
||||
$value = $this->getRelationAttribute($name, $item);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联属性值
|
||||
* @access protected
|
||||
* @param string $name 属性名
|
||||
* @param array $item 数据
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRelationAttribute($name, &$item)
|
||||
{
|
||||
$relation = $this->isRelationAttr($name);
|
||||
|
||||
if ($relation) {
|
||||
$modelRelation = $this->$relation();
|
||||
if ($modelRelation instanceof Relation) {
|
||||
$value = $this->getRelationData($modelRelation);
|
||||
|
||||
if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) {
|
||||
|
||||
foreach ($bindAttr as $key => $attr) {
|
||||
$key = is_numeric($key) ? $attr : $key;
|
||||
|
||||
if (isset($item[$key])) {
|
||||
throw new Exception('bind attr has exists:' . $key);
|
||||
} else {
|
||||
$item[$key] = $value ? $value->getAttr($attr) : null;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存关联对象值
|
||||
$this->relation[$name] = $value;
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据读取 类型转换
|
||||
* @access protected
|
||||
* @param mixed $value 值
|
||||
* @param string|array $type 要转换的类型
|
||||
* @return mixed
|
||||
*/
|
||||
protected function readTransform($value, $type)
|
||||
{
|
||||
if (is_null($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($type)) {
|
||||
list($type, $param) = $type;
|
||||
} elseif (strpos($type, ':')) {
|
||||
list($type, $param) = explode(':', $type, 2);
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'integer':
|
||||
$value = (int) $value;
|
||||
break;
|
||||
case 'float':
|
||||
if (empty($param)) {
|
||||
$value = (float) $value;
|
||||
} else {
|
||||
$value = (float) number_format($value, $param, '.', '');
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
$value = (bool) $value;
|
||||
break;
|
||||
case 'timestamp':
|
||||
if (!is_null($value)) {
|
||||
$format = !empty($param) ? $param : $this->dateFormat;
|
||||
$value = $this->formatDateTime($format, $value, true);
|
||||
}
|
||||
break;
|
||||
case 'datetime':
|
||||
if (!is_null($value)) {
|
||||
$format = !empty($param) ? $param : $this->dateFormat;
|
||||
$value = $this->formatDateTime($format, $value);
|
||||
}
|
||||
break;
|
||||
case 'json':
|
||||
$value = json_decode($value, true);
|
||||
break;
|
||||
case 'array':
|
||||
$value = empty($value) ? [] : json_decode($value, true);
|
||||
break;
|
||||
case 'object':
|
||||
$value = empty($value) ? new \stdClass() : json_decode($value);
|
||||
break;
|
||||
case 'serialize':
|
||||
try {
|
||||
$value = unserialize($value);
|
||||
} catch (\Exception $e) {
|
||||
$value = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (false !== strpos($type, '\\')) {
|
||||
// 对象类型
|
||||
$value = new $type($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据字段获取器
|
||||
* @access public
|
||||
* @param string|array $name 字段名
|
||||
* @param callable $callback 闭包获取器
|
||||
* @return $this
|
||||
*/
|
||||
public function withAttribute($name, $callback = null)
|
||||
{
|
||||
if (is_array($name)) {
|
||||
foreach ($name as $key => $val) {
|
||||
$key = Loader::parseName($key);
|
||||
|
||||
$this->withAttr[$key] = $val;
|
||||
}
|
||||
} else {
|
||||
$name = Loader::parseName($name);
|
||||
|
||||
$this->withAttr[$name] = $callback;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
273
thinkphp/library/think/model/concern/Conversion.php
Normal file
273
thinkphp/library/think/model/concern/Conversion.php
Normal file
@@ -0,0 +1,273 @@
|
||||
<?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\Exception;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
use think\model\Collection as ModelCollection;
|
||||
|
||||
/**
|
||||
* 模型数据转换处理
|
||||
*/
|
||||
trait Conversion
|
||||
{
|
||||
/**
|
||||
* 数据输出显示的属性
|
||||
* @var array
|
||||
*/
|
||||
protected $visible = [];
|
||||
|
||||
/**
|
||||
* 数据输出隐藏的属性
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [];
|
||||
|
||||
/**
|
||||
* 数据输出需要追加的属性
|
||||
* @var array
|
||||
*/
|
||||
protected $append = [];
|
||||
|
||||
/**
|
||||
* 数据集对象名
|
||||
* @var string
|
||||
*/
|
||||
protected $resultSetType;
|
||||
|
||||
/**
|
||||
* 设置需要附加的输出属性
|
||||
* @access public
|
||||
* @param array $append 属性列表
|
||||
* @param bool $override 是否覆盖
|
||||
* @return $this
|
||||
*/
|
||||
public function append(array $append = [], $override = false)
|
||||
{
|
||||
$this->append = $override ? $append : array_merge($this->append, $append);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置附加关联对象的属性
|
||||
* @access public
|
||||
* @param string $attr 关联属性
|
||||
* @param string|array $append 追加属性名
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*/
|
||||
public function appendRelationAttr($attr, $append)
|
||||
{
|
||||
if (is_string($append)) {
|
||||
$append = explode(',', $append);
|
||||
}
|
||||
|
||||
$relation = Loader::parseName($attr, 1, false);
|
||||
if (isset($this->relation[$relation])) {
|
||||
$model = $this->relation[$relation];
|
||||
} else {
|
||||
$model = $this->getRelationData($this->$relation());
|
||||
}
|
||||
|
||||
if ($model instanceof Model) {
|
||||
foreach ($append as $key => $attr) {
|
||||
$key = is_numeric($key) ? $attr : $key;
|
||||
if (isset($this->data[$key])) {
|
||||
throw new Exception('bind attr has exists:' . $key);
|
||||
} else {
|
||||
$this->data[$key] = $model->getAttr($attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要隐藏的输出属性
|
||||
* @access public
|
||||
* @param array $hidden 属性列表
|
||||
* @param bool $override 是否覆盖
|
||||
* @return $this
|
||||
*/
|
||||
public function hidden(array $hidden = [], $override = false)
|
||||
{
|
||||
$this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要输出的属性
|
||||
* @access public
|
||||
* @param array $visible
|
||||
* @param bool $override 是否覆盖
|
||||
* @return $this
|
||||
*/
|
||||
public function visible(array $visible = [], $override = false)
|
||||
{
|
||||
$this->visible = $override ? $visible : array_merge($this->visible, $visible);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换当前模型对象为数组
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$item = [];
|
||||
$hasVisible = false;
|
||||
|
||||
foreach ($this->visible as $key => $val) {
|
||||
if (is_string($val)) {
|
||||
if (strpos($val, '.')) {
|
||||
list($relation, $name) = explode('.', $val);
|
||||
$this->visible[$relation][] = $name;
|
||||
} else {
|
||||
$this->visible[$val] = true;
|
||||
$hasVisible = true;
|
||||
}
|
||||
unset($this->visible[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->hidden as $key => $val) {
|
||||
if (is_string($val)) {
|
||||
if (strpos($val, '.')) {
|
||||
list($relation, $name) = explode('.', $val);
|
||||
$this->hidden[$relation][] = $name;
|
||||
} else {
|
||||
$this->hidden[$val] = true;
|
||||
}
|
||||
unset($this->hidden[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// 合并关联数据
|
||||
$data = array_merge($this->data, $this->relation);
|
||||
|
||||
foreach ($data as $key => $val) {
|
||||
if ($val instanceof Model || $val instanceof ModelCollection) {
|
||||
// 关联模型对象
|
||||
if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
|
||||
$val->visible($this->visible[$key]);
|
||||
} elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
|
||||
$val->hidden($this->hidden[$key]);
|
||||
}
|
||||
// 关联模型对象
|
||||
if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
|
||||
$item[$key] = $val->toArray();
|
||||
}
|
||||
} elseif (isset($this->visible[$key])) {
|
||||
$item[$key] = $this->getAttr($key);
|
||||
} elseif (!isset($this->hidden[$key]) && !$hasVisible) {
|
||||
$item[$key] = $this->getAttr($key);
|
||||
}
|
||||
}
|
||||
|
||||
// 追加属性(必须定义获取器)
|
||||
if (!empty($this->append)) {
|
||||
foreach ($this->append as $key => $name) {
|
||||
if (is_array($name)) {
|
||||
// 追加关联对象属性
|
||||
$relation = $this->getRelation($key);
|
||||
|
||||
if (!$relation) {
|
||||
$relation = $this->getAttr($key);
|
||||
if ($relation) {
|
||||
$relation->visible($name);
|
||||
}
|
||||
}
|
||||
|
||||
$item[$key] = $relation ? $relation->append($name)->toArray() : [];
|
||||
} elseif (strpos($name, '.')) {
|
||||
list($key, $attr) = explode('.', $name);
|
||||
// 追加关联对象属性
|
||||
$relation = $this->getRelation($key);
|
||||
|
||||
if (!$relation) {
|
||||
$relation = $this->getAttr($key);
|
||||
if ($relation) {
|
||||
$relation->visible([$attr]);
|
||||
}
|
||||
}
|
||||
|
||||
$item[$key] = $relation ? $relation->append([$attr])->toArray() : [];
|
||||
} else {
|
||||
$item[$name] = $this->getAttr($name, $item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换当前模型对象为JSON字符串
|
||||
* @access public
|
||||
* @param integer $options json参数
|
||||
* @return string
|
||||
*/
|
||||
public function toJson($options = JSON_UNESCAPED_UNICODE)
|
||||
{
|
||||
return json_encode($this->toArray(), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除当前模型的关联属性
|
||||
* @access public
|
||||
* @return $this
|
||||
*/
|
||||
public function removeRelation()
|
||||
{
|
||||
$this->relation = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
// JsonSerializable
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换数据集为数据集对象
|
||||
* @access public
|
||||
* @param array|Collection $collection 数据集
|
||||
* @param string $resultSetType 数据集类
|
||||
* @return Collection
|
||||
*/
|
||||
public function toCollection($collection, $resultSetType = null)
|
||||
{
|
||||
$resultSetType = $resultSetType ?: $this->resultSetType;
|
||||
|
||||
if ($resultSetType && false !== strpos($resultSetType, '\\')) {
|
||||
$collection = new $resultSetType($collection);
|
||||
} else {
|
||||
$collection = new ModelCollection($collection);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
}
|
||||
238
thinkphp/library/think/model/concern/ModelEvent.php
Normal file
238
thinkphp/library/think/model/concern/ModelEvent.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?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\Container;
|
||||
use think\Loader;
|
||||
|
||||
/**
|
||||
* 模型事件处理
|
||||
*/
|
||||
trait ModelEvent
|
||||
{
|
||||
/**
|
||||
* 模型回调
|
||||
* @var array
|
||||
*/
|
||||
private static $event = [];
|
||||
|
||||
/**
|
||||
* 模型事件观察
|
||||
* @var array
|
||||
*/
|
||||
protected static $observe = ['before_write', 'after_write', 'before_insert', 'after_insert', 'before_update', 'after_update', 'before_delete', 'after_delete', 'before_restore', 'after_restore'];
|
||||
|
||||
/**
|
||||
* 绑定模型事件观察者类
|
||||
* @var array
|
||||
*/
|
||||
protected $observerClass;
|
||||
|
||||
/**
|
||||
* 是否需要事件响应
|
||||
* @var bool
|
||||
*/
|
||||
private $withEvent = true;
|
||||
|
||||
/**
|
||||
* 注册回调方法
|
||||
* @access public
|
||||
* @param string $event 事件名
|
||||
* @param callable $callback 回调方法
|
||||
* @param bool $override 是否覆盖
|
||||
* @return void
|
||||
*/
|
||||
public static function event($event, $callback, $override = false)
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
if ($override) {
|
||||
self::$event[$class][$event] = [];
|
||||
}
|
||||
|
||||
self::$event[$class][$event][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除回调方法
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public static function flushEvent()
|
||||
{
|
||||
self::$event[static::class] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个模型观察者
|
||||
*
|
||||
* @param object|string $class
|
||||
* @return void
|
||||
*/
|
||||
public static function observe($class)
|
||||
{
|
||||
self::flushEvent();
|
||||
|
||||
foreach (static::$observe as $event) {
|
||||
$eventFuncName = Loader::parseName($event, 1, false);
|
||||
|
||||
if (method_exists($class, $eventFuncName)) {
|
||||
static::event($event, [$class, $eventFuncName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前操作的事件响应
|
||||
* @access protected
|
||||
* @param bool $event 是否需要事件响应
|
||||
* @return $this
|
||||
*/
|
||||
public function withEvent($event)
|
||||
{
|
||||
$this->withEvent = $event;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @access protected
|
||||
* @param string $event 事件名
|
||||
* @return bool
|
||||
*/
|
||||
protected function trigger($event)
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
if ($this->withEvent && isset(self::$event[$class][$event])) {
|
||||
foreach (self::$event[$class][$event] as $callback) {
|
||||
$result = Container::getInstance()->invoke($callback, [$this]);
|
||||
|
||||
if (false === $result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型before_insert事件快捷方法
|
||||
* @access protected
|
||||
* @param callable $callback
|
||||
* @param bool $override
|
||||
*/
|
||||
protected static function beforeInsert($callback, $override = false)
|
||||
{
|
||||
self::event('before_insert', $callback, $override);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型after_insert事件快捷方法
|
||||
* @access protected
|
||||
* @param callable $callback
|
||||
* @param bool $override
|
||||
*/
|
||||
protected static function afterInsert($callback, $override = false)
|
||||
{
|
||||
self::event('after_insert', $callback, $override);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型before_update事件快捷方法
|
||||
* @access protected
|
||||
* @param callable $callback
|
||||
* @param bool $override
|
||||
*/
|
||||
protected static function beforeUpdate($callback, $override = false)
|
||||
{
|
||||
self::event('before_update', $callback, $override);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型after_update事件快捷方法
|
||||
* @access protected
|
||||
* @param callable $callback
|
||||
* @param bool $override
|
||||
*/
|
||||
protected static function afterUpdate($callback, $override = false)
|
||||
{
|
||||
self::event('after_update', $callback, $override);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型before_write事件快捷方法
|
||||
* @access protected
|
||||
* @param callable $callback
|
||||
* @param bool $override
|
||||
*/
|
||||
protected static function beforeWrite($callback, $override = false)
|
||||
{
|
||||
self::event('before_write', $callback, $override);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型after_write事件快捷方法
|
||||
* @access protected
|
||||
* @param callable $callback
|
||||
* @param bool $override
|
||||
*/
|
||||
protected static function afterWrite($callback, $override = false)
|
||||
{
|
||||
self::event('after_write', $callback, $override);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型before_delete事件快捷方法
|
||||
* @access protected
|
||||
* @param callable $callback
|
||||
* @param bool $override
|
||||
*/
|
||||
protected static function beforeDelete($callback, $override = false)
|
||||
{
|
||||
self::event('before_delete', $callback, $override);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型after_delete事件快捷方法
|
||||
* @access protected
|
||||
* @param callable $callback
|
||||
* @param bool $override
|
||||
*/
|
||||
protected static function afterDelete($callback, $override = false)
|
||||
{
|
||||
self::event('after_delete', $callback, $override);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型before_restore事件快捷方法
|
||||
* @access protected
|
||||
* @param callable $callback
|
||||
* @param bool $override
|
||||
*/
|
||||
protected static function beforeRestore($callback, $override = false)
|
||||
{
|
||||
self::event('before_restore', $callback, $override);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型after_restore事件快捷方法
|
||||
* @access protected
|
||||
* @param callable $callback
|
||||
* @param bool $override
|
||||
*/
|
||||
protected static function afterRestore($callback, $override = false)
|
||||
{
|
||||
self::event('after_restore', $callback, $override);
|
||||
}
|
||||
}
|
||||
670
thinkphp/library/think/model/concern/RelationShip.php
Normal file
670
thinkphp/library/think/model/concern/RelationShip.php
Normal file
@@ -0,0 +1,670 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
246
thinkphp/library/think/model/concern/SoftDelete.php
Normal file
246
thinkphp/library/think/model/concern/SoftDelete.php
Normal file
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
namespace think\model\concern;
|
||||
|
||||
use think\db\Query;
|
||||
|
||||
/**
|
||||
* 数据软删除
|
||||
*/
|
||||
trait SoftDelete
|
||||
{
|
||||
|
||||
/**
|
||||
* 是否包含软删除数据
|
||||
* @var bool
|
||||
*/
|
||||
protected $withTrashed = false;
|
||||
|
||||
/**
|
||||
* 判断当前实例是否被软删除
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function trashed()
|
||||
{
|
||||
$field = $this->getDeleteTimeField();
|
||||
|
||||
if ($field && !empty($this->getOrigin($field))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询软删除数据
|
||||
* @access public
|
||||
* @return Query
|
||||
*/
|
||||
public static function withTrashed()
|
||||
{
|
||||
$model = new static();
|
||||
|
||||
return $model->withTrashedData(true)->db(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否包含软删除数据
|
||||
* @access protected
|
||||
* @param bool $withTrashed 是否包含软删除数据
|
||||
* @return $this
|
||||
*/
|
||||
protected function withTrashedData($withTrashed)
|
||||
{
|
||||
$this->withTrashed = $withTrashed;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只查询软删除数据
|
||||
* @access public
|
||||
* @return Query
|
||||
*/
|
||||
public static function onlyTrashed()
|
||||
{
|
||||
$model = new static();
|
||||
$field = $model->getDeleteTimeField(true);
|
||||
|
||||
if ($field) {
|
||||
return $model
|
||||
->db(false)
|
||||
->useSoftDelete($field, $model->getWithTrashedExp());
|
||||
}
|
||||
|
||||
return $model->db(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取软删除数据的查询条件
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function getWithTrashedExp()
|
||||
{
|
||||
return is_null($this->defaultSoftDelete) ?
|
||||
['notnull', ''] : ['<>', $this->defaultSoftDelete];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前的记录
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function delete($force = false)
|
||||
{
|
||||
if (!$this->isExists() || false === $this->trigger('before_delete', $this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$force = $force ?: $this->isForce();
|
||||
$name = $this->getDeleteTimeField();
|
||||
|
||||
if ($name && !$force) {
|
||||
// 软删除
|
||||
$this->data($name, $this->autoWriteTimestamp($name));
|
||||
|
||||
$result = $this->isUpdate()->withEvent(false)->save();
|
||||
|
||||
$this->withEvent(true);
|
||||
} else {
|
||||
// 读取更新条件
|
||||
$where = $this->getWhere();
|
||||
|
||||
// 删除当前模型数据
|
||||
$result = $this->db(false)
|
||||
->where($where)
|
||||
->removeOption('soft_delete')
|
||||
->delete();
|
||||
}
|
||||
|
||||
// 关联删除
|
||||
if (!empty($this->relationWrite)) {
|
||||
$this->autoRelationDelete();
|
||||
}
|
||||
|
||||
$this->trigger('after_delete', $this);
|
||||
|
||||
$this->exists(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除记录
|
||||
* @access public
|
||||
* @param mixed $data 主键列表 支持闭包查询条件
|
||||
* @param bool $force 是否强制删除
|
||||
* @return bool
|
||||
*/
|
||||
public static function destroy($data, $force = false)
|
||||
{
|
||||
// 传入空不执行删除,但是0可以删除
|
||||
if (empty($data) && 0 !== $data) {
|
||||
return false;
|
||||
}
|
||||
// 包含软删除数据
|
||||
$query = (new static())->db(false);
|
||||
|
||||
if (is_array($data) && key($data) !== 0) {
|
||||
$query->where($data);
|
||||
$data = null;
|
||||
} elseif ($data instanceof \Closure) {
|
||||
call_user_func_array($data, [ & $query]);
|
||||
$data = null;
|
||||
} elseif (is_null($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$resultSet = $query->select($data);
|
||||
|
||||
if ($resultSet) {
|
||||
foreach ($resultSet as $data) {
|
||||
$data->force($force)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复被软删除的记录
|
||||
* @access public
|
||||
* @param array $where 更新条件
|
||||
* @return bool
|
||||
*/
|
||||
public function restore($where = [])
|
||||
{
|
||||
$name = $this->getDeleteTimeField();
|
||||
|
||||
if ($name) {
|
||||
if (false === $this->trigger('before_restore')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($where)) {
|
||||
$pk = $this->getPk();
|
||||
|
||||
$where[] = [$pk, '=', $this->getData($pk)];
|
||||
}
|
||||
|
||||
// 恢复删除
|
||||
$this->db(false)
|
||||
->where($where)
|
||||
->useSoftDelete($name, $this->getWithTrashedExp())
|
||||
->update([$name => $this->defaultSoftDelete]);
|
||||
|
||||
$this->trigger('after_restore');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取软删除字段
|
||||
* @access protected
|
||||
* @param bool $read 是否查询操作 写操作的时候会自动去掉表别名
|
||||
* @return string|false
|
||||
*/
|
||||
protected function getDeleteTimeField($read = false)
|
||||
{
|
||||
$field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time';
|
||||
|
||||
if (false === $field) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false === strpos($field, '.')) {
|
||||
$field = '__TABLE__.' . $field;
|
||||
}
|
||||
|
||||
if (!$read && strpos($field, '.')) {
|
||||
$array = explode('.', $field);
|
||||
$field = array_pop($array);
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询的时候默认排除软删除数据
|
||||
* @access protected
|
||||
* @param Query $query
|
||||
* @return void
|
||||
*/
|
||||
protected function withNoTrashed($query)
|
||||
{
|
||||
$field = $this->getDeleteTimeField(true);
|
||||
|
||||
if ($field) {
|
||||
$condition = is_null($this->defaultSoftDelete) ? ['null', ''] : ['=', $this->defaultSoftDelete];
|
||||
$query->useSoftDelete($field, $condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
92
thinkphp/library/think/model/concern/TimeStamp.php
Normal file
92
thinkphp/library/think/model/concern/TimeStamp.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?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 DateTime;
|
||||
|
||||
/**
|
||||
* 自动时间戳
|
||||
*/
|
||||
trait TimeStamp
|
||||
{
|
||||
/**
|
||||
* 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型
|
||||
* @var bool|string
|
||||
*/
|
||||
protected $autoWriteTimestamp;
|
||||
|
||||
/**
|
||||
* 创建时间字段 false表示关闭
|
||||
* @var false|string
|
||||
*/
|
||||
protected $createTime = 'create_time';
|
||||
|
||||
/**
|
||||
* 更新时间字段 false表示关闭
|
||||
* @var false|string
|
||||
*/
|
||||
protected $updateTime = 'update_time';
|
||||
|
||||
/**
|
||||
* 时间字段显示格式
|
||||
* @var string
|
||||
*/
|
||||
protected $dateFormat;
|
||||
|
||||
/**
|
||||
* 时间日期字段格式化处理
|
||||
* @access protected
|
||||
* @param mixed $format 日期格式
|
||||
* @param mixed $time 时间日期表达式
|
||||
* @param bool $timestamp 是否进行时间戳转换
|
||||
* @return mixed
|
||||
*/
|
||||
protected function formatDateTime($format, $time = 'now', $timestamp = false)
|
||||
{
|
||||
if (empty($time)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (false === $format) {
|
||||
return $time;
|
||||
} elseif (false !== strpos($format, '\\')) {
|
||||
return new $format($time);
|
||||
}
|
||||
|
||||
if ($timestamp) {
|
||||
$dateTime = new DateTime();
|
||||
$dateTime->setTimestamp($time);
|
||||
} else {
|
||||
$dateTime = new DateTime($time);
|
||||
}
|
||||
|
||||
return $dateTime->format($format);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查时间字段写入
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function checkTimeStampWrite()
|
||||
{
|
||||
// 自动写入创建时间和更新时间
|
||||
if ($this->autoWriteTimestamp) {
|
||||
if ($this->createTime && !isset($this->data[$this->createTime])) {
|
||||
$this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime);
|
||||
}
|
||||
if ($this->updateTime && !isset($this->data[$this->updateTime])) {
|
||||
$this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
323
thinkphp/library/think/model/relation/BelongsTo.php
Normal file
323
thinkphp/library/think/model/relation/BelongsTo.php
Normal file
@@ -0,0 +1,323 @@
|
||||
<?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\relation;
|
||||
|
||||
use Closure;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
|
||||
class BelongsTo extends OneToOne
|
||||
{
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $localKey 关联主键
|
||||
* @param string $relation 关联名
|
||||
*/
|
||||
public function __construct(Model $parent, $model, $foreignKey, $localKey, $relation = null)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->foreignKey = $foreignKey;
|
||||
$this->localKey = $localKey;
|
||||
$this->joinType = 'INNER';
|
||||
$this->query = (new $model)->db();
|
||||
$this->relation = $relation;
|
||||
|
||||
if (get_class($parent) == $model) {
|
||||
$this->selfRelation = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据
|
||||
* @access public
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包查询条件
|
||||
* @return Model
|
||||
*/
|
||||
public function getRelation($subRelation = '', $closure = null)
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$relationModel = $this->query
|
||||
->removeWhereField($this->localKey)
|
||||
->where($this->localKey, $this->parent->$foreignKey)
|
||||
->relation($subRelation)
|
||||
->find();
|
||||
|
||||
if ($relationModel) {
|
||||
$relationModel->setParent(clone $this->parent);
|
||||
}
|
||||
|
||||
return $relationModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联统计子查询
|
||||
* @access public
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $aggregateAlias 聚合字段别名
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '')
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
|
||||
if ($return && is_string($return)) {
|
||||
$aggregateAlias = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->whereExp($this->localKey, '=' . $this->parent->getTable() . '.' . $this->foreignKey)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @return integer
|
||||
*/
|
||||
public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
|
||||
{
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
if (!isset($result->$foreignKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
|
||||
if ($return && is_string($return)) {
|
||||
$name = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->where($this->localKey, '=', $result->$foreignKey)
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @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')
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$model = basename(str_replace('\\', '/', get_class($this->parent)));
|
||||
$relation = basename(str_replace('\\', '/', $this->model));
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) {
|
||||
$query->table([$table => $relation])
|
||||
->field($relation . '.' . $localKey)
|
||||
->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @access public
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null)
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$model = basename(str_replace('\\', '/', get_class($this->parent)));
|
||||
$relation = basename(str_replace('\\', '/', $this->model));
|
||||
|
||||
if (is_array($where)) {
|
||||
$this->getQueryWhere($where, $relation);
|
||||
}
|
||||
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->field($fields)
|
||||
->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->where($where);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集)
|
||||
* @access protected
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$range = [];
|
||||
foreach ($resultSet as $result) {
|
||||
// 获取关联外键列表
|
||||
if (isset($result->$foreignKey)) {
|
||||
$range[] = $result->$foreignKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
$this->query->removeWhereField($localKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$localKey, 'in', $range],
|
||||
], $localKey, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联属性名
|
||||
$attr = Loader::parseName($relation);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
// 关联模型
|
||||
if (!isset($data[$result->$foreignKey])) {
|
||||
$relationModel = null;
|
||||
} else {
|
||||
$relationModel = $data[$result->$foreignKey];
|
||||
$relationModel->setParent(clone $result);
|
||||
$relationModel->isUpdate(true);
|
||||
}
|
||||
|
||||
if (!empty($this->bindAttr)) {
|
||||
// 绑定关联属性
|
||||
$this->bindAttr($relationModel, $result);
|
||||
} else {
|
||||
// 设置关联属性
|
||||
$result->setRelation($attr, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据)
|
||||
* @access protected
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
protected function eagerlyOne(&$result, $relation, $subRelation, $closure)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$this->query->removeWhereField($localKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$localKey, '=', $result->$foreignKey],
|
||||
], $localKey, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联模型
|
||||
if (!isset($data[$result->$foreignKey])) {
|
||||
$relationModel = null;
|
||||
} else {
|
||||
$relationModel = $data[$result->$foreignKey];
|
||||
$relationModel->setParent(clone $result);
|
||||
$relationModel->isUpdate(true);
|
||||
}
|
||||
|
||||
if (!empty($this->bindAttr)) {
|
||||
// 绑定关联属性
|
||||
$this->bindAttr($relationModel, $result);
|
||||
} else {
|
||||
// 设置关联属性
|
||||
$result->setRelation(Loader::parseName($relation), $relationModel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加关联数据
|
||||
* @access public
|
||||
* @param Model $model 关联模型对象
|
||||
* @return Model
|
||||
*/
|
||||
public function associate($model)
|
||||
{
|
||||
$this->parent->setAttr($this->foreignKey, $model->getKey());
|
||||
$this->parent->save();
|
||||
|
||||
return $this->parent->setRelation($this->relation, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销关联数据
|
||||
* @access public
|
||||
* @return Model
|
||||
*/
|
||||
public function dissociate()
|
||||
{
|
||||
$this->parent->setAttr($this->foreignKey, null);
|
||||
$this->parent->save();
|
||||
|
||||
return $this->parent->setRelation($this->relation, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次)
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery()
|
||||
{
|
||||
if (empty($this->baseQuery)) {
|
||||
if (isset($this->parent->{$this->foreignKey})) {
|
||||
// 关联查询带入关联条件
|
||||
$this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey});
|
||||
}
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
711
thinkphp/library/think/model/relation/BelongsToMany.php
Normal file
711
thinkphp/library/think/model/relation/BelongsToMany.php
Normal file
@@ -0,0 +1,711 @@
|
||||
<?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\relation;
|
||||
|
||||
use Closure;
|
||||
use think\Collection;
|
||||
use think\db\Query;
|
||||
use think\Exception;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
use think\model\Pivot;
|
||||
use think\model\Relation;
|
||||
|
||||
class BelongsToMany extends Relation
|
||||
{
|
||||
// 中间表表名
|
||||
protected $middle;
|
||||
// 中间表模型名称
|
||||
protected $pivotName;
|
||||
// 中间表数据名称
|
||||
protected $pivotDataName = 'pivot';
|
||||
// 中间表模型对象
|
||||
protected $pivot;
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $table 中间表名
|
||||
* @param string $foreignKey 关联模型外键
|
||||
* @param string $localKey 当前模型关联键
|
||||
*/
|
||||
public function __construct(Model $parent, $model, $table, $foreignKey, $localKey)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->foreignKey = $foreignKey;
|
||||
$this->localKey = $localKey;
|
||||
|
||||
if (false !== strpos($table, '\\')) {
|
||||
$this->pivotName = $table;
|
||||
$this->middle = basename(str_replace('\\', '/', $table));
|
||||
} else {
|
||||
$this->middle = $table;
|
||||
}
|
||||
|
||||
$this->query = (new $model)->db();
|
||||
$this->pivot = $this->newPivot();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置中间表模型
|
||||
* @access public
|
||||
* @param $pivot
|
||||
* @return $this
|
||||
*/
|
||||
public function pivot($pivot)
|
||||
{
|
||||
$this->pivotName = $pivot;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置中间表数据名称
|
||||
* @access public
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function pivotDataName($name)
|
||||
{
|
||||
$this->pivotDataName = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取中间表更新条件
|
||||
* @param $data
|
||||
* @return array
|
||||
*/
|
||||
protected function getUpdateWhere($data)
|
||||
{
|
||||
return [
|
||||
$this->localKey => $data[$this->localKey],
|
||||
$this->foreignKey => $data[$this->foreignKey],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化中间表模型
|
||||
* @access public
|
||||
* @param array $data
|
||||
* @param bool $isUpdate
|
||||
* @return Pivot
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function newPivot($data = [], $isUpdate = false)
|
||||
{
|
||||
$class = $this->pivotName ?: '\\think\\model\\Pivot';
|
||||
$pivot = new $class($data, $this->parent, $this->middle);
|
||||
|
||||
if ($pivot instanceof Pivot) {
|
||||
return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot;
|
||||
}
|
||||
|
||||
throw new Exception('pivot model must extends: \think\model\Pivot');
|
||||
}
|
||||
|
||||
/**
|
||||
* 合成中间表模型
|
||||
* @access protected
|
||||
* @param array|Collection|Paginator $models
|
||||
*/
|
||||
protected function hydratePivot($models)
|
||||
{
|
||||
foreach ($models as $model) {
|
||||
$pivot = [];
|
||||
|
||||
foreach ($model->getData() as $key => $val) {
|
||||
if (strpos($key, '__')) {
|
||||
list($name, $attr) = explode('__', $key, 2);
|
||||
|
||||
if ('pivot' == $name) {
|
||||
$pivot[$attr] = $val;
|
||||
unset($model->$key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$model->setRelation($this->pivotDataName, $this->newPivot($pivot, true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联查询Query对象
|
||||
* @access protected
|
||||
* @return Query
|
||||
*/
|
||||
protected function buildQuery()
|
||||
{
|
||||
$foreignKey = $this->foreignKey;
|
||||
$localKey = $this->localKey;
|
||||
|
||||
// 关联查询
|
||||
$pk = $this->parent->getPk();
|
||||
|
||||
$condition[] = ['pivot.' . $localKey, '=', $this->parent->$pk];
|
||||
|
||||
return $this->belongsToManyQuery($foreignKey, $localKey, $condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据
|
||||
* @access public
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包查询条件
|
||||
* @return Collection
|
||||
*/
|
||||
public function getRelation($subRelation = '', $closure = null)
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$result = $this->buildQuery()->relation($subRelation)->select();
|
||||
$this->hydratePivot($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载select方法
|
||||
* @access public
|
||||
* @param mixed $data
|
||||
* @return Collection
|
||||
*/
|
||||
public function select($data = null)
|
||||
{
|
||||
$result = $this->buildQuery()->select($data);
|
||||
$this->hydratePivot($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载paginate方法
|
||||
* @access public
|
||||
* @param null $listRows
|
||||
* @param bool $simple
|
||||
* @param array $config
|
||||
* @return Paginator
|
||||
*/
|
||||
public function paginate($listRows = null, $simple = false, $config = [])
|
||||
{
|
||||
$result = $this->buildQuery()->paginate($listRows, $simple, $config);
|
||||
$this->hydratePivot($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载find方法
|
||||
* @access public
|
||||
* @param mixed $data
|
||||
* @return Model
|
||||
*/
|
||||
public function find($data = null)
|
||||
{
|
||||
$result = $this->buildQuery()->find($data);
|
||||
if ($result) {
|
||||
$this->hydratePivot([$result]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找多条记录 如果不存在则抛出异常
|
||||
* @access public
|
||||
* @param array|string|Query|\Closure $data
|
||||
* @return Collection
|
||||
*/
|
||||
public function selectOrFail($data = null)
|
||||
{
|
||||
return $this->failException(true)->select($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找单条记录 如果不存在则抛出异常
|
||||
* @access public
|
||||
* @param array|string|Query|\Closure $data
|
||||
* @return Model
|
||||
*/
|
||||
public function findOrFail($data = null)
|
||||
{
|
||||
return $this->failException(true)->find($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @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 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @return Query
|
||||
* @throws Exception
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null)
|
||||
{
|
||||
throw new Exception('relation not support: hasWhere');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置中间表的查询条件
|
||||
* @access public
|
||||
* @param string $field
|
||||
* @param string $op
|
||||
* @param mixed $condition
|
||||
* @return $this
|
||||
*/
|
||||
public function wherePivot($field, $op = null, $condition = null)
|
||||
{
|
||||
$this->query->where('pivot.' . $field, $op, $condition);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集)
|
||||
* @access public
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
|
||||
{
|
||||
$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, $closure);
|
||||
|
||||
// 关联属性名
|
||||
$attr = Loader::parseName($relation);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
if (!isset($data[$result->$pk])) {
|
||||
$data[$result->$pk] = [];
|
||||
}
|
||||
|
||||
$result->setRelation($attr, $this->resultSetBuild($data[$result->$pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(单个数据)
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(&$result, $relation, $subRelation, $closure)
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (isset($result->$pk)) {
|
||||
$pk = $result->$pk;
|
||||
// 查询管理数据
|
||||
$data = $this->eagerlyManyToMany([
|
||||
['pivot.' . $this->localKey, '=', $pk],
|
||||
], $relation, $subRelation, $closure);
|
||||
|
||||
// 关联数据封装
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
$result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @return integer
|
||||
*/
|
||||
public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (!isset($result->$pk)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$pk = $result->$pk;
|
||||
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
|
||||
if ($return && is_string($return)) {
|
||||
$name = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
|
||||
['pivot.' . $this->localKey, '=', $pk],
|
||||
])->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联统计子查询
|
||||
* @access public
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $aggregateAlias 聚合字段别名
|
||||
* @return array
|
||||
*/
|
||||
public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '')
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
|
||||
if ($return && is_string($return)) {
|
||||
$aggregateAlias = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
|
||||
[
|
||||
'pivot.' . $this->localKey, 'exp', $this->query->raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()),
|
||||
],
|
||||
])->fetchSql()->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多对多 关联模型预查询
|
||||
* @access protected
|
||||
* @param array $where 关联预查询条件
|
||||
* @param string $relation 关联名
|
||||
* @param string $subRelation 子关联
|
||||
* @param \Closure $closure 闭包
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyManyToMany($where, $relation, $subRelation = '', $closure = null)
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$list = $this->belongsToManyQuery($this->foreignKey, $this->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->setRelation($this->pivotDataName, $this->newPivot($pivot, true));
|
||||
|
||||
$data[$pivot[$this->localKey]][] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* BELONGS TO MANY 关联查询
|
||||
* @access protected
|
||||
* @param string $foreignKey 关联模型关联键
|
||||
* @param string $localKey 当前模型关联键
|
||||
* @param array $condition 关联查询条件
|
||||
* @return Query
|
||||
*/
|
||||
protected function belongsToManyQuery($foreignKey, $localKey, $condition = [])
|
||||
{
|
||||
// 关联查询封装
|
||||
$tableName = $this->query->getTable();
|
||||
$table = $this->pivot->getTable();
|
||||
$fields = $this->getQueryFields($tableName);
|
||||
|
||||
$query = $this->query
|
||||
->field($fields)
|
||||
->field(true, false, $table, 'pivot', 'pivot__');
|
||||
|
||||
if (empty($this->baseQuery)) {
|
||||
$relationFk = $this->query->getPk();
|
||||
$query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk)
|
||||
->where($condition);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增)当前关联数据对象
|
||||
* @access public
|
||||
* @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键
|
||||
* @param array $pivot 中间表额外数据
|
||||
* @return array|Pivot
|
||||
*/
|
||||
public function save($data, array $pivot = [])
|
||||
{
|
||||
// 保存关联表/中间表数据
|
||||
return $this->attach($data, $pivot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存当前关联数据对象
|
||||
* @access public
|
||||
* @param array $dataSet 数据集
|
||||
* @param array $pivot 中间表额外数据
|
||||
* @param bool $samePivot 额外数据是否相同
|
||||
* @return array|false
|
||||
*/
|
||||
public function saveAll(array $dataSet, array $pivot = [], $samePivot = false)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($dataSet as $key => $data) {
|
||||
if (!$samePivot) {
|
||||
$pivotData = isset($pivot[$key]) ? $pivot[$key] : [];
|
||||
} else {
|
||||
$pivotData = $pivot;
|
||||
}
|
||||
|
||||
$result[] = $this->attach($data, $pivotData);
|
||||
}
|
||||
|
||||
return empty($result) ? false : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 附加关联的一个中间表数据
|
||||
* @access public
|
||||
* @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键
|
||||
* @param array $pivot 中间表额外数据
|
||||
* @return array|Pivot
|
||||
* @throws Exception
|
||||
*/
|
||||
public function attach($data, $pivot = [])
|
||||
{
|
||||
if (is_array($data)) {
|
||||
if (key($data) === 0) {
|
||||
$id = $data;
|
||||
} else {
|
||||
// 保存关联表数据
|
||||
$model = new $this->model;
|
||||
$id = $model->insertGetId($data);
|
||||
}
|
||||
} 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;
|
||||
$ids = (array) $id;
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$pivot[$this->foreignKey] = $id;
|
||||
$this->pivot->replace()
|
||||
->exists(false)
|
||||
->data([])
|
||||
->save($pivot);
|
||||
$result[] = $this->newPivot($pivot, true);
|
||||
}
|
||||
|
||||
if (count($result) == 1) {
|
||||
// 返回中间表模型对象
|
||||
$result = $result[0];
|
||||
}
|
||||
|
||||
return $result;
|
||||
} else {
|
||||
throw new Exception('miss relation data');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在关联数据
|
||||
* @access public
|
||||
* @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键
|
||||
* @return Pivot
|
||||
* @throws Exception
|
||||
*/
|
||||
public function attached($data)
|
||||
{
|
||||
if ($data instanceof Model) {
|
||||
$id = $data->getKey();
|
||||
} else {
|
||||
$id = $data;
|
||||
}
|
||||
|
||||
$pivot = $this->pivot
|
||||
->where($this->localKey, $this->parent->getKey())
|
||||
->where($this->foreignKey, $id)
|
||||
->find();
|
||||
|
||||
return $pivot ?: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除关联的一个中间表数据
|
||||
* @access public
|
||||
* @param integer|array $data 数据 可以使用关联对象的主键
|
||||
* @param bool $relationDel 是否同时删除关联表数据
|
||||
* @return integer
|
||||
*/
|
||||
public function detach($data = null, $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];
|
||||
}
|
||||
|
||||
$result = $this->pivot->where($pivot)->delete();
|
||||
|
||||
// 删除关联表数据
|
||||
if (isset($id) && $relationDel) {
|
||||
$model = $this->model;
|
||||
$model::destroy($id);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据同步
|
||||
* @access public
|
||||
* @param array $ids
|
||||
* @param bool $detaching
|
||||
* @return array
|
||||
*/
|
||||
public function sync($ids, $detaching = true)
|
||||
{
|
||||
$changes = [
|
||||
'attached' => [],
|
||||
'detached' => [],
|
||||
'updated' => [],
|
||||
];
|
||||
|
||||
$pk = $this->parent->getPk();
|
||||
|
||||
$current = $this->pivot
|
||||
->where($this->localKey, $this->parent->$pk)
|
||||
->column($this->foreignKey);
|
||||
|
||||
$records = [];
|
||||
|
||||
foreach ($ids as $key => $value) {
|
||||
if (!is_array($value)) {
|
||||
$records[$value] = [];
|
||||
} else {
|
||||
$records[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$detach = array_diff($current, array_keys($records));
|
||||
|
||||
if ($detaching && count($detach) > 0) {
|
||||
$this->detach($detach);
|
||||
$changes['detached'] = $detach;
|
||||
}
|
||||
|
||||
foreach ($records as $id => $attributes) {
|
||||
if (!in_array($id, $current)) {
|
||||
$this->attach($id, $attributes);
|
||||
$changes['attached'][] = $id;
|
||||
} elseif (count($attributes) > 0 && $this->attach($id, $attributes)) {
|
||||
$changes['updated'][] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次)
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery()
|
||||
{
|
||||
if (empty($this->baseQuery) && $this->parent->getData()) {
|
||||
$pk = $this->parent->getPk();
|
||||
$table = $this->pivot->getTable();
|
||||
|
||||
$this->query
|
||||
->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())
|
||||
->where('pivot.' . $this->localKey, $this->parent->$pk);
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
360
thinkphp/library/think/model/relation/HasMany.php
Normal file
360
thinkphp/library/think/model/relation/HasMany.php
Normal file
@@ -0,0 +1,360 @@
|
||||
<?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\relation;
|
||||
|
||||
use Closure;
|
||||
use think\db\Query;
|
||||
use think\Loader;
|
||||
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 当前模型主键
|
||||
*/
|
||||
public function __construct(Model $parent, $model, $foreignKey, $localKey)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->foreignKey = $foreignKey;
|
||||
$this->localKey = $localKey;
|
||||
$this->query = (new $model)->db();
|
||||
|
||||
if (get_class($parent) == $model) {
|
||||
$this->selfRelation = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据
|
||||
* @access public
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包查询条件
|
||||
* @return \think\Collection
|
||||
*/
|
||||
public function getRelation($subRelation = '', $closure = null)
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$list = $this->query
|
||||
->where($this->foreignKey, $this->parent->{$this->localKey})
|
||||
->relation($subRelation)
|
||||
->select();
|
||||
|
||||
$parent = clone $this->parent;
|
||||
|
||||
foreach ($list as &$model) {
|
||||
$model->setParent($parent);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询
|
||||
* @access public
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$range = [];
|
||||
|
||||
foreach ($resultSet as $result) {
|
||||
// 获取关联外键列表
|
||||
if (isset($result->$localKey)) {
|
||||
$range[] = $result->$localKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
$where = [
|
||||
[$this->foreignKey, 'in', $range],
|
||||
];
|
||||
$data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联属性名
|
||||
$attr = Loader::parseName($relation);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
$pk = $result->$localKey;
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
foreach ($data[$pk] as &$relationModel) {
|
||||
$relationModel->setParent(clone $result);
|
||||
}
|
||||
|
||||
$result->setRelation($attr, $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(&$result, $relation, $subRelation, $closure)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if (isset($result->$localKey)) {
|
||||
$pk = $result->$localKey;
|
||||
$where = [
|
||||
[$this->foreignKey, '=', $pk],
|
||||
];
|
||||
$data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联数据封装
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
foreach ($data[$pk] as &$relationModel) {
|
||||
$relationModel->setParent(clone $result);
|
||||
}
|
||||
|
||||
$result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @return integer
|
||||
*/
|
||||
public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if (!isset($result->$localKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
if ($return && is_string($return)) {
|
||||
$name = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->where($this->foreignKey, '=', $result->$localKey)
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联统计子查询
|
||||
* @access public
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $aggregateAlias 聚合字段别名
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '')
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
|
||||
if ($return && is_string($return)) {
|
||||
$aggregateAlias = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->query->alias($aggregate . '_table')
|
||||
->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一对多 关联模型预查询
|
||||
* @access public
|
||||
* @param array $where 关联预查询条件
|
||||
* @param string $relation 关联名
|
||||
* @param string $subRelation 子关联
|
||||
* @param \Closure $closure
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = null)
|
||||
{
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$this->query->removeWhereField($this->foreignKey);
|
||||
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$list = $this->query->where($where)->with($subRelation)->select();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
|
||||
foreach ($list as $set) {
|
||||
$data[$set->$foreignKey][] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增)当前关联数据对象
|
||||
* @access public
|
||||
* @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键
|
||||
* @param boolean $replace 是否自动识别更新和写入
|
||||
* @return Model|false
|
||||
*/
|
||||
public function save($data, $replace = true)
|
||||
{
|
||||
$model = $this->make();
|
||||
|
||||
return $model->replace($replace)->save($data) ? $model : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联对象实例
|
||||
* @param array $data
|
||||
* @return Model
|
||||
*/
|
||||
public function make($data = [])
|
||||
{
|
||||
if ($data instanceof Model) {
|
||||
$data = $data->getData();
|
||||
}
|
||||
|
||||
// 保存关联表数据
|
||||
$data[$this->foreignKey] = $this->parent->{$this->localKey};
|
||||
|
||||
return new $this->model($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存当前关联数据对象
|
||||
* @access public
|
||||
* @param array|\think\Collection $dataSet 数据集
|
||||
* @param boolean $replace 是否自动识别更新和写入
|
||||
* @return array|false
|
||||
*/
|
||||
public function saveAll($dataSet, $replace = true)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($dataSet as $key => $data) {
|
||||
$result[] = $this->save($data, $replace);
|
||||
}
|
||||
|
||||
return empty($result) ? false : $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')
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$model = basename(str_replace('\\', '/', get_class($this->parent)));
|
||||
$relation = basename(str_replace('\\', '/', $this->model));
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->field($model . '.*')
|
||||
->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->group($relation . '.' . $this->foreignKey)
|
||||
->having('count(' . $id . ')' . $operator . $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @access public
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null)
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$model = basename(str_replace('\\', '/', get_class($this->parent)));
|
||||
$relation = basename(str_replace('\\', '/', $this->model));
|
||||
|
||||
if (is_array($where)) {
|
||||
$this->getQueryWhere($where, $relation);
|
||||
}
|
||||
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->group($model . '.' . $this->localKey)
|
||||
->field($fields)
|
||||
->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->where($where);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次)
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery()
|
||||
{
|
||||
if (empty($this->baseQuery)) {
|
||||
if (isset($this->parent->{$this->localKey})) {
|
||||
// 关联查询带入关联条件
|
||||
$this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
|
||||
}
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
363
thinkphp/library/think/model/relation/HasManyThrough.php
Normal file
363
thinkphp/library/think/model/relation/HasManyThrough.php
Normal file
@@ -0,0 +1,363 @@
|
||||
<?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\relation;
|
||||
|
||||
use Closure;
|
||||
use think\db\Query;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
use think\model\Relation;
|
||||
|
||||
class HasManyThrough extends Relation
|
||||
{
|
||||
// 中间关联表外键
|
||||
protected $throughKey;
|
||||
// 中间表模型
|
||||
protected $through;
|
||||
|
||||
/**
|
||||
* 中间主键
|
||||
* @var string
|
||||
*/
|
||||
protected $throughPk;
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @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)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->through = (new $through)->db();
|
||||
$this->foreignKey = $foreignKey;
|
||||
$this->throughKey = $throughKey;
|
||||
$this->throughPk = $this->through->getPk();
|
||||
$this->localKey = $localKey;
|
||||
$this->query = (new $model)->db();
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据
|
||||
* @access public
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包查询条件
|
||||
* @return \think\Collection
|
||||
*/
|
||||
public function getRelation($subRelation = '', $closure = null)
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$this->baseQuery();
|
||||
|
||||
return $this->query->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')
|
||||
{
|
||||
$model = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent))));
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$relation = (new $this->model)->db();
|
||||
$relationTable = $relation->getTable();
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
if ('*' != $id) {
|
||||
$id = $relationTable . '.' . $relation->getPk();
|
||||
}
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->field($model . '.*')
|
||||
->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey)
|
||||
->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relationTable) {
|
||||
$query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->group($relationTable . '.' . $this->throughKey)
|
||||
->having('count(' . $id . ')' . $operator . $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @access public
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null)
|
||||
{
|
||||
$model = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent))));
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$modelTable = (new $this->model)->db()->getTable();
|
||||
|
||||
if (is_array($where)) {
|
||||
$this->getQueryWhere($where, $modelTable);
|
||||
}
|
||||
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey)
|
||||
->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk)
|
||||
->when($softDelete, function ($query) use ($softDelete, $modelTable) {
|
||||
$query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->group($modelTable . '.' . $this->throughKey)
|
||||
->where($where)
|
||||
->field($fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集)
|
||||
* @access protected
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param mixed $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array &$resultSet, $relation, $subRelation = '', $closure = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$range = [];
|
||||
foreach ($resultSet as $result) {
|
||||
// 获取关联外键列表
|
||||
if (isset($result->$localKey)) {
|
||||
$range[] = $result->$localKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
$this->query->removeWhereField($foreignKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$this->foreignKey, 'in', $range],
|
||||
], $foreignKey, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联属性名
|
||||
$attr = Loader::parseName($relation);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
$pk = $result->$localKey;
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
foreach ($data[$pk] as &$relationModel) {
|
||||
$relationModel->setParent(clone $result);
|
||||
}
|
||||
|
||||
// 设置关联属性
|
||||
$result->setRelation($attr, $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据)
|
||||
* @access protected
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param mixed $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult($result, $relation, $subRelation = '', $closure = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
$pk = $result->$localKey;
|
||||
|
||||
$this->query->removeWhereField($foreignKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$foreignKey, '=', $pk],
|
||||
], $foreignKey, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联数据封装
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
foreach ($data[$pk] as &$relationModel) {
|
||||
$relationModel->setParent(clone $result);
|
||||
}
|
||||
|
||||
$result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联模型预查询
|
||||
* @access public
|
||||
* @param array $where 关联预查询条件
|
||||
* @param string $key 关联键名
|
||||
* @param string $relation 关联名
|
||||
* @param mixed $subRelation 子关联
|
||||
* @param Closure $closure
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyWhere(array $where, $key, $relation, $subRelation = '', $closure = null)
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
$throughList = $this->through->where($where)->select();
|
||||
$keys = $throughList->column($this->throughPk, $this->throughPk);
|
||||
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$list = $this->query->where($this->throughKey, 'in', $keys)->select();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
$keys = $throughList->column($this->foreignKey, $this->throughPk);
|
||||
|
||||
foreach ($list as $set) {
|
||||
$data[$keys[$set->{$this->throughKey}]][] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @return integer
|
||||
*/
|
||||
public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if (!isset($result->$localKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
if ($return && is_string($return)) {
|
||||
$name = $return;
|
||||
}
|
||||
}
|
||||
|
||||
$alias = Loader::parseName(basename(str_replace('\\', '/', $this->model)));
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$modelTable = $this->parent->getTable();
|
||||
|
||||
if (false === strpos($field, '.')) {
|
||||
$field = $alias . '.' . $field;
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->alias($alias)
|
||||
->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
|
||||
->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
|
||||
->where($throughTable . '.' . $this->foreignKey, $result->$localKey)
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联统计子查询
|
||||
* @access public
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery($closure = null, $aggregate = 'count', $field = '*', &$name = null)
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
if ($return && is_string($return)) {
|
||||
$name = $return;
|
||||
}
|
||||
}
|
||||
|
||||
$alias = Loader::parseName(basename(str_replace('\\', '/', $this->model)));
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$modelTable = $this->parent->getTable();
|
||||
|
||||
if (false === strpos($field, '.')) {
|
||||
$field = $alias . '.' . $field;
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->alias($alias)
|
||||
->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
|
||||
->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
|
||||
->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次)
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery()
|
||||
{
|
||||
if (empty($this->baseQuery) && $this->parent->getData()) {
|
||||
$alias = Loader::parseName(basename(str_replace('\\', '/', $this->model)));
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$modelTable = $this->parent->getTable();
|
||||
$fields = $this->getQueryFields($alias);
|
||||
|
||||
$this->query
|
||||
->field($fields)
|
||||
->alias($alias)
|
||||
->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
|
||||
->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
|
||||
->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey});
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
294
thinkphp/library/think/model/relation/HasOne.php
Normal file
294
thinkphp/library/think/model/relation/HasOne.php
Normal file
@@ -0,0 +1,294 @@
|
||||
<?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\relation;
|
||||
|
||||
use Closure;
|
||||
use think\db\Query;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
|
||||
class HasOne extends OneToOne
|
||||
{
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $localKey 当前模型主键
|
||||
*/
|
||||
public function __construct(Model $parent, $model, $foreignKey, $localKey)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->foreignKey = $foreignKey;
|
||||
$this->localKey = $localKey;
|
||||
$this->joinType = 'INNER';
|
||||
$this->query = (new $model)->db();
|
||||
|
||||
if (get_class($parent) == $model) {
|
||||
$this->selfRelation = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据
|
||||
* @access public
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包查询条件
|
||||
* @return Model
|
||||
*/
|
||||
public function getRelation($subRelation = '', $closure = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
// 判断关联类型执行查询
|
||||
$relationModel = $this->query
|
||||
->removeWhereField($this->foreignKey)
|
||||
->where($this->foreignKey, $this->parent->$localKey)
|
||||
->relation($subRelation)
|
||||
->find();
|
||||
|
||||
if ($relationModel) {
|
||||
$relationModel->setParent(clone $this->parent);
|
||||
}
|
||||
|
||||
return $relationModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联统计子查询
|
||||
* @access public
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $aggregateAlias 聚合字段别名
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '')
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
|
||||
if ($return && is_string($return)) {
|
||||
$aggregateAlias = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @return integer
|
||||
*/
|
||||
public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if (!isset($result->$localKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
if ($return && is_string($return)) {
|
||||
$name = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->where($this->foreignKey, '=', $result->$localKey)
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @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')
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$model = basename(str_replace('\\', '/', get_class($this->parent)));
|
||||
$relation = basename(str_replace('\\', '/', $this->model));
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) {
|
||||
$query->table([$table => $relation])
|
||||
->field($relation . '.' . $foreignKey)
|
||||
->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @access public
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null)
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$model = basename(str_replace('\\', '/', get_class($this->parent)));
|
||||
$relation = basename(str_replace('\\', '/', $this->model));
|
||||
|
||||
if (is_array($where)) {
|
||||
$this->getQueryWhere($where, $relation);
|
||||
}
|
||||
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->field($fields)
|
||||
->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->where($where);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集)
|
||||
* @access protected
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$range = [];
|
||||
foreach ($resultSet as $result) {
|
||||
// 获取关联外键列表
|
||||
if (isset($result->$localKey)) {
|
||||
$range[] = $result->$localKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
$this->query->removeWhereField($foreignKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$foreignKey, 'in', $range],
|
||||
], $foreignKey, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联属性名
|
||||
$attr = Loader::parseName($relation);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
// 关联模型
|
||||
if (!isset($data[$result->$localKey])) {
|
||||
$relationModel = null;
|
||||
} else {
|
||||
$relationModel = $data[$result->$localKey];
|
||||
$relationModel->setParent(clone $result);
|
||||
$relationModel->isUpdate(true);
|
||||
}
|
||||
|
||||
if (!empty($this->bindAttr)) {
|
||||
// 绑定关联属性
|
||||
$this->bindAttr($relationModel, $result, $this->bindAttr);
|
||||
} else {
|
||||
// 设置关联属性
|
||||
$result->setRelation($attr, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据)
|
||||
* @access protected
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
protected function eagerlyOne(&$result, $relation, $subRelation, $closure)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$this->query->removeWhereField($foreignKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$foreignKey, '=', $result->$localKey],
|
||||
], $foreignKey, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联模型
|
||||
if (!isset($data[$result->$localKey])) {
|
||||
$relationModel = null;
|
||||
} else {
|
||||
$relationModel = $data[$result->$localKey];
|
||||
$relationModel->setParent(clone $result);
|
||||
$relationModel->isUpdate(true);
|
||||
}
|
||||
|
||||
if (!empty($this->bindAttr)) {
|
||||
// 绑定关联属性
|
||||
$this->bindAttr($relationModel, $result, $this->bindAttr);
|
||||
} else {
|
||||
$result->setRelation(Loader::parseName($relation), $relationModel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次)
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery()
|
||||
{
|
||||
if (empty($this->baseQuery)) {
|
||||
if (isset($this->parent->{$this->localKey})) {
|
||||
// 关联查询带入关联条件
|
||||
$this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
|
||||
}
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
342
thinkphp/library/think/model/relation/MorphMany.php
Normal file
342
thinkphp/library/think/model/relation/MorphMany.php
Normal file
@@ -0,0 +1,342 @@
|
||||
<?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\relation;
|
||||
|
||||
use Closure;
|
||||
use think\db\Query;
|
||||
use think\Exception;
|
||||
use think\Loader;
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据
|
||||
* @access public
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包查询条件
|
||||
* @return \think\Collection
|
||||
*/
|
||||
public function getRelation($subRelation = '', $closure = null)
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$this->baseQuery();
|
||||
|
||||
$list = $this->query->relation($subRelation)->select();
|
||||
$parent = clone $this->parent;
|
||||
|
||||
foreach ($list as &$model) {
|
||||
$model->setParent($parent);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @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 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null)
|
||||
{
|
||||
throw new Exception('relation not support: hasWhere');
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询
|
||||
* @access public
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
|
||||
{
|
||||
$morphType = $this->morphType;
|
||||
$morphKey = $this->morphKey;
|
||||
$type = $this->type;
|
||||
$range = [];
|
||||
|
||||
foreach ($resultSet as $result) {
|
||||
$pk = $result->getPk();
|
||||
// 获取关联外键列表
|
||||
if (isset($result->$pk)) {
|
||||
$range[] = $result->$pk;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
$where = [
|
||||
[$morphKey, 'in', $range],
|
||||
[$morphType, '=', $type],
|
||||
];
|
||||
$data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联属性名
|
||||
$attr = Loader::parseName($relation);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
if (!isset($data[$result->$pk])) {
|
||||
$data[$result->$pk] = [];
|
||||
}
|
||||
|
||||
foreach ($data[$result->$pk] as &$relationModel) {
|
||||
$relationModel->setParent(clone $result);
|
||||
$relationModel->isUpdate(true);
|
||||
}
|
||||
|
||||
$result->setRelation($attr, $this->resultSetBuild($data[$result->$pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(&$result, $relation, $subRelation, $closure)
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (isset($result->$pk)) {
|
||||
$key = $result->$pk;
|
||||
$where = [
|
||||
[$this->morphKey, '=', $key],
|
||||
[$this->morphType, '=', $this->type],
|
||||
];
|
||||
$data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure);
|
||||
|
||||
if (!isset($data[$key])) {
|
||||
$data[$key] = [];
|
||||
}
|
||||
|
||||
foreach ($data[$key] as &$relationModel) {
|
||||
$relationModel->setParent(clone $result);
|
||||
$relationModel->isUpdate(true);
|
||||
}
|
||||
|
||||
$result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$key]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @return integer
|
||||
*/
|
||||
public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (!isset($result->$pk)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
|
||||
if ($return && is_string($return)) {
|
||||
$name = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->where([
|
||||
[$this->morphKey, '=', $result->$pk],
|
||||
[$this->morphType, '=', $this->type],
|
||||
])
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联统计子查询
|
||||
* @access public
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $aggregateAlias 聚合字段别名
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '')
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$return = $closure($this->query);
|
||||
|
||||
if ($return && is_string($return)) {
|
||||
$aggregateAlias = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk())
|
||||
->where($this->morphType, '=', $this->type)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多态一对多 关联模型预查询
|
||||
* @access protected
|
||||
* @param array $where 关联预查询条件
|
||||
* @param string $relation 关联名
|
||||
* @param string $subRelation 子关联
|
||||
* @param \Closure $closure 闭包
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = null)
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
$this->query->removeOption('where');
|
||||
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$list = $this->query->where($where)->with($subRelation)->select();
|
||||
$morphKey = $this->morphKey;
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
foreach ($list as $set) {
|
||||
$data[$set->$morphKey][] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增)当前关联数据对象
|
||||
* @access public
|
||||
* @param mixed $data 数据
|
||||
* @return Model|false
|
||||
*/
|
||||
public function save($data)
|
||||
{
|
||||
$model = $this->make();
|
||||
|
||||
return $model->save($data) ? $model : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联对象实例
|
||||
* @param array $data
|
||||
* @return Model
|
||||
*/
|
||||
public function make($data = [])
|
||||
{
|
||||
if ($data instanceof Model) {
|
||||
$data = $data->getData();
|
||||
}
|
||||
|
||||
// 保存关联表数据
|
||||
$pk = $this->parent->getPk();
|
||||
|
||||
$data[$this->morphKey] = $this->parent->$pk;
|
||||
$data[$this->morphType] = $this->type;
|
||||
|
||||
return new $this->model($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存当前关联数据对象
|
||||
* @access public
|
||||
* @param array $dataSet 数据集
|
||||
* @return array|false
|
||||
*/
|
||||
public function saveAll(array $dataSet)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($dataSet as $key => $data) {
|
||||
$result[] = $this->save($data);
|
||||
}
|
||||
|
||||
return empty($result) ? false : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次)
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery()
|
||||
{
|
||||
if (empty($this->baseQuery) && $this->parent->getData()) {
|
||||
$pk = $this->parent->getPk();
|
||||
|
||||
$this->query->where([
|
||||
[$this->morphKey, '=', $this->parent->$pk],
|
||||
[$this->morphType, '=', $this->type],
|
||||
]);
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
257
thinkphp/library/think/model/relation/MorphOne.php
Normal file
257
thinkphp/library/think/model/relation/MorphOne.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?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\relation;
|
||||
|
||||
use Closure;
|
||||
use think\db\Query;
|
||||
use think\Exception;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
use think\model\Relation;
|
||||
|
||||
class MorphOne extends Relation
|
||||
{
|
||||
// 多态字段
|
||||
protected $morphKey;
|
||||
protected $morphType;
|
||||
// 多态类型
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @access public
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $morphKey 关联外键
|
||||
* @param string $morphType 多态字段名
|
||||
* @param string $type 多态类型
|
||||
*/
|
||||
public function __construct(Model $parent, $model, $morphKey, $morphType, $type)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->type = $type;
|
||||
$this->morphKey = $morphKey;
|
||||
$this->morphType = $morphType;
|
||||
$this->query = (new $model)->db();
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据
|
||||
* @access public
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包查询条件
|
||||
* @return Model
|
||||
*/
|
||||
public function getRelation($subRelation = '', $closure = null)
|
||||
{
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$this->baseQuery();
|
||||
|
||||
$relationModel = $this->query->relation($subRelation)->find();
|
||||
|
||||
if ($relationModel) {
|
||||
$relationModel->setParent(clone $this->parent);
|
||||
}
|
||||
|
||||
return $relationModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @access public
|
||||
* @param string $operator 比较操作符
|
||||
* @param integer $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @return Query
|
||||
*/
|
||||
public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @access public
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null)
|
||||
{
|
||||
throw new Exception('relation not support: hasWhere');
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询
|
||||
* @access public
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
|
||||
{
|
||||
$morphType = $this->morphType;
|
||||
$morphKey = $this->morphKey;
|
||||
$type = $this->type;
|
||||
$range = [];
|
||||
|
||||
foreach ($resultSet as $result) {
|
||||
$pk = $result->getPk();
|
||||
// 获取关联外键列表
|
||||
if (isset($result->$pk)) {
|
||||
$range[] = $result->$pk;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
$data = $this->eagerlyMorphToOne([
|
||||
[$morphKey, 'in', $range],
|
||||
[$morphType, '=', $type],
|
||||
], $relation, $subRelation, $closure);
|
||||
|
||||
// 关联属性名
|
||||
$attr = Loader::parseName($relation);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
if (!isset($data[$result->$pk])) {
|
||||
$relationModel = null;
|
||||
} else {
|
||||
$relationModel = $data[$result->$pk];
|
||||
$relationModel->setParent(clone $result);
|
||||
$relationModel->isUpdate(true);
|
||||
}
|
||||
|
||||
$result->setRelation($attr, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(&$result, $relation, $subRelation, $closure)
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (isset($result->$pk)) {
|
||||
$pk = $result->$pk;
|
||||
$data = $this->eagerlyMorphToOne([
|
||||
[$this->morphKey, '=', $pk],
|
||||
[$this->morphType, '=', $this->type],
|
||||
], $relation, $subRelation, $closure);
|
||||
|
||||
if (isset($data[$pk])) {
|
||||
$relationModel = $data[$pk];
|
||||
$relationModel->setParent(clone $result);
|
||||
$relationModel->isUpdate(true);
|
||||
} else {
|
||||
$relationModel = null;
|
||||
}
|
||||
|
||||
$result->setRelation(Loader::parseName($relation), $relationModel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 多态一对一 关联模型预查询
|
||||
* @access protected
|
||||
* @param array $where 关联预查询条件
|
||||
* @param string $relation 关联名
|
||||
* @param string $subRelation 子关联
|
||||
* @param \Closure $closure 闭包
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = null)
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$list = $this->query->where($where)->with($subRelation)->select();
|
||||
$morphKey = $this->morphKey;
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
|
||||
foreach ($list as $set) {
|
||||
$data[$set->$morphKey] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增)当前关联数据对象
|
||||
* @access public
|
||||
* @param mixed $data 数据
|
||||
* @return Model|false
|
||||
*/
|
||||
public function save($data)
|
||||
{
|
||||
$model = $this->make();
|
||||
return $model->save($data) ? $model : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联对象实例
|
||||
* @param array $data
|
||||
* @return Model
|
||||
*/
|
||||
public function make($data = [])
|
||||
{
|
||||
if ($data instanceof Model) {
|
||||
$data = $data->getData();
|
||||
}
|
||||
|
||||
// 保存关联表数据
|
||||
$pk = $this->parent->getPk();
|
||||
|
||||
$data[$this->morphKey] = $this->parent->$pk;
|
||||
$data[$this->morphType] = $this->type;
|
||||
|
||||
return new $this->model($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(进执行一次)
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery()
|
||||
{
|
||||
if (empty($this->baseQuery) && $this->parent->getData()) {
|
||||
$pk = $this->parent->getPk();
|
||||
|
||||
$this->query->where([
|
||||
[$this->morphKey, '=', $this->parent->$pk],
|
||||
[$this->morphType, '=', $this->type],
|
||||
]);
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
316
thinkphp/library/think/model/relation/MorphTo.php
Normal file
316
thinkphp/library/think/model/relation/MorphTo.php
Normal file
@@ -0,0 +1,316 @@
|
||||
<?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\relation;
|
||||
|
||||
use Closure;
|
||||
use think\Exception;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
use think\model\Relation;
|
||||
|
||||
class MorphTo extends Relation
|
||||
{
|
||||
// 多态字段
|
||||
protected $morphKey;
|
||||
protected $morphType;
|
||||
// 多态别名
|
||||
protected $alias;
|
||||
// 关联名
|
||||
protected $relation;
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $morphType 多态字段名
|
||||
* @param string $morphKey 外键名
|
||||
* @param array $alias 多态别名定义
|
||||
* @param string $relation 关联名
|
||||
*/
|
||||
public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->morphType = $morphType;
|
||||
$this->morphKey = $morphKey;
|
||||
$this->alias = $alias;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的关联模型类的实例
|
||||
* @access public
|
||||
* @return Model
|
||||
*/
|
||||
public function getModel()
|
||||
{
|
||||
$morphType = $this->morphType;
|
||||
$model = $this->parseModel($this->parent->$morphType);
|
||||
|
||||
return (new $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据
|
||||
* @access public
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包查询条件
|
||||
* @return Model
|
||||
*/
|
||||
public function getRelation($subRelation = '', $closure = null)
|
||||
{
|
||||
$morphKey = $this->morphKey;
|
||||
$morphType = $this->morphType;
|
||||
|
||||
// 多态模型
|
||||
$model = $this->parseModel($this->parent->$morphType);
|
||||
|
||||
// 主键数据
|
||||
$pk = $this->parent->$morphKey;
|
||||
|
||||
$relationModel = (new $model)->relation($subRelation)->find($pk);
|
||||
|
||||
if ($relationModel) {
|
||||
$relationModel->setParent(clone $this->parent);
|
||||
}
|
||||
|
||||
return $relationModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @access public
|
||||
* @param string $operator 比较操作符
|
||||
* @param integer $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @return Query
|
||||
*/
|
||||
public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @access public
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null)
|
||||
{
|
||||
throw new Exception('relation not support: hasWhere');
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析模型的完整命名空间
|
||||
* @access protected
|
||||
* @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 array $alias 别名定义
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlias($alias)
|
||||
{
|
||||
$this->alias = $alias;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除关联查询参数
|
||||
* @access public
|
||||
* @return $this
|
||||
*/
|
||||
public function removeOption()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询
|
||||
* @access public
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
|
||||
{
|
||||
$morphKey = $this->morphKey;
|
||||
$morphType = $this->morphType;
|
||||
$range = [];
|
||||
|
||||
foreach ($resultSet as $result) {
|
||||
// 获取关联外键列表
|
||||
if (!empty($result->$morphKey)) {
|
||||
$range[$result->$morphType][] = $result->$morphKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
// 关联属性名
|
||||
$attr = Loader::parseName($relation);
|
||||
|
||||
foreach ($range as $key => $val) {
|
||||
// 多态类型映射
|
||||
$model = $this->parseModel($key);
|
||||
$obj = (new $model)->db();
|
||||
$pk = $obj->getPk();
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
if ($closure instanceof \Closure) {
|
||||
$closure($obj);
|
||||
|
||||
if ($field = $obj->getOptions('with_field')) {
|
||||
$obj->field($field)->removeOption('with_field');
|
||||
}
|
||||
}
|
||||
$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])) {
|
||||
$relationModel = null;
|
||||
} else {
|
||||
$relationModel = $data[$result->$morphKey];
|
||||
$relationModel->setParent(clone $result);
|
||||
$relationModel->isUpdate(true);
|
||||
}
|
||||
|
||||
$result->setRelation($attr, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(&$result, $relation, $subRelation, $closure)
|
||||
{
|
||||
$morphKey = $this->morphKey;
|
||||
$morphType = $this->morphType;
|
||||
// 多态类型映射
|
||||
$model = $this->parseModel($result->{$this->morphType});
|
||||
|
||||
$this->eagerlyMorphToOne($model, $relation, $result, $subRelation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @return integer
|
||||
*/
|
||||
public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
|
||||
{}
|
||||
|
||||
/**
|
||||
* 多态MorphTo 关联模型预查询
|
||||
* @access protected
|
||||
* @param string $model 关联模型对象
|
||||
* @param string $relation 关联名
|
||||
* @param Model $result
|
||||
* @param string $subRelation 子关联
|
||||
* @return void
|
||||
*/
|
||||
protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '')
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
$pk = $this->parent->{$this->morphKey};
|
||||
$data = (new $model)->with($subRelation)->find($pk);
|
||||
|
||||
if ($data) {
|
||||
$data->setParent(clone $result);
|
||||
$data->isUpdate(true);
|
||||
}
|
||||
|
||||
$result->setRelation(Loader::parseName($relation), $data ?: null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加关联数据
|
||||
* @access public
|
||||
* @param Model $model 关联模型对象
|
||||
* @param string $type 多态类型
|
||||
* @return Model
|
||||
*/
|
||||
public function associate($model, $type = '')
|
||||
{
|
||||
$morphKey = $this->morphKey;
|
||||
$morphType = $this->morphType;
|
||||
$pk = $model->getPk();
|
||||
|
||||
$this->parent->setAttr($morphKey, $model->$pk);
|
||||
$this->parent->setAttr($morphType, $type ?: get_class($model));
|
||||
$this->parent->save();
|
||||
|
||||
return $this->parent->setRelation($this->relation, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销关联数据
|
||||
* @access public
|
||||
* @return Model
|
||||
*/
|
||||
public function dissociate()
|
||||
{
|
||||
$morphKey = $this->morphKey;
|
||||
$morphType = $this->morphType;
|
||||
|
||||
$this->parent->setAttr($morphKey, null);
|
||||
$this->parent->setAttr($morphType, null);
|
||||
$this->parent->save();
|
||||
|
||||
return $this->parent->setRelation($this->relation, null);
|
||||
}
|
||||
|
||||
}
|
||||
335
thinkphp/library/think/model/relation/OneToOne.php
Normal file
335
thinkphp/library/think/model/relation/OneToOne.php
Normal file
@@ -0,0 +1,335 @@
|
||||
<?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\relation;
|
||||
|
||||
use Closure;
|
||||
use think\db\Query;
|
||||
use think\Exception;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
use think\model\Relation;
|
||||
|
||||
/**
|
||||
* Class OneToOne
|
||||
* @package think\model\relation
|
||||
*
|
||||
*/
|
||||
abstract class OneToOne extends Relation
|
||||
{
|
||||
// 预载入方式 0 -JOIN 1 -IN
|
||||
protected $eagerlyType = 1;
|
||||
// 当前关联的JOIN类型
|
||||
protected $joinType;
|
||||
// 要绑定的属性
|
||||
protected $bindAttr = [];
|
||||
// 关联名
|
||||
protected $relation;
|
||||
|
||||
/**
|
||||
* 设置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 mixed $field 关联字段
|
||||
* @param string $joinType JOIN方式
|
||||
* @param \Closure $closure 闭包条件
|
||||
* @param bool $first
|
||||
* @return void
|
||||
*/
|
||||
public function eagerly(Query $query, $relation, $field, $joinType, $closure, $first)
|
||||
{
|
||||
$name = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent))));
|
||||
|
||||
if ($first) {
|
||||
$table = $query->getTable();
|
||||
$query->table([$table => $name]);
|
||||
|
||||
if ($query->getOptions('field')) {
|
||||
$masterField = $query->getOptions('field');
|
||||
$query->removeOption('field');
|
||||
} else {
|
||||
$masterField = true;
|
||||
}
|
||||
|
||||
$query->field($masterField, false, $table, $name);
|
||||
}
|
||||
|
||||
// 预载入封装
|
||||
$joinTable = $this->query->getTable();
|
||||
$joinAlias = $relation;
|
||||
$joinType = $joinType ?: $this->joinType;
|
||||
|
||||
$query->via($joinAlias);
|
||||
|
||||
if ($this instanceof BelongsTo) {
|
||||
$joinOn = $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey;
|
||||
} else {
|
||||
$joinOn = $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey;
|
||||
}
|
||||
|
||||
if ($closure instanceof Closure) {
|
||||
// 执行闭包查询
|
||||
$closure($query);
|
||||
// 使用withField指定获取关联的字段,如
|
||||
// $query->where(['id'=>1])->withField('id,name');
|
||||
if ($query->getOptions('with_field')) {
|
||||
$field = $query->getOptions('with_field');
|
||||
$query->removeOption('with_field');
|
||||
}
|
||||
}
|
||||
|
||||
$query->join([$joinTable => $joinAlias], $joinOn, $joinType)
|
||||
->field($field, false, $joinTable, $joinAlias, $relation . '__');
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集)
|
||||
* @access protected
|
||||
* @param array $resultSet
|
||||
* @param string $relation
|
||||
* @param string $subRelation
|
||||
* @param \Closure $closure
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure);
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据)
|
||||
* @access protected
|
||||
* @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 bool $join 是否为JOIN方式
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $join = false)
|
||||
{
|
||||
if ($join || 0 == $this->eagerlyType) {
|
||||
// 模型JOIN关联组装
|
||||
foreach ($resultSet as $result) {
|
||||
$this->match($this->model, $relation, $result);
|
||||
}
|
||||
} else {
|
||||
// IN查询
|
||||
$this->eagerlySet($resultSet, $relation, $subRelation, $closure);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据)
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* @param bool $join 是否为JOIN方式
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(&$result, $relation, $subRelation, $closure, $join = false)
|
||||
{
|
||||
if (0 == $this->eagerlyType || $join) {
|
||||
// 模型JOIN关联组装
|
||||
$this->match($this->model, $relation, $result);
|
||||
} else {
|
||||
// IN查询
|
||||
$this->eagerlyOne($result, $relation, $subRelation, $closure);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增)当前关联数据对象
|
||||
* @access public
|
||||
* @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键
|
||||
* @return Model|false
|
||||
*/
|
||||
public function save($data)
|
||||
{
|
||||
if ($data instanceof Model) {
|
||||
$data = $data->getData();
|
||||
}
|
||||
|
||||
$model = new $this->model;
|
||||
// 保存关联表数据
|
||||
$data[$this->foreignKey] = $this->parent->{$this->localKey};
|
||||
|
||||
return $model->save($data) ? $model : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置预载入方式
|
||||
* @access public
|
||||
* @param integer $type 预载入方式 0 JOIN查询 1 IN查询
|
||||
* @return $this
|
||||
*/
|
||||
public function setEagerlyType($type)
|
||||
{
|
||||
$this->eagerlyType = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预载入方式
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getEagerlyType()
|
||||
{
|
||||
return $this->eagerlyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定关联表的属性到父模型属性
|
||||
* @access public
|
||||
* @param mixed $attr 要绑定的属性列表
|
||||
* @return $this
|
||||
*/
|
||||
public function bind($attr)
|
||||
{
|
||||
if (is_string($attr)) {
|
||||
$attr = explode(',', $attr);
|
||||
}
|
||||
$this->bindAttr = $attr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取绑定属性
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getBindAttr()
|
||||
{
|
||||
return $this->bindAttr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一对一 关联模型预查询拼装
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($list[$relation])) {
|
||||
$array = array_unique($list[$relation]);
|
||||
|
||||
if (count($array) == 1 && null === current($array)) {
|
||||
$relationModel = null;
|
||||
} else {
|
||||
$relationModel = new $model($list[$relation]);
|
||||
$relationModel->setParent(clone $result);
|
||||
$relationModel->isUpdate(true);
|
||||
}
|
||||
|
||||
if (!empty($this->bindAttr)) {
|
||||
$this->bindAttr($relationModel, $result, $this->bindAttr);
|
||||
}
|
||||
} else {
|
||||
$relationModel = null;
|
||||
}
|
||||
|
||||
$result->setRelation(Loader::parseName($relation), $relationModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定关联属性到父模型
|
||||
* @access protected
|
||||
* @param Model $model 关联模型对象
|
||||
* @param Model $result 父模型对象
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function bindAttr($model, &$result)
|
||||
{
|
||||
foreach ($this->bindAttr as $key => $attr) {
|
||||
$key = is_numeric($key) ? $attr : $key;
|
||||
if (isset($result->$key)) {
|
||||
throw new Exception('bind attr has exists:' . $key);
|
||||
} else {
|
||||
$result->setAttr($key, $model ? $model->$attr : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 一对一 关联模型预查询(IN方式)
|
||||
* @access public
|
||||
* @param array $where 关联预查询条件
|
||||
* @param string $key 关联键名
|
||||
* @param string $relation 关联名
|
||||
* @param string $subRelation 子关联
|
||||
* @param \Closure $closure
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = null)
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
if ($closure instanceof Closure) {
|
||||
$closure($this->query);
|
||||
|
||||
if ($field = $this->query->getOptions('with_field')) {
|
||||
$this->query->field($field)->removeOption('with_field');
|
||||
}
|
||||
}
|
||||
|
||||
$list = $this->query->where($where)->with($subRelation)->select();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
|
||||
foreach ($list as $set) {
|
||||
$data[$set->$key] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user