提交
This commit is contained in:
284
vendor/topthink/think-orm/src/model/Collection.php
vendored
Normal file
284
vendor/topthink/think-orm/src/model/Collection.php
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: zhangyajun <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\model;
|
||||
|
||||
use think\Collection as BaseCollection;
|
||||
use think\model\contract\Modelable as Model;
|
||||
use think\Paginator;
|
||||
|
||||
/**
|
||||
* 模型数据集类.
|
||||
*
|
||||
* @template TKey of array-key
|
||||
* @template TModel of \think\Model
|
||||
*
|
||||
* @extends BaseCollection<TKey, TModel>
|
||||
*/
|
||||
class Collection extends BaseCollection
|
||||
{
|
||||
/**
|
||||
* 延迟预载入关联查询.
|
||||
*
|
||||
* @param array $relation 关联
|
||||
* @param mixed $cache 关联缓存
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function load(array $relation, $cache = false)
|
||||
{
|
||||
if (!$this->isEmpty()) {
|
||||
$item = current($this->items);
|
||||
$item->eagerlyResultSet($this->items, $relation, [], false, $cache);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据集的数据.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
$this->each(function (Model $model) {
|
||||
$model->delete();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据.
|
||||
*
|
||||
* @param array $data 数据数组
|
||||
* @param array $allowField 允许字段
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update(array $data, array $allowField = []): bool
|
||||
{
|
||||
$this->each(function (Model $model) use ($data, $allowField) {
|
||||
if (!empty($allowField)) {
|
||||
$model->allowField($allowField);
|
||||
}
|
||||
|
||||
$model->save($data);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要隐藏的输出属性.
|
||||
*
|
||||
* @param array $hidden 属性列表
|
||||
* @param bool $merge 是否合并
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function hidden(array $hidden, bool $merge = false)
|
||||
{
|
||||
$this->each(function (Model $model) use ($hidden, $merge) {
|
||||
$model->hidden($hidden, $merge);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要输出的属性.
|
||||
*
|
||||
* @param array $visible
|
||||
* @param bool $merge 是否合并
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function visible(array $visible, bool $merge = false)
|
||||
{
|
||||
$this->each(function (Model $model) use ($visible, $merge) {
|
||||
$model->visible($visible, $merge);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要追加的输出属性.
|
||||
*
|
||||
* @param array $append 属性列表
|
||||
* @param bool $merge 是否合并
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function append(array $append, bool $merge = false)
|
||||
{
|
||||
$this->each(function (Model $model) use ($append, $merge) {
|
||||
$model->append($append, $merge);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性映射.
|
||||
*
|
||||
* @param array $mapping 属性映射
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function mapping(array $mapping)
|
||||
{
|
||||
$this->each(function (Model $model) use ($mapping) {
|
||||
$model->mapping($mapping);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型输出场景.
|
||||
*
|
||||
* @param string $scene 场景名称
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function scene(string $scene)
|
||||
{
|
||||
$this->each(function (Model $model) use ($scene) {
|
||||
$model->scene($scene);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据字段获取器.
|
||||
*
|
||||
* @param string|array $name 字段名
|
||||
* @param callable $callback 闭包获取器
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withAttr(string|array $name, ?callable $callback = null)
|
||||
{
|
||||
$this->each(function (Model $model) use ($name, $callback) {
|
||||
$model->withFieldAttr($name, $callback);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定(一对一)关联属性到当前模型.
|
||||
*
|
||||
* @param string $relation 关联名称
|
||||
* @param array $attrs 绑定属性
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function bindAttr(string $relation, array $attrs = [])
|
||||
{
|
||||
$this->each(function (Model $model) use ($relation, $attrs) {
|
||||
$model->bindAttr($relation, $attrs);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按指定键整理数据.
|
||||
*
|
||||
* @param mixed $items 数据
|
||||
* @param string|null $indexKey 键名
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dictionary($items = null, ?string &$indexKey = null)
|
||||
{
|
||||
if ($items instanceof self || $items instanceof Paginator) {
|
||||
$items = $items->all();
|
||||
}
|
||||
|
||||
$items = is_null($items) ? $this->items : $items;
|
||||
|
||||
if ($items && empty($indexKey)) {
|
||||
$indexKey = $items[0]->getPk();
|
||||
}
|
||||
|
||||
if (isset($indexKey) && is_string($indexKey)) {
|
||||
return array_column($items, null, $indexKey);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较数据集,返回差集.
|
||||
*
|
||||
* @param mixed $items 数据
|
||||
* @param string|null $indexKey 指定比较的键名
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function diff($items, ?string $indexKey = null)
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
return new static($items);
|
||||
}
|
||||
|
||||
$diff = [];
|
||||
$dictionary = $this->dictionary($items, $indexKey);
|
||||
|
||||
if (is_string($indexKey)) {
|
||||
foreach ($this->items as $item) {
|
||||
if (!isset($dictionary[$item[$indexKey]])) {
|
||||
$diff[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new static($diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较数据集,返回交集.
|
||||
*
|
||||
* @param mixed $items 数据
|
||||
* @param string|null $indexKey 指定比较的键名
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function intersect($items, ?string $indexKey = null)
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
return new static([]);
|
||||
}
|
||||
|
||||
$intersect = [];
|
||||
$dictionary = $this->dictionary($items, $indexKey);
|
||||
|
||||
if (is_string($indexKey)) {
|
||||
foreach ($this->items as $item) {
|
||||
if (isset($dictionary[$item[$indexKey]])) {
|
||||
$intersect[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new static($intersect);
|
||||
}
|
||||
}
|
||||
77
vendor/topthink/think-orm/src/model/Pivot.php
vendored
Normal file
77
vendor/topthink/think-orm/src/model/Pivot.php
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 多对多中间表模型类.
|
||||
*/
|
||||
class Pivot extends Model
|
||||
{
|
||||
/**
|
||||
* 父模型.
|
||||
*
|
||||
* @var Model
|
||||
*/
|
||||
public $parent;
|
||||
protected $pivotName;
|
||||
|
||||
/**
|
||||
* 是否时间自动写入.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @param Model|null $parent 上级模型
|
||||
* @param string $table 中间数据表名
|
||||
*/
|
||||
public function __construct(array $data = [], ?Model $parent = null, string $table = '')
|
||||
{
|
||||
$this->pivotName = $table;
|
||||
$this->parent = $parent;
|
||||
parent::__construct($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化模型.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
if (is_null($this->getOption('name'))) {
|
||||
$this->setOption('name', $this->pivotName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的模型实例.
|
||||
*
|
||||
* @param array|object $data 数据
|
||||
* @param array $options
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function newInstance(array | object $data = [], array $options = [])
|
||||
{
|
||||
$this->data($data);
|
||||
return $this->clone();
|
||||
}
|
||||
}
|
||||
354
vendor/topthink/think-orm/src/model/Relation.php
vendored
Normal file
354
vendor/topthink/think-orm/src/model/Relation.php
vendored
Normal file
@@ -0,0 +1,354 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model;
|
||||
|
||||
use Closure;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\model\Collection;
|
||||
use think\model\contract\Modelable as Model;
|
||||
|
||||
/**
|
||||
* 模型关联基础类.
|
||||
*
|
||||
* @mixin Query
|
||||
*/
|
||||
abstract class Relation
|
||||
{
|
||||
/**
|
||||
* 父模型对象
|
||||
*
|
||||
* @var Model
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* 当前关联的模型类名.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* 关联模型查询对象
|
||||
*
|
||||
* @var Query
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* 关联表外键.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $foreignKey;
|
||||
|
||||
/**
|
||||
* 关联表主键.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $localKey;
|
||||
|
||||
/**
|
||||
* 是否执行关联基础查询.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $baseQuery;
|
||||
|
||||
/**
|
||||
* 是否为自关联.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $selfRelation = false;
|
||||
|
||||
/**
|
||||
* 关联数据字段限制.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $withField;
|
||||
|
||||
/**
|
||||
* 排除关联数据字段.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $withoutField;
|
||||
|
||||
/**
|
||||
* 默认数据.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $default;
|
||||
|
||||
/**
|
||||
* 获取一条关联数据.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isOneofMany = false;
|
||||
|
||||
/**
|
||||
* 获取关联的所属模型.
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function getParent(): Model
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的关联模型类的Query实例.
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联表外键.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getForeignKey(): string
|
||||
{
|
||||
return $this->foreignKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联表主键.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalKey(): string
|
||||
{
|
||||
return $this->localKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的关联模型类的实例.
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function getModel(): Model
|
||||
{
|
||||
return $this->query->getModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前关联是否为自关联.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSelfRelation(): bool
|
||||
{
|
||||
return $this->selfRelation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装关联数据集.
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
*
|
||||
* @param array $resultSet 关联数据结果集
|
||||
* @return Collection 返回模型集合对象
|
||||
*/
|
||||
protected function resultSetBuild(array $resultSet)
|
||||
{
|
||||
return (new $this->model())->toCollection($resultSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联查询的字段
|
||||
*
|
||||
* 根据模型名称处理查询字段
|
||||
*
|
||||
* @param string $model 模型名称
|
||||
* @return mixed 返回处理后的查询字段
|
||||
*/
|
||||
protected function getQueryFields(string $model)
|
||||
{
|
||||
$fields = $this->query->getOption('field');
|
||||
$this->query->removeOption('field');
|
||||
|
||||
return $this->getRelationQueryFields($fields, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联查询的字段
|
||||
*
|
||||
* 处理关联查询的字段,添加表名前缀
|
||||
*
|
||||
* @param mixed $fields 字段定义
|
||||
* @param string $model 模型名称
|
||||
* @return mixed 返回处理后的查询字段
|
||||
*/
|
||||
protected function getRelationQueryFields($fields, string $model)
|
||||
{
|
||||
if (empty($fields) || '*' == $fields) {
|
||||
return $model . '.*';
|
||||
}
|
||||
|
||||
if (is_string($fields)) {
|
||||
$fields = explode(',', $fields);
|
||||
}
|
||||
|
||||
foreach ($fields as &$field) {
|
||||
if (!str_contains($field, '.')) {
|
||||
$field = $model . '.' . $field;
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理关联查询条件
|
||||
*
|
||||
* 为查询条件添加关联表前缀
|
||||
*
|
||||
* @param array &$where 查询条件
|
||||
* @param string $relation 关联表名
|
||||
* @return void
|
||||
*/
|
||||
protected function getQueryWhere(array &$where, string $relation): void
|
||||
{
|
||||
if (array_is_list($where) && isset($where[0]) && is_string($where[0])) {
|
||||
$where = [ $where ];
|
||||
}
|
||||
foreach ($where as $key => &$val) {
|
||||
if (is_string($key)) {
|
||||
$where[] = [!str_contains($key, '.') ? $relation . '.' . $key : $key, '=', $val];
|
||||
unset($where[$key]);
|
||||
} elseif (is_array($val) && isset($val[0]) && !str_contains($val[0], '.')) {
|
||||
$val[0] = $relation . '.' . $val[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联数据默认值
|
||||
*
|
||||
* @param mixed $data 模型数据
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getDefaultModel($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
$model = new $this->model($data);
|
||||
} elseif ($data instanceof Closure) {
|
||||
$model = new $this->model();
|
||||
$data($model);
|
||||
} else {
|
||||
$model = $data;
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理关联查询及软删除的关联查询
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $relation 关联名
|
||||
* @param mixed $where 查询条件
|
||||
* @param string $logic 查询逻辑
|
||||
* @return Query 返回查询对象
|
||||
*/
|
||||
protected function getRelationSoftDelete(Query $query, $relation, $where = null, $logic = '')
|
||||
{
|
||||
if ($where) {
|
||||
if (is_array($where)) {
|
||||
$this->getQueryWhere($where, $relation);
|
||||
} elseif ($where instanceof Query) {
|
||||
$where->via($relation);
|
||||
} elseif ($where instanceof Closure) {
|
||||
$where($this->query->via($relation));
|
||||
$where = $this->query;
|
||||
}
|
||||
|
||||
$whereLogic = 'OR' == $logic ? 'whereOr' : 'where';
|
||||
$query->$whereLogic(function ($query) use ($where) {
|
||||
$query->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
// 启用软删除则增加软删除条件
|
||||
$softDelete = $this->query->getOption('soft_delete');
|
||||
return $query->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联的最新一条数据.
|
||||
*
|
||||
* @param string $field 排序字段
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function first(string $field = '')
|
||||
{
|
||||
$field = $field ?: $this->query->getPk();
|
||||
$this->query->order($field, 'desc');
|
||||
$this->isOneofMany = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联的最旧一条数据.
|
||||
*
|
||||
* @param string $field 排序字段
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function last(string $field = '')
|
||||
{
|
||||
$field = $field ?: $this->query->getPk();
|
||||
$this->query->order($field, 'asc');
|
||||
$this->isOneofMany = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if ($this->query) {
|
||||
// 执行基础查询
|
||||
$this->baseQuery();
|
||||
|
||||
$result = call_user_func_array([$this->query, $method], $args);
|
||||
|
||||
return $result === $this->query ? $this : $result;
|
||||
}
|
||||
|
||||
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
|
||||
}
|
||||
}
|
||||
744
vendor/topthink/think-orm/src/model/View.php
vendored
Normal file
744
vendor/topthink/think-orm/src/model/View.php
vendored
Normal file
@@ -0,0 +1,744 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
use think\Entity;
|
||||
use think\exception\ValidateException;
|
||||
use think\helper\Str;
|
||||
use think\Model;
|
||||
use think\model\Collection;
|
||||
use think\model\contract\Modelable;
|
||||
|
||||
/**
|
||||
* 视图模型
|
||||
*/
|
||||
abstract class View extends Entity
|
||||
{
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param Model $model 模型连接对象
|
||||
*/
|
||||
public function __construct(?Model $model = null)
|
||||
{
|
||||
parent::__construct($model);
|
||||
|
||||
// 初始化模型数据
|
||||
$this->initData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化实体数据属性.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initData()
|
||||
{
|
||||
// 获取属性映射关系
|
||||
$properties = $this->getEntityPropertiesMap();
|
||||
$data = $this->model()->getData();
|
||||
if (empty($data)) {
|
||||
return ;
|
||||
}
|
||||
foreach ($properties as $key => $field) {
|
||||
if (is_int($key)) {
|
||||
// 主模型同名属性
|
||||
$this->$field = $this->fetchViewAttr($field, $data);
|
||||
} elseif (strpos($field, '->')) {
|
||||
// 关联属性或JSON字段映射
|
||||
$this->$key = $this->getRelationMapAttr($field, $data);
|
||||
} else {
|
||||
// 主模型属性映射
|
||||
$this->$key = $this->fetchViewAttr($field, $data);
|
||||
}
|
||||
}
|
||||
// 标记数据存在
|
||||
$this->exists(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联或JSON字段映射的属性值.
|
||||
*
|
||||
* @param string $field 视图属性
|
||||
* @param array $data 模型数据
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function getRelationMapAttr(string $field, array $data)
|
||||
{
|
||||
$items = explode('->', $field);
|
||||
$relation = array_shift($items);
|
||||
if (isset($data[$relation])) {
|
||||
$value = $this->model()->$relation;
|
||||
foreach ($items as $item) {
|
||||
if (is_array($value)) {
|
||||
$value = $value[$item] ?? null;
|
||||
} elseif (is_object($value)) {
|
||||
$value = $value->$item ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $value ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视图属性值(支持视图获取器).
|
||||
*
|
||||
* @param string $field 视图属性
|
||||
* @param array $data 模型数据
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function fetchViewAttr(string $field, array $data)
|
||||
{
|
||||
$method = 'get' . Str::camel($field) . 'Attr';
|
||||
$model = $this->model();
|
||||
if (method_exists($this, $method)) {
|
||||
// 视图获取器
|
||||
$value = $this->$method($model);
|
||||
} elseif ($model->hasData($field)) {
|
||||
// 获取主模型数据(支持获取器)
|
||||
$value = $model->$field;
|
||||
} else {
|
||||
// 获取自动映射的属性数据
|
||||
$value = $this->getAutoRelationValue($field, $data);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取autoMapping自动映射的视图属性值.
|
||||
*
|
||||
* @param string $field 视图属性
|
||||
* @param array $data 模型数据
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function getAutoRelationValue(string $field, array $data)
|
||||
{
|
||||
$relations = $this->getOption('autoMapping', []);
|
||||
if ($relations) {
|
||||
$mapping = $this->getOption('viewMapping', []);
|
||||
foreach ($relations as $relation) {
|
||||
if (isset($data[$relation]) && $this->model()->$relation->hasData($field)) {
|
||||
$value = $this->model()->$relation->$field;
|
||||
if (!isset($mapping[$field])) {
|
||||
$mapping[$field] = $relation . '->' . $field;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->setOption('viewMapping', $mapping);
|
||||
}
|
||||
return $value ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体属性列表.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getEntityProperties(): array
|
||||
{
|
||||
$reflection = new ReflectionClass($this);
|
||||
$properties = [];
|
||||
foreach ($reflection->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
|
||||
$properties[] = $property->getName();
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包含映射关系的实体属性列表.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getEntityPropertiesMap(): array
|
||||
{
|
||||
$properties = $this->getOption('viewProperties');
|
||||
if (empty($properties)) {
|
||||
// 获取实体属性列表
|
||||
$fields = $this->getEntityProperties();
|
||||
// 获取属性映射列表
|
||||
$mapping = $this->getOption('viewMapping', []);
|
||||
$relations = $this->getOption('autoMapping', []);
|
||||
$properties = [];
|
||||
foreach ($fields as $field) {
|
||||
if (isset($mapping[$field])) {
|
||||
// 映射属性
|
||||
$properties[$field] = $mapping[$field];
|
||||
if (strpos($mapping[$field], '->')) {
|
||||
$relation = strstr($mapping[$field], '->', true);
|
||||
if (!$this->model()->getFieldType($relation)) {
|
||||
$relations[] = $relation;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 主模型同名属性
|
||||
$properties[] = $field;
|
||||
}
|
||||
}
|
||||
$this->setOption('autoRelation', array_unique($relations));
|
||||
$this->setOption('viewProperties', $properties);
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析autoMapping的字段映射
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseAutoMapping(): array
|
||||
{
|
||||
$fields = $this->getEntityProperties();
|
||||
$mapping = $this->getOption('viewMapping', []);
|
||||
$relations = $this->getOption('autoMapping', []);
|
||||
if ($relations) {
|
||||
array_unshift($relations, $this->model());
|
||||
foreach ($fields as $field) {
|
||||
if (isset($mapping[$field])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($relations as $relation) {
|
||||
if (is_object($relation) && $relation->getFieldType($field)) {
|
||||
break;
|
||||
} elseif (is_string($relation) && !strpos($relation, '.') && $this->model()->$relation()->getFieldType($field)) {
|
||||
$mapping[$field] = $relation . '->' . $field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->setOption('viewMapping', $mapping);
|
||||
}
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为数组. 视图模型不支持 hidden visible append
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$data = $this->getData();
|
||||
foreach ($data as $name => &$val) {
|
||||
if ($val instanceof Modelable || $val instanceof Collection) {
|
||||
$val = $val->toArray();
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置视图模型数据
|
||||
*
|
||||
* @param array|object $data 数据
|
||||
* @param mixed $validate 是否验证数据
|
||||
* @return $this
|
||||
*/
|
||||
public function data(array | object $data, $validate = false)
|
||||
{
|
||||
// 处理对象数据
|
||||
if (is_object($data)) {
|
||||
$data = get_object_vars($data);
|
||||
}
|
||||
|
||||
foreach ($this->getEntityProperties() as $field) {
|
||||
$this->$field = $data[$field] ?? null;
|
||||
}
|
||||
|
||||
// 验证数据
|
||||
if ($validate) {
|
||||
if (!is_bool($validate)) {
|
||||
// 指定验证场景
|
||||
$this->scene($validate);
|
||||
}
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新模型数据.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
$this->initData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空视图模型数据
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
foreach ($this->getEntityProperties() as $field) {
|
||||
$this->$field = null;
|
||||
}
|
||||
$this->exists(false);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视图模型数据
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getData(): array
|
||||
{
|
||||
$data = [];
|
||||
foreach ($this->getEntityProperties() as $field) {
|
||||
$data[$field] = $this->$field;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数据是否为空.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->model()->isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取克隆的模型实例.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function clone()
|
||||
{
|
||||
$model = new static();
|
||||
return $model->setModel($this->model());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型.
|
||||
*
|
||||
* @param Model $model 模型对象
|
||||
* @return $this
|
||||
*/
|
||||
public function setModel(Model $model)
|
||||
{
|
||||
parent::setModel($model);
|
||||
$this->initData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型数据转Json.
|
||||
*
|
||||
* @param int $options json参数
|
||||
* @return string
|
||||
*/
|
||||
public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
|
||||
{
|
||||
return json_encode($this->toArray(), $options);
|
||||
}
|
||||
|
||||
// JsonSerializable
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置关联数据.
|
||||
*
|
||||
* @param string $relation 关联属性
|
||||
* @param Model $model 关联数据
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setRelation($relation, $model)
|
||||
{
|
||||
$this->model()->setRelation($relation, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置关联绑定数据
|
||||
*
|
||||
* @param Model $model 关联对象
|
||||
* @param array $bind 绑定属性
|
||||
* @param string $relation 关联名称
|
||||
* @return void
|
||||
*/
|
||||
public function bindRelationAttr($model, $bind, $relation)
|
||||
{
|
||||
if ($relation) {
|
||||
$this->setRelation($relation, $model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图模型数据转换为模型数据(用于写入 暂不支持子关联写入).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function convertData(): array
|
||||
{
|
||||
// 获取属性映射
|
||||
$properties = $this->getEntityPropertiesMap();
|
||||
$data = $this->getData();
|
||||
$item = [];
|
||||
$together = [];
|
||||
$array = [];
|
||||
foreach ($properties as $key => $field) {
|
||||
if (strpos($field, '->')) {
|
||||
if (!isset($data[$key]) || substr_count($field, '->') > 1) {
|
||||
// 排除空值 以及 多级关联属性值
|
||||
continue;
|
||||
}
|
||||
[$relation, $field] = explode('->', $field);
|
||||
if ('json' == $this->model()->getFieldType($relation)) {
|
||||
// JSON数据赋值
|
||||
$array[$relation][$field] = $data[$key];
|
||||
} else {
|
||||
// 关联数据赋值
|
||||
$together[] = $relation;
|
||||
if ($this->model()->hasData($relation)) {
|
||||
// 关联更新
|
||||
$this->model()->$relation->$field = $data[$key];
|
||||
} else {
|
||||
// 新增关联
|
||||
$array[$relation][$field] = $data[$key];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$value = $data[is_int($key) ? $field : $key];
|
||||
if (isset($value)) {
|
||||
$item[$field] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关联数据或JSON数据封装
|
||||
foreach ($array as $relation => $val) {
|
||||
$this->model()->$relation = $val;
|
||||
}
|
||||
|
||||
if (!empty($together)) {
|
||||
// 自动关联写入
|
||||
$this->model()->together(array_unique($together));
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证场景.
|
||||
*
|
||||
* @param string|array $scene 场景名或数组
|
||||
* @return $this
|
||||
*/
|
||||
public function scene(string|array $scene)
|
||||
{
|
||||
return $this->setOption('scene', $scene);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证视图模型数据.
|
||||
*
|
||||
* @throws ValidateException
|
||||
* @return bool
|
||||
*/
|
||||
private function validate(): bool
|
||||
{
|
||||
$validater = $this->getOption('validate');
|
||||
if (!empty($validater) && !$this->getOption('dataHasValidate', false)) {
|
||||
$data = $this->getData();
|
||||
$result = validate($validater)
|
||||
->scene($this->getOption('scene') ?: array_keys($data))
|
||||
->check($data);
|
||||
if ($result) {
|
||||
$this->setOption('dataHasValidate', true);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据是否存在.
|
||||
*
|
||||
* @param bool $exists
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function exists(bool $exists = true)
|
||||
{
|
||||
return $this->setOption('exists', $exists);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数据是否存在数据库.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isExists(): bool
|
||||
{
|
||||
return $this->getOption('exists', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存模型实例数据.
|
||||
*
|
||||
* @param array|object $data 数据
|
||||
* @param mixed $where 更新条件 true为强制新增
|
||||
* @param bool $refresh 是否刷新数据
|
||||
* @return bool
|
||||
*/
|
||||
public function save(array | object $data = [], $where = [], bool $refresh = false): bool
|
||||
{
|
||||
if ($data) {
|
||||
$this->data($data);
|
||||
}
|
||||
// 验证数据
|
||||
$this->validate();
|
||||
|
||||
// 根据映射关系转换为实际模型数据
|
||||
$data = $this->convertData();
|
||||
|
||||
// 处理自动时间字段数据
|
||||
foreach ($this->model()->getAutoTimeFields() as $field) {
|
||||
unset($data[$field]);
|
||||
}
|
||||
|
||||
$result = $this->model()
|
||||
->exists($this->isExists())
|
||||
->save($data, $where, $refresh);
|
||||
|
||||
if ($result) {
|
||||
// 刷新数据
|
||||
$this->refresh();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除模型数据.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
if ($this->model()->delete()) {
|
||||
$this->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入数据.
|
||||
*
|
||||
* @param array|object $data 数据
|
||||
* @return static
|
||||
*/
|
||||
public static function create(array | object $data)
|
||||
{
|
||||
$entity = new static();
|
||||
$entity->exists(false)->save($data, true);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据.
|
||||
*
|
||||
* @param array|object $data 数据
|
||||
* @param mixed $where 更新条件
|
||||
* @return static
|
||||
*/
|
||||
public static function update(array | object $data, $where = [])
|
||||
{
|
||||
$entity = new static();
|
||||
$entity->exists(true)->save($data, $where, true);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据集写入
|
||||
*
|
||||
* @param iterable $dataSet 数据集
|
||||
* @param bool $replace 是否replace
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function saveAll(iterable $dataSet, bool $replace = true): Collection
|
||||
{
|
||||
$collection = [];
|
||||
foreach ($dataSet as $data) {
|
||||
$entity = new static();
|
||||
$pk = $entity->getPk();
|
||||
if ($replace) {
|
||||
$exists = true;
|
||||
foreach ((array) $pk as $field) {
|
||||
if (is_string($field) && !isset($data[$field])) {
|
||||
$exists = false;
|
||||
}
|
||||
}
|
||||
$entity->exists($exists);
|
||||
}
|
||||
$entity->save($data, !$replace);
|
||||
$collection[] = $entity;
|
||||
}
|
||||
return new Collection($collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性 支持获取器
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
if (property_exists($this, $name)) {
|
||||
return $this->$name ?? null;
|
||||
}
|
||||
return $this->model()->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据 支持类型自动转换
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param mixed $value 值
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __set(string $name, $value): void
|
||||
{
|
||||
if (property_exists($this, $name)) {
|
||||
$this->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测数据对象的值
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
return !is_null($this->__get($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁数据对象的值
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __unset(string $name): void
|
||||
{
|
||||
unset($this->$name);
|
||||
}
|
||||
|
||||
public function __debugInfo()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆模型实例
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化模型对象
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return $this->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化模型对象
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function __unserialize(array $data)
|
||||
{
|
||||
parent::__construct();
|
||||
if (!empty($data)){
|
||||
$this->exists(true);
|
||||
}
|
||||
|
||||
foreach ($data as $name => $val) {
|
||||
$this->$name = $val;
|
||||
}
|
||||
}
|
||||
|
||||
public static function __callStatic($method, $args)
|
||||
{
|
||||
$entity = new static();
|
||||
$model = $entity->model();
|
||||
if (in_array($method, ['destroy'])) {
|
||||
$db = $model;
|
||||
} else {
|
||||
// 处理映射字段的查询
|
||||
$map = $entity->parseAutoMapping();
|
||||
$alias = Str::snake(class_basename($model));
|
||||
$db = $model->db()->alias($alias)->via($alias)->fieldMap($map);
|
||||
}
|
||||
|
||||
$auto = $entity->getOption('autoRelation');
|
||||
if (!empty($auto) && !in_array(strtolower($method), ['with','withjoin'])) {
|
||||
// 自动关联查询
|
||||
$db->with($auto);
|
||||
}
|
||||
|
||||
return call_user_func_array([$db, $method], $args);
|
||||
}
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (in_array($method, ['hidden', 'visible', 'append'])) {
|
||||
// 不支持输出设置
|
||||
return $this;
|
||||
}
|
||||
|
||||
// 调用Model类方法
|
||||
$result = call_user_func_array([$this->model(), $method], $args);
|
||||
return $result instanceof Model ? $this : $result;
|
||||
}
|
||||
}
|
||||
72
vendor/topthink/think-orm/src/model/Virtual.php
vendored
Normal file
72
vendor/topthink/think-orm/src/model/Virtual.php
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model;
|
||||
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\Model;
|
||||
use think\model\contract\Modelable;
|
||||
|
||||
/**
|
||||
* Class Virtual.
|
||||
* 虚拟模型
|
||||
*/
|
||||
abstract class Virtual extends Model
|
||||
{
|
||||
/**
|
||||
* 创建数据.
|
||||
*
|
||||
* @param array|object $data 数据
|
||||
* @param array $allowField 允许字段
|
||||
* @param bool $replace 使用Replace
|
||||
* @param string $suffix 数据表后缀
|
||||
* @return Modelable
|
||||
*/
|
||||
public static function create(array | object $data, array $allowField = [], bool $replace = false, string $suffix = ''): Modelable
|
||||
{
|
||||
$model = new static();
|
||||
|
||||
if (!empty($data)) {
|
||||
// 初始化模型数据
|
||||
$model->data($data);
|
||||
}
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Db对象实例.
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
throw new Exception('virtual model not support db query');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据表字段类型列表(或某个字段的类型).
|
||||
*
|
||||
* @param string|null $field 字段名
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
protected function getFields(?string $field = null)
|
||||
{
|
||||
$schema = array_merge($this->getOption('schema', []), $this->getOption('type', []));
|
||||
|
||||
if ($field) {
|
||||
return $schema[$field] ?? null;
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
719
vendor/topthink/think-orm/src/model/concern/Attribute.php
vendored
Normal file
719
vendor/topthink/think-orm/src/model/concern/Attribute.php
vendored
Normal file
@@ -0,0 +1,719 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\concern;
|
||||
|
||||
use BackedEnum;
|
||||
use Stringable;
|
||||
use think\db\Express;
|
||||
use think\db\Raw;
|
||||
use think\helper\Str;
|
||||
use think\model\Collection;
|
||||
use think\model\contract\EnumTransform;
|
||||
use think\model\contract\FieldTypeTransform;
|
||||
use think\model\contract\Modelable as Model;
|
||||
use think\model\contract\Typeable;
|
||||
use think\model\type\Date;
|
||||
use think\model\type\DateTime;
|
||||
use think\model\type\Json;
|
||||
|
||||
/**
|
||||
* 模型数据处理.
|
||||
*/
|
||||
trait Attribute
|
||||
{
|
||||
/**
|
||||
* 初始化模型数据.
|
||||
*
|
||||
* @param array|object $data 实体模型数据
|
||||
* @param bool $fromSave
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function initializeData(array | object $data, bool $fromSave = false)
|
||||
{
|
||||
// 分析数据
|
||||
$data = $this->parseData($data);
|
||||
$schema = $this->getFields();
|
||||
$fields = array_keys($schema);
|
||||
|
||||
// 模型赋值
|
||||
foreach ($data as $name => $value) {
|
||||
if (in_array($name, $this->getOption('disuse'))) {
|
||||
// 废弃字段
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_contains($name, '__')) {
|
||||
// 组装关联JOIN查询数据
|
||||
[$relation, $attr] = explode('__', $name, 2);
|
||||
|
||||
$relations[$relation][$attr] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$trueName = $fromSave ? $this->getMappingName($name) : $name;
|
||||
if (in_array($trueName, $fields)) {
|
||||
$type = $schema[$trueName] ?? 'string';
|
||||
// 读取数据后进行类型转换
|
||||
if (!$fromSave || !$this->hasSetAttr($trueName)) {
|
||||
$value = $this->readTransform($value, $type);
|
||||
}
|
||||
// 数据赋值
|
||||
$this->setData($trueName, $value);
|
||||
if ($trueName == $this->getPk()) {
|
||||
$this->setKey($value);
|
||||
}
|
||||
// 记录原始数据
|
||||
$origin[$trueName] = $value;
|
||||
} else {
|
||||
// 非数据表字段或关联数据 额外赋值
|
||||
$this->setData($trueName, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($relations)) {
|
||||
// 设置关联数据
|
||||
$this->parseRelationData($relations);
|
||||
}
|
||||
|
||||
if (!empty($origin) && !$fromSave) {
|
||||
$this->trigger('AfterRead');
|
||||
$this->setOption('origin', $origin);
|
||||
$this->setOption('get', []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主键名.
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function getPk()
|
||||
{
|
||||
return $this->getOption('pk', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表名(不含前后缀).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->getOption('name', Str::snake(class_basename(static::class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析模型数据.
|
||||
*
|
||||
* @param array|object $data 数据
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseData(array | object $data): array
|
||||
{
|
||||
if ($data instanceof self) {
|
||||
$data = $data->getData();
|
||||
} elseif (is_object($data)) {
|
||||
$data = get_object_vars($data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态设置数据字段获取器.
|
||||
*
|
||||
* @param array|string $attr 字段名
|
||||
* @param callable $callback 闭包获取器
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withFieldAttr(array | string $attr, ?callable $callback = null)
|
||||
{
|
||||
if (is_array($attr)) {
|
||||
foreach ($attr as $name => $closure) {
|
||||
$this->withFieldAttr($name, $closure);
|
||||
}
|
||||
} else {
|
||||
$name = $this->getRealFieldName($attr);
|
||||
$this->setWeakData('withAttr', $name, $callback);
|
||||
// 自动追加输出
|
||||
self::$weakMap[$this]['append'][] = $name;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实际字段名.
|
||||
* 严格模式下 完全和数据表字段对应一致(默认)
|
||||
* 非严格模式 统一转换为snake规范(支持驼峰规范读取)
|
||||
*
|
||||
* @param string $name 字段名
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRealFieldName(string $name)
|
||||
{
|
||||
if (false === $this->getOption('strict')) {
|
||||
return Str::snake($name);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据读取 类型转换.
|
||||
*
|
||||
* @param mixed $value 值
|
||||
* @param string|array|null $type 要转换的类型
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function readTransform($value, string | array | null $type)
|
||||
{
|
||||
if (is_null($type) || is_null($value) || $value instanceof Raw || $value instanceof Express) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$param = '';
|
||||
if (is_array($type)) {
|
||||
[$type, $param] = $type;
|
||||
} elseif (str_contains($type, ':')) {
|
||||
[$type, $param] = explode(':', $type, 2);
|
||||
}
|
||||
|
||||
$typeTransform = static function (string $type, $value, $model, $param) {
|
||||
if (class_exists($type) && !($value instanceof $type)) {
|
||||
if (is_subclass_of($type, Typeable::class)) {
|
||||
$value = $type::from($value, $model);
|
||||
if ($param && $value instanceof DateTime) {
|
||||
// 设置时间输出格式
|
||||
$value->setFormat($param);
|
||||
}
|
||||
} elseif (is_subclass_of($type, FieldTypeTransform::class)) {
|
||||
$value = $type::get($value, $model);
|
||||
} elseif (is_subclass_of($type, BackedEnum::class)) {
|
||||
$value = $type::from($value);
|
||||
if (is_subclass_of($type, EnumTransform::class)) {
|
||||
$value = $value->value();
|
||||
} elseif ($model->getOption('enumReadName')) {
|
||||
$method = $model->getOption('enumReadName');
|
||||
$value = is_string($method) ? $value->$method() : $value->name;
|
||||
}
|
||||
} else {
|
||||
// 对象类型
|
||||
$value = new $type($value);
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
};
|
||||
|
||||
return match ($type) {
|
||||
'string','bigint'=> (string) $value,
|
||||
'int','integer' => (int) $value,
|
||||
'float' => empty($param) ? (float) $value : (float) number_format($value, (int) $param, '.', ''),
|
||||
'bool','boolean' => (bool) $value,
|
||||
'array' => empty($value) ? [] : (is_array($value) ? $value : json_decode($value, true)),
|
||||
'object' => empty($value) ? new \stdClass() : (is_string($value) ? json_decode($value) : json_decode(json_encode($value, JSON_FORCE_OBJECT))),
|
||||
'json' => $typeTransform(Json::class, $value, $this, $param),
|
||||
'date' => $typeTransform(Date::class, $value, $this, $param),
|
||||
'datetime' => $typeTransform(DateTime::class, $value, $this, $param),
|
||||
'timestamp' => $typeTransform(DateTime::class, $value, $this, $param),
|
||||
default => $typeTransform($type, $value, $this, $param),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据写入 类型转换.
|
||||
*
|
||||
* @param mixed $value 值
|
||||
* @param string|array|null $type 要转换的类型
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function writeTransform($value, string | array | null $type)
|
||||
{
|
||||
if (is_null($type) || is_null($value) || $value instanceof Raw || $value instanceof Express) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$param = '';
|
||||
if (is_array($type)) {
|
||||
[$type, $param] = $type;
|
||||
} elseif (str_contains($type, ':')) {
|
||||
[$type, $param] = explode(':', $type, 2);
|
||||
}
|
||||
|
||||
$typeTransform = static function (string $type, $value, $model) {
|
||||
if (class_exists($type)) {
|
||||
if (is_subclass_of($type, Typeable::class)) {
|
||||
$value = $value->value();
|
||||
} elseif (is_subclass_of($type, FieldTypeTransform::class)) {
|
||||
$value = $type::set($value, $model);
|
||||
} elseif ($value instanceof BackedEnum) {
|
||||
$value = $value->value;
|
||||
} elseif ($value instanceof Stringable) {
|
||||
$value = $value->__toString();
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
};
|
||||
|
||||
return match ($type) {
|
||||
'string','bigint' => (string) $value,
|
||||
'int', 'integer' => (int) $value,
|
||||
'float' => empty($param) ? (float) $value : (float) number_format($value, (int) $param, '.', ''),
|
||||
'bool', 'boolean' => $value ? 1 : 0,
|
||||
'object' => is_object($value) ? json_encode($value, JSON_FORCE_OBJECT) : $value,
|
||||
'array' => json_encode((array) $value, JSON_UNESCAPED_UNICODE),
|
||||
'json' => $typeTransform(Json::class, $value, $this),
|
||||
'date' => $typeTransform(Date::class, $value, $this),
|
||||
'datetime' => $typeTransform(DateTime::class, $value, $this),
|
||||
'timestamp' => $typeTransform(DateTime::class, $value, $this),
|
||||
default => $typeTransform($type, $value, $this),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新对象原始数据(为当前数据).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function refreshOrigin()
|
||||
{
|
||||
return $this->setOption('origin', $this->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主键值
|
||||
*
|
||||
* @param int|string $value 值
|
||||
* @return void
|
||||
*/
|
||||
public function setKey($value)
|
||||
{
|
||||
$pk = $this->getPk();
|
||||
|
||||
if (is_string($pk)) {
|
||||
$this->set($pk, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主键值
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
$pk = $this->getPk();
|
||||
if (is_null($pk)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_string($pk)) {
|
||||
return $this->get($pk);
|
||||
}
|
||||
|
||||
foreach ($pk as $name) {
|
||||
$data[$name] = $this->get($name);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置模型数据.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function data(array $data)
|
||||
{
|
||||
$this->initializeData($data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型实际数据.
|
||||
*
|
||||
* @param string|null $name 字段名
|
||||
* @return mixed
|
||||
*/
|
||||
public function getData(?string $name = null)
|
||||
{
|
||||
if ($name) {
|
||||
$name = $this->getRealFieldName($name);
|
||||
return $this->getWeakData('data', $name);
|
||||
}
|
||||
return $this->getOption('data', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断模型是否存在数据字段.
|
||||
*
|
||||
* @param string $name 字段名
|
||||
* @return bool
|
||||
*/
|
||||
public function hasData(string $name): bool
|
||||
{
|
||||
return $this->hasGetAttr($name) || array_key_exists($this->getMappingName($name), self::$weakMap[$this]['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据对象的实际值
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param mixed $value 值
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setData(string $name, $value)
|
||||
{
|
||||
$this->setWeakData('data', $name, $value);
|
||||
if ($this->getWeakData('get', $name)) {
|
||||
$this->setWeakData('get', $name, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空模型数据.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->setOption('data', []);
|
||||
$this->setOption('origin', []);
|
||||
$this->setOption('get', []);
|
||||
$this->setOption('relation', []);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始数据.
|
||||
*
|
||||
* @param string|null $name 字段名
|
||||
* @param bool $transform 是否自动类型转换
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOrigin(?string $name = null, bool $transfrom = false)
|
||||
{
|
||||
if ($name) {
|
||||
$name = $this->getRealFieldName($name);
|
||||
$result = $this->getWeakData('origin', $name);
|
||||
return $transfrom ? $this->writeTransform($result, $this->getFields($name)) : $result;
|
||||
}
|
||||
return $this->getOption('origin');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数据是否为空.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty($this->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断JSON数据是否为数组格式.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function isJsonAssoc(): bool|null
|
||||
{
|
||||
return $this->getOption('jsonAssoc', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置JSON数据格式.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function jsonAssoc(bool $assoc = true)
|
||||
{
|
||||
return $this->setOption('jsonAssoc', $assoc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据对象的值 并进行类型自动转换
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param mixed $value 值
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function set(string $name, $value)
|
||||
{
|
||||
$name = $this->getMappingName($name);
|
||||
$type = $this->getFields()[$name] ?? '';
|
||||
|
||||
if ($this->isExists() && in_array($name, $this->getOption('readonly'))) {
|
||||
// 只读属性不能赋值
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_null($value) && is_subclass_of($type, Model::class)) {
|
||||
// 关联数据为空 设置一个空模型
|
||||
$value = new $type();
|
||||
} elseif (!($value instanceof Model || $value instanceof Collection || $value instanceof FieldTypeTransform) && $type && !$this->hasSetAttr($name)) {
|
||||
// 类型自动转换
|
||||
$value = $this->readTransform($value, $type);
|
||||
}
|
||||
|
||||
$this->setData($name, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段是否定义修改器
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasSetAttr(string $name): bool
|
||||
{
|
||||
$attr = Str::studly($name);
|
||||
$method = 'set' . $attr . 'Attr';
|
||||
return method_exists($this, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段是否定义获取器
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasGetAttr(string $name): bool
|
||||
{
|
||||
$attr = Str::studly($name);
|
||||
$method = 'get' . $attr . 'Attr';
|
||||
return method_exists($this, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用修改器或类型自动转换处理数据(写入数据前自动调用)
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param mixed $value 值
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function setWithAttr(string $name, $value)
|
||||
{
|
||||
$attr = Str::studly($name);
|
||||
$method = 'set' . $attr . 'Attr';
|
||||
if (method_exists($this, $method)) {
|
||||
$value = $this->$method($value, $this->getData());
|
||||
} else {
|
||||
// 类型转换
|
||||
$value = $this->writeTransform($value, $this->getFields($name));
|
||||
}
|
||||
|
||||
if ($value instanceof Express) {
|
||||
// 处理运算表达式
|
||||
$step = $value->getStep();
|
||||
$origin = $this->getOrigin($name);
|
||||
$real = match ($value->getType()) {
|
||||
'+' => $origin + $step,
|
||||
'-' => $origin - $step,
|
||||
'*' => $origin * $step,
|
||||
'/' => $origin / $step,
|
||||
default => $origin,
|
||||
};
|
||||
$this->set($name, $real);
|
||||
} elseif (is_scalar($value)) {
|
||||
// 同步写入修改器或类型自动转换结果
|
||||
$this->set($name, $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据对象的值(支持使用获取器)
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param bool $attr 是否使用获取器
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $name, bool $attr = true)
|
||||
{
|
||||
$name = $this->getMappingName($name);
|
||||
if ($attr && $value = $this->getWeakData('get', $name)) {
|
||||
// 已经输出的数据直接返回
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (!array_key_exists($name, $this->getData()) && !array_key_exists($name, $this->getFields())) {
|
||||
// 动态获取关联数据
|
||||
$value = $this->getRelationData($name) ?: null;
|
||||
} else {
|
||||
$value = $this->getData($name);
|
||||
}
|
||||
|
||||
if ($attr) {
|
||||
// 通过获取器输出
|
||||
$value = $this->getWithAttr($name, $value, $this->getData());
|
||||
$this->setWeakData('get', $name, $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取映射字段
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getMappingName(string $name): string
|
||||
{
|
||||
$mapping = $this->getOption('mapping');
|
||||
return array_search($name, $mapping) ?: $this->getRealFieldName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据对象的值(经过获取器和类型转换)
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param mixed $value 值
|
||||
* @param array $data 所有数据
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function getWithAttr(string $name, $value, array $data = [])
|
||||
{
|
||||
$attr = Str::studly($name);
|
||||
$method = 'get' . $attr . 'Attr';
|
||||
$withAttr = $this->getWeakData('withAttr', $name);
|
||||
if ($withAttr) {
|
||||
// 动态获取器
|
||||
$value = $withAttr($value, $data, $this);
|
||||
} elseif (method_exists($this, $method)) {
|
||||
// 获取器
|
||||
$value = $this->$method($value, $data);
|
||||
} elseif ($value instanceof Typeable || is_subclass_of($value, EnumTransform::class, false)) {
|
||||
// 类型自动转换
|
||||
if ($value instanceof Json) {
|
||||
// JSON数据转换
|
||||
$value = $this->readTransformJson($name, $value);
|
||||
} else {
|
||||
$value = $value->value();
|
||||
}
|
||||
} elseif (is_int($value) && $this->isTimeAttr($name) && false != $this->getDateFormat()) {
|
||||
// 兼容数字类型时间字段的自动转换输出
|
||||
$value = (new \DateTime())
|
||||
->setTimestamp($value)
|
||||
->format($this->getDateFormat());
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理JSON数据对象的值
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param Json $value 值
|
||||
*
|
||||
* @return array|object
|
||||
*/
|
||||
protected function readTransformJson(string $name, Json $value)
|
||||
{
|
||||
// JSON数据转换
|
||||
$value = $value->value();
|
||||
if ($value) {
|
||||
foreach ($value as $key => &$val) {
|
||||
$type = $this->getFields($name . '->' . $key);
|
||||
if ($type) {
|
||||
// 定义了JSON属性类型自动转换
|
||||
$val = $this->readTransform($val, $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function isTimeAttr(string $name): bool
|
||||
{
|
||||
return in_array($name, [$this->getOption('createTime'), $this->getOption('updateTime'), $this->getOption('deleteTime')]) || in_array($name, $this->getOption('timestampField', []));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用获取器获取数据对象的值
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttr(string $name)
|
||||
{
|
||||
return $this->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据对象的值 并进行类型自动转换
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param mixed $value 值
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttr(string $name, $value)
|
||||
{
|
||||
return $this->set($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据是否存在.
|
||||
*
|
||||
* @param bool $exists
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function exists(bool $exists = true)
|
||||
{
|
||||
return $this->setOption('exists', $exists);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数据是否存在数据库.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isExists(): bool
|
||||
{
|
||||
return $this->getOption('exists', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置枚举类型自动读取数据方式
|
||||
* true 表示使用name值返回
|
||||
* 字符串 表示使用枚举类的方法返回
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withEnumRead(bool | string $method = true)
|
||||
{
|
||||
return $this->setOption('enumReadName', $method);
|
||||
}
|
||||
}
|
||||
138
vendor/topthink/think-orm/src/model/concern/AutoWriteData.php
vendored
Normal file
138
vendor/topthink/think-orm/src/model/concern/AutoWriteData.php
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\concern;
|
||||
|
||||
use Closure;
|
||||
use Stringable;
|
||||
use think\model\contract\Typeable;
|
||||
use think\model\type\DateTime;
|
||||
|
||||
/**
|
||||
* 自动写入数据.
|
||||
*/
|
||||
trait AutoWriteData
|
||||
{
|
||||
/**
|
||||
* 字段自动写入.
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @param bool $update 是否更新
|
||||
* @param array $allow 允许字段
|
||||
* @return void
|
||||
*/
|
||||
protected function autoWriteData(array &$data, bool $update, array $allow = [])
|
||||
{
|
||||
// 数据写入前置检查
|
||||
$this->checkData($data, $update);
|
||||
|
||||
// 自动时间戳处理
|
||||
$this->autoDateTime($data, $update, $allow);
|
||||
|
||||
$auto = $this->getOption($update ? 'update' : 'insert', []);
|
||||
foreach ($auto as $name => $val) {
|
||||
$field = is_string($name) ? $name : $val;
|
||||
if (!isset($data[$field])) {
|
||||
if ($val instanceof Closure) {
|
||||
$value = $val($this);
|
||||
} else {
|
||||
$value = is_string($name) ? $val : $this->setWithAttr($field, null, $data);
|
||||
}
|
||||
$data[$field] = $value;
|
||||
$this->setData($field, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间字段自动写入.
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @param bool $update 是否更新
|
||||
* @param array $allow 允许字段
|
||||
* @return void
|
||||
*/
|
||||
protected function autoDateTime(array &$data, bool $update, array $allow)
|
||||
{
|
||||
$autoDateTime = $this->getOption('autoWriteTimestamp', true);
|
||||
if ($autoDateTime) {
|
||||
$dateTimeFields = [$this->getOption('updateTime')];
|
||||
if (!$update) {
|
||||
array_unshift($dateTimeFields, $this->getOption('createTime'));
|
||||
}
|
||||
|
||||
foreach ($dateTimeFields as $field) {
|
||||
if (is_string($field) && (empty($allow) || in_array($field, $allow))) {
|
||||
$data[$field] = $this->getDateTime($field);
|
||||
$this->setData($field, $this->readTransform($data[$field], $this->getFields($field)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getAutoTimeFields(): array
|
||||
{
|
||||
return [$this->getOption('createTime'), $this->getOption('updateTime')];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间.
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getDateTime(string $field)
|
||||
{
|
||||
$type = $this->getFields($field) ?? 'string';
|
||||
if (in_array($type, ['int', 'integer'])) {
|
||||
return time();
|
||||
} elseif (is_subclass_of($type, Typeable::class)) {
|
||||
return $type::from('now', $this)->format('Y-m-d H:i:s.u');
|
||||
} elseif (str_contains($type, '\\')) {
|
||||
$obj = new $type();
|
||||
if ($obj instanceof Stringable) {
|
||||
return $obj->__toString();
|
||||
} else {
|
||||
return (string) $obj;
|
||||
}
|
||||
} else {
|
||||
return DateTime::from('now', $this)->format('Y-m-d H:i:s.u');
|
||||
}
|
||||
}
|
||||
|
||||
public function getAutoWriteTimestamp()
|
||||
{
|
||||
return $this->getOption('autoWriteTimestamp');
|
||||
}
|
||||
|
||||
public function isAutoWriteTimestamp(string | bool $auto)
|
||||
{
|
||||
return $this->setOption('autoWriteTimestamp', $auto);
|
||||
}
|
||||
|
||||
public function getDateFormat()
|
||||
{
|
||||
return $this->getOption('dateFormat');
|
||||
}
|
||||
|
||||
public function setDateFormat(string | bool $format)
|
||||
{
|
||||
return $this->setOption('dateFormat', $format);
|
||||
}
|
||||
|
||||
public function setTimeField($createTime, $updateTime)
|
||||
{
|
||||
$this->setOption('createTime', $createTime);
|
||||
$this->setOption('updateTime', $updateTime);
|
||||
}
|
||||
}
|
||||
208
vendor/topthink/think-orm/src/model/concern/Conversion.php
vendored
Normal file
208
vendor/topthink/think-orm/src/model/concern/Conversion.php
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\concern;
|
||||
|
||||
use Closure;
|
||||
use think\helper\Str;
|
||||
use think\model\Collection;
|
||||
use think\model\contract\Modelable;
|
||||
|
||||
/**
|
||||
* 模型数据转换处理.
|
||||
*/
|
||||
trait Conversion
|
||||
{
|
||||
/**
|
||||
* 设置需要附加的输出属性.
|
||||
*
|
||||
* @param array $append 属性列表
|
||||
* @param bool $merge 是否合并
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function append(array $append, bool $merge = false)
|
||||
{
|
||||
return $this->setOption('append', $merge ? array_merge($this->getOption('append'), $append) : $append);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要隐藏的输出属性.
|
||||
*
|
||||
* @param array $hidden 属性列表
|
||||
* @param bool $merge 是否合并
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function hidden(array $hidden, bool $merge = false)
|
||||
{
|
||||
return $this->setOption('hidden', $merge ? array_merge($this->getOption('hidden'), $hidden) : $hidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需要输出的属性.
|
||||
*
|
||||
* @param array $visible
|
||||
* @param bool $merge 是否合并
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function visible(array $visible, bool $merge = false)
|
||||
{
|
||||
return $this->setOption('visible', $merge ? array_merge($this->getOption('visible'), $visible) : $visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性的映射输出.
|
||||
*
|
||||
* @param array $map
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function mapping(array $map)
|
||||
{
|
||||
return $this->setOption('mapping', $map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输出场景.
|
||||
*
|
||||
* @param string $scene
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function scene(string $scene)
|
||||
{
|
||||
$method = 'scene' . Str::studly($scene);
|
||||
if (method_exists($this, $method)) {
|
||||
call_user_func([$this, $method]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型数据转数组.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$mapping = $this->getOption('mapping');
|
||||
foreach (['visible', 'hidden', 'append'] as $convert) {
|
||||
${$convert} = $this->getOption($convert);
|
||||
foreach (${$convert} as $key => $val) {
|
||||
if (is_string($key)) {
|
||||
$relation[$key][$convert] = $val;
|
||||
unset(${$convert}[$key]);
|
||||
} elseif (str_contains($val, '.')) {
|
||||
[$relName, $name] = explode('.', $val);
|
||||
$relation[$relName][$convert][] = $name;
|
||||
unset(${$convert}[$key]);
|
||||
} elseif ($item = array_search($val, $mapping)) {
|
||||
${$convert}[$key] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
$data = $this->getData();
|
||||
$allow = array_diff($visible ?: array_keys($data), $hidden);
|
||||
|
||||
$item = [];
|
||||
foreach ($data as $name => $val) {
|
||||
if ($val instanceof Modelable || $val instanceof Collection) {
|
||||
if (in_array($name, $hidden)) {
|
||||
// 隐藏关联属性
|
||||
unset($item[$name]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($relation[$name])) {
|
||||
// 处理关联数据输出
|
||||
foreach ($relation[$name] as $key => $attr) {
|
||||
$val->$key($attr);
|
||||
}
|
||||
}
|
||||
$item[$name] = $val->toArray();
|
||||
} elseif (empty($allow) || in_array($name, $allow)) {
|
||||
// 通过获取器输出
|
||||
$item[$name] = $this->getWithAttr($name, $val, $data);
|
||||
}
|
||||
|
||||
if (array_key_exists($name, $item) && isset($mapping[$name])) {
|
||||
// 检查字段映射
|
||||
$item[$mapping[$name]] = $item[$name];
|
||||
unset($item[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
// 输出额外属性 必须定义获取器
|
||||
foreach ($this->getOption('append') as $key => $field) {
|
||||
if (is_numeric($key)) {
|
||||
$item[$field] = $this->get($field);
|
||||
} else {
|
||||
// 追加关联属性
|
||||
$relation = $this->getRelationData($key, false);
|
||||
foreach((array) $field as $key => $name) {
|
||||
if (is_numeric($key)) {
|
||||
$item[$name] = $relation?->get($name);
|
||||
} else {
|
||||
$item[$name] = $relation?->get($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->getOption('convertNameToCamel')) {
|
||||
foreach ($item as $key => $val) {
|
||||
$name = Str::camel($key);
|
||||
if ($name !== $key) {
|
||||
$item[$name] = $val;
|
||||
unset($item[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型数据转Json.
|
||||
*
|
||||
* @param int $options json参数
|
||||
* @return string
|
||||
*/
|
||||
public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
|
||||
{
|
||||
return json_encode($this->toArray(), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为数据集对象
|
||||
*
|
||||
* @param array|Collection $collection 数据集
|
||||
* @param string|null $resultSetType 数据集类
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function toCollection(iterable $collection = [], ?string $resultSetType = null): Collection
|
||||
{
|
||||
$resultSetType = $resultSetType ?: $this->getOption('resultSetType');
|
||||
|
||||
if ($resultSetType && str_contains($resultSetType, '\\')) {
|
||||
$collection = new $resultSetType($collection);
|
||||
} else {
|
||||
$collection = new Collection($collection);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
202
vendor/topthink/think-orm/src/model/concern/DbConnect.php
vendored
Normal file
202
vendor/topthink/think-orm/src/model/concern/DbConnect.php
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\concern;
|
||||
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 数据库连接.
|
||||
*/
|
||||
trait DbConnect
|
||||
{
|
||||
/**
|
||||
* 设置Db对象实例.(用于兼容)
|
||||
*/
|
||||
public static function setDb($db)
|
||||
{}
|
||||
|
||||
/**
|
||||
* 获取Db对象实例.
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
$db = $this->initDb()->newQuery($this->getOption('query'));
|
||||
|
||||
if ($this->getOption('cache')) {
|
||||
[$key, $expire, $tag] = $this->getOption('cache');
|
||||
$db->cache($key, $expire, $tag);
|
||||
}
|
||||
|
||||
return $db->schema($this->getOption('schema'))
|
||||
->pk($this->getPk())
|
||||
->suffix($this->getOption('suffix'))
|
||||
->setKey($this->getKey())
|
||||
->replace($this->getOption('replace', false))
|
||||
->model($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库连接对象.
|
||||
* @return Query
|
||||
*/
|
||||
private function initDb()
|
||||
{
|
||||
$connection = $this->getOption('connection');
|
||||
if ($this->getOption('db')) {
|
||||
$db = $this->getOption('db')->connect($connection);
|
||||
} else {
|
||||
$db = Db::connect($connection);
|
||||
}
|
||||
|
||||
$db = $db->name($this->getName());
|
||||
if ($this->getOption('table')) {
|
||||
$db->table($this->getOption('table'));
|
||||
} else {
|
||||
$db->suffix($this->getOption('suffix'));
|
||||
}
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据表字段类型列表(或某个字段的类型).
|
||||
*
|
||||
* @param string|null $field 字段名
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
protected function getFields(?string $field = null)
|
||||
{
|
||||
$schema = $this->getOption('schema');
|
||||
if (empty($schema)) {
|
||||
// 获取数据表信息
|
||||
$db = $this->initDb();
|
||||
$fields = $db->getFieldsType();
|
||||
$schema = array_merge($fields, $this->getOption('type', []));
|
||||
// 获取主键
|
||||
if (!$this->getOption('pk')) {
|
||||
$this->setOption('pk', $db->getPk());
|
||||
}
|
||||
|
||||
$this->setOption('schema', $schema);
|
||||
}
|
||||
|
||||
if ($field) {
|
||||
return $schema[$field] ?? null;
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增数据是否使用Replace.
|
||||
*
|
||||
* @param bool $replace
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function replace(bool $replace = true)
|
||||
{
|
||||
return $this->setOption('replace', $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前模型的数据表后缀
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSuffix(): string
|
||||
{
|
||||
return $this->getOption('suffix', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前模型数据表的后缀
|
||||
*
|
||||
* @param string $suffix 数据表后缀
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSuffix(string $suffix)
|
||||
{
|
||||
$this->setOption('suffix', $suffix);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建实体模型查询.
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @return void
|
||||
*/
|
||||
protected function query(Query $query) {}
|
||||
|
||||
/**
|
||||
* 获取查询对象
|
||||
*
|
||||
* @param array|null $scope 设置不使用的全局查询范围
|
||||
* @return Query
|
||||
*/
|
||||
public function db(array | null $scope = []): Query
|
||||
{
|
||||
$query = $this->getQuery();
|
||||
// 全局查询范围
|
||||
if (is_array($scope)) {
|
||||
$globalScope = array_diff($this->getOption('globalScope', []), $scope);
|
||||
$query->scope($globalScope);
|
||||
}
|
||||
// 执行扩展查询
|
||||
$this->query($query);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置不使用的全局查询范围.
|
||||
*
|
||||
* @param array $scope 不启用的全局查询范围
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public static function withoutGlobalScope(?array $scope = null): Query
|
||||
{
|
||||
$model = new static();
|
||||
|
||||
return $model->db($scope);
|
||||
}
|
||||
|
||||
public static function __callStatic($method, $args)
|
||||
{
|
||||
$model = new static();
|
||||
$db = $model->db();
|
||||
|
||||
if (!empty(self::$weakMap[$model]['autoRelation'])) {
|
||||
// 自动获取关联数据
|
||||
$db->with(self::$weakMap[$model]['autoRelation']);
|
||||
}
|
||||
|
||||
return call_user_func_array([$db, $method], $args);
|
||||
}
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if ($this->isExists() && strtolower($method) == 'withattr') {
|
||||
return call_user_func_array([$this, 'withFieldAttr'], $args);
|
||||
}
|
||||
|
||||
return call_user_func_array([$this->db(), $method], $args);
|
||||
}
|
||||
}
|
||||
85
vendor/topthink/think-orm/src/model/concern/ModelEvent.php
vendored
Normal file
85
vendor/topthink/think-orm/src/model/concern/ModelEvent.php
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\concern;
|
||||
|
||||
use ReflectionClass;
|
||||
use think\db\exception\ModelEventException;
|
||||
use think\helper\Str;
|
||||
|
||||
/**
|
||||
* 模型事件处理.
|
||||
*/
|
||||
trait ModelEvent
|
||||
{
|
||||
/**
|
||||
* 设置Event对象 (用于兼容)
|
||||
*
|
||||
* @param object $event Event对象
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setEvent($event)
|
||||
{}
|
||||
|
||||
/**
|
||||
* 当前操作的事件响应.
|
||||
*
|
||||
* @param bool $event 是否需要事件响应
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withEvent(bool $event)
|
||||
{
|
||||
return $this->setOption('withEvent', $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件.
|
||||
*
|
||||
* @param string $event 事件名
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function trigger(string $event): bool
|
||||
{
|
||||
if (!$this->getOption('withEvent', true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$method = 'on' . Str::studly($event);
|
||||
$obj = $this->getOption('event');
|
||||
$obser = $this->getOption('eventObserver');
|
||||
try {
|
||||
if ($obser) {
|
||||
$reflect = new ReflectionClass($obser);
|
||||
$observer = $reflect->newinstance();
|
||||
} else {
|
||||
$observer = $this;
|
||||
}
|
||||
|
||||
if (method_exists($observer, $method)) {
|
||||
$result = $this->invoke([$observer, $method], [$this]);
|
||||
} elseif (is_object($obj) && method_exists($obj, 'trigger')) {
|
||||
$result = $obj->trigger(static::class . '.' . $event, $this);
|
||||
$result = empty($result) ? true : end($result);
|
||||
} else {
|
||||
$result = true;
|
||||
}
|
||||
|
||||
return false !== $result;
|
||||
} catch (ModelEventException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
vendor/topthink/think-orm/src/model/concern/OptimLock.php
vendored
Normal file
87
vendor/topthink/think-orm/src/model/concern/OptimLock.php
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace think\model\concern;
|
||||
|
||||
use think\db\exception\DbException as Exception;
|
||||
|
||||
/**
|
||||
* 乐观锁
|
||||
*/
|
||||
trait OptimLock
|
||||
{
|
||||
protected function getOptimLockField()
|
||||
{
|
||||
return $this->getOption('optimLock') ?? 'lock_version';
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据检查.
|
||||
* @param array $data 数据
|
||||
* @param bool $isUpdate 是否更新
|
||||
* @return void
|
||||
*/
|
||||
protected function checkData(array &$data, bool $isUpdate): void
|
||||
{
|
||||
$isUpdate ? $this->updateLockVersion($data) : $this->recordLockVersion($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录乐观锁
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @return void
|
||||
*/
|
||||
protected function recordLockVersion(array &$data): void
|
||||
{
|
||||
$optimLock = $this->getOptimLockField();
|
||||
|
||||
$this->setData($optimLock, 0);
|
||||
$data[$optimLock] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新乐观锁
|
||||
*
|
||||
* @param array $data 数据
|
||||
* @return void
|
||||
*/
|
||||
protected function updateLockVersion(array &$data): void
|
||||
{
|
||||
$optimLock = $this->getOptimLockField();
|
||||
$lockVer = $this->getOrigin($optimLock);
|
||||
|
||||
$this->setData($optimLock, $lockVer + 1);
|
||||
$data[$optimLock] = $lockVer + 1;
|
||||
}
|
||||
|
||||
public function getDbWhere($where)
|
||||
{
|
||||
$db = $this->db();
|
||||
// 检查条件
|
||||
if (!empty($where)) {
|
||||
$db->where($where);
|
||||
}
|
||||
$optimLock = $this->getOptimLockField();
|
||||
$lockVer = $this->getOrigin($optimLock);
|
||||
$pk = $this->getPk();
|
||||
if (is_array($pk)) {
|
||||
$db->where($this->getKey());
|
||||
} else {
|
||||
$db->where($pk, '=', $this->getKey());
|
||||
}
|
||||
$db->where($optimLock, '=', $lockVer);
|
||||
|
||||
return $db;
|
||||
}
|
||||
}
|
||||
902
vendor/topthink/think-orm/src/model/concern/RelationShip.php
vendored
Normal file
902
vendor/topthink/think-orm/src/model/concern/RelationShip.php
vendored
Normal file
@@ -0,0 +1,902 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\concern;
|
||||
|
||||
use Closure;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\db\exception\InvalidArgumentException;
|
||||
use think\helper\Str;
|
||||
use think\model\Collection;
|
||||
use think\model\contract\Modelable as 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\HasOneThrough;
|
||||
use think\model\relation\MorphMany;
|
||||
use think\model\relation\MorphOne;
|
||||
use think\model\relation\MorphTo;
|
||||
use think\model\relation\MorphToMany;
|
||||
use think\model\relation\OneToOne;
|
||||
use think\model\View;
|
||||
|
||||
/**
|
||||
* 实体模型关联处理.
|
||||
*/
|
||||
trait RelationShip
|
||||
{
|
||||
/**
|
||||
* 关联数据写入或删除.
|
||||
*
|
||||
* @param array $relation 关联
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function together(array $relation)
|
||||
{
|
||||
return $this->setOption('together', $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置关联JOIN数据.
|
||||
*
|
||||
* @param array $relations 关联数据
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function parseRelationData(array $relations)
|
||||
{
|
||||
foreach ($relations as $relation => $val) {
|
||||
$relation = $this->getRealFieldName($relation);
|
||||
$type = $this->getFields($relation);
|
||||
$bind = $this->getBindAttr($this->getOption('bindAttr'), $relation);
|
||||
if (!empty($bind)) {
|
||||
// 绑定关联属性
|
||||
$this->bindRelationAttr($val, $bind, $relation);
|
||||
} elseif (is_subclass_of($type, Model::class)) {
|
||||
// 明确类型直接设置关联属性
|
||||
$this->setRelation($relation, new $type($val));
|
||||
} else {
|
||||
// 寄存关联数据
|
||||
$this->setTempRelation($relation, $val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 寄存关联数据.
|
||||
*
|
||||
* @param string $relation 关联属性
|
||||
* @param array $data 关联数据
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setTempRelation(string $relation, array $data)
|
||||
{
|
||||
$this->setWeakData('relation', $relation, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取寄存的关联数据.
|
||||
*
|
||||
* @param string $relation 关联属性
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRelation(string $relation): array
|
||||
{
|
||||
return $this->getWeakData('relation', $relation, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入模型关联数据(一对一).
|
||||
*
|
||||
* @param array $relations 数据
|
||||
* @param bool $isUpdate 是否更新
|
||||
* @return void
|
||||
*/
|
||||
private function relationSave(array $relations = [], bool $isUpdate = true)
|
||||
{
|
||||
$together = $this->getOption('together');
|
||||
foreach ($together as $key => $name) {
|
||||
if (is_numeric($key) && isset($relations[$name])) {
|
||||
// 支持关联写入或更新
|
||||
$method = Str::camel($name);
|
||||
$relation = $relations[$name];
|
||||
$data = null;
|
||||
if ($relation instanceof Model) {
|
||||
if ($isUpdate) {
|
||||
$relation->save();
|
||||
} else {
|
||||
$data = $this->$method()->save($relation);
|
||||
}
|
||||
} else {
|
||||
// 数组或数据集
|
||||
$relationModel = $this->$method();
|
||||
if ($relationModel instanceof OneToOne) {
|
||||
$data = $relationModel->save($relation);
|
||||
} elseif ($relationModel instanceof HasMany || $relationModel instanceof MorphMany) {
|
||||
$data = $relationModel->saveAll($relation);
|
||||
if ($data) {
|
||||
$data = $this->toCollection($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($data) {
|
||||
// 重新赋值关联数据
|
||||
$this->set($name, $data);
|
||||
}
|
||||
} elseif (is_array($name)) {
|
||||
// 关联写入
|
||||
$data = [];
|
||||
if (array_is_list($name)) {
|
||||
// 绑定关联属性
|
||||
foreach($name as $field) {
|
||||
if ($this->getData($field)) {
|
||||
$data[$field] = $this->getData($field);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$data = $name;
|
||||
}
|
||||
$method = Str::camel($key);
|
||||
$this->$method()->save($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除模型关联数据(一对一).
|
||||
*
|
||||
* @param array $relations 数据
|
||||
* @return void
|
||||
*/
|
||||
private function relationDelete(array $relations = [])
|
||||
{
|
||||
foreach ($relations as $name => $relation) {
|
||||
if ($relation && in_array($name, $this->getOption('together'))) {
|
||||
$relation->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联数据
|
||||
*
|
||||
* @param string $name 名称
|
||||
* @param bool $set 是否设置为当前模型属性
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRelationData(string $name, bool $set = true)
|
||||
{
|
||||
$method = Str::camel($name);
|
||||
if (method_exists($this, $method) && !method_exists('think\Model', $method)) {
|
||||
$modelRelation = $this->$method();
|
||||
if ($modelRelation instanceof Relation) {
|
||||
$value = $modelRelation->getRelation();
|
||||
if ($set) {
|
||||
$this->setData($name, $value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在关联
|
||||
*
|
||||
* @param string $name 名称
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRelation(string $name)
|
||||
{
|
||||
$method = Str::camel($name);
|
||||
if (method_exists($this, $method)) {
|
||||
$modelRelation = $this->$method();
|
||||
if ($modelRelation instanceof Relation) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getBindAttr($bind, $name)
|
||||
{
|
||||
return $bind[$name] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置关联绑定数据
|
||||
*
|
||||
* @param Model|array $model 关联对象
|
||||
* @param array $bind 绑定属性
|
||||
* @return void
|
||||
*/
|
||||
public function bindRelationAttr(Model | array $model, array $bind = [])
|
||||
{
|
||||
$data = is_array($model) ? $model : $model->toArray();
|
||||
foreach ($data as $key => $val) {
|
||||
if (isset($bind[$key])) {
|
||||
$this->set($bind[$key], $val);
|
||||
} elseif ($attr = array_search($key, $bind)) {
|
||||
$this->set(is_numeric($attr) ? $key : $attr, $val);
|
||||
} elseif (in_array($key, $bind)) {
|
||||
$this->set($key, $val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置关联数据.
|
||||
*
|
||||
* @param string $relation 关联属性
|
||||
* @param Model|Collection $data 关联数据
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setRelation(string $relation, $data)
|
||||
{
|
||||
$this->__set($relation, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询存在关联数据的模型.
|
||||
*
|
||||
* @param string $relation 关联方法名
|
||||
* @param mixed $operator 比较操作符
|
||||
* @param int $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public static function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', ?Query $query = null): Query
|
||||
{
|
||||
return (new static())
|
||||
->$relation()
|
||||
->has($operator, $count, $id, $joinType, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询不存在关联数据的模型.
|
||||
*
|
||||
* @param string $relation 关联方法名
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public static function hasNot(string $relation, string $id = '*', string $joinType = '', ?Query $query = null): Query
|
||||
{
|
||||
return (new static())
|
||||
->$relation()
|
||||
->has('=', 0, $id, $joinType, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string|array $relation 关联方法名 或 ['关联方法名', '关联表别名']
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public static function hasWhere(string|array $relation, $where = [], string $fields = '*', string $joinType = '', ?Query $query = null): Query
|
||||
{
|
||||
if (is_array($relation)) {
|
||||
[$relation, $alias] = $relation;
|
||||
}
|
||||
|
||||
return (new static())
|
||||
->$relation()
|
||||
->hasWhere($where, $fields, $joinType, $query, '', $alias ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string|array $relation 关联方法名 或 ['关联方法名', '关联表别名']
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public static function hasWhereOr(string|array $relation, $where = [], string $fields = '*', string $joinType = '', ?Query $query = null): Query
|
||||
{
|
||||
if (is_array($relation)) {
|
||||
[$relation, $alias] = $relation;
|
||||
}
|
||||
|
||||
return (new static())
|
||||
->$relation()
|
||||
->hasWhere($where, $fields, $joinType, $query, 'OR', $alias ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前模型的关联数据.
|
||||
*
|
||||
* @param array $relations 关联名
|
||||
* @param array $withRelationAttr 关联获取器
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function relationQuery(array $relations, array $withRelationAttr = []): void
|
||||
{
|
||||
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 (str_contains($relation, '.')) {
|
||||
[$relation, $subRelation] = explode('.', $relation, 2);
|
||||
}
|
||||
|
||||
$method = Str::camel($relation);
|
||||
$relationName = Str::snake($relation);
|
||||
$relationResult = $this->$method();
|
||||
|
||||
if (isset($withRelationAttr[$relationName])) {
|
||||
$relationResult->withAttr($withRelationAttr[$relationName]);
|
||||
}
|
||||
|
||||
$this->setRelation($relation, $relationResult->getRelation((array) $subRelation, $closure));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询 JOIN方式.
|
||||
*
|
||||
* @param Query $query Query对象
|
||||
* @param string $relation 关联方法名
|
||||
* @param mixed $field 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Closure $closure 闭包
|
||||
* @param bool $first
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function eagerly(Query $query, string $relation, $field, string $joinType = '', ?Closure $closure = null, bool $first = false): bool
|
||||
{
|
||||
$relation = Str::camel($relation);
|
||||
$class = $this->$relation();
|
||||
|
||||
if ($class instanceof OneToOne) {
|
||||
$class->eagerly($query, $relation, $field, $joinType, $closure, $first);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询 返回数据集.
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param array $relations 关联名
|
||||
* @param array $withRelationAttr 关联获取器
|
||||
* @param bool $join 是否为JOIN方式
|
||||
* @param mixed $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array $resultSet, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void
|
||||
{
|
||||
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 (str_contains($relation, '.')) {
|
||||
[$relation, $subRelation] = explode('.', $relation, 2);
|
||||
|
||||
$subRelation = [$subRelation];
|
||||
}
|
||||
|
||||
$relationName = $relation;
|
||||
$relation = Str::camel($relation);
|
||||
$relationResult = $this->$relation();
|
||||
|
||||
if (isset($withRelationAttr[$relationName])) {
|
||||
$relationResult->withAttr($withRelationAttr[$relationName]);
|
||||
}
|
||||
|
||||
if (is_scalar($cache)) {
|
||||
$relationCache = [$cache];
|
||||
} else {
|
||||
$relationCache = $cache[$relationName] ?? $cache;
|
||||
}
|
||||
|
||||
$relationResult->eagerlyResultSet($resultSet, $relationName, $subRelation, $closure, $relationCache, $join);
|
||||
}
|
||||
|
||||
// 刷新视图模型数据
|
||||
foreach ($resultSet as $result) {
|
||||
if ($result instanceof View) {
|
||||
$result->refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询 返回模型对象
|
||||
*
|
||||
* @param array $relations 关联
|
||||
* @param array $withRelationAttr 关联获取器
|
||||
* @param bool $join 是否为JOIN方式
|
||||
* @param mixed $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(Model $result, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void
|
||||
{
|
||||
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 (str_contains($relation, '.')) {
|
||||
[$relation, $subRelation] = explode('.', $relation, 2);
|
||||
|
||||
$subRelation = [$subRelation];
|
||||
}
|
||||
|
||||
$relationName = $relation;
|
||||
$relation = Str::camel($relation);
|
||||
$relationResult = $this->$relation();
|
||||
|
||||
if (isset($withRelationAttr[$relationName])) {
|
||||
$relationResult->withAttr($withRelationAttr[$relationName]);
|
||||
}
|
||||
|
||||
if (is_scalar($cache)) {
|
||||
$relationCache = [$cache];
|
||||
} else {
|
||||
$relationCache = $cache[$relationName] ?? [];
|
||||
}
|
||||
|
||||
$relationResult->eagerlyResult($result, $relationName, $subRelation, $closure, $relationCache, $join);
|
||||
}
|
||||
|
||||
if ($result instanceof View) {
|
||||
// 刷新视图模型数据
|
||||
$result->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定(一对一)关联属性到当前模型.
|
||||
*
|
||||
* @param string $relation 关联名称
|
||||
* @param array $attrs 绑定属性
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function bindAttr(string $relation, array $attrs = [])
|
||||
{
|
||||
$relation = $this->__get($relation);
|
||||
|
||||
foreach ($attrs as $key => $attr) {
|
||||
if (is_numeric($key)) {
|
||||
if (!is_string($attr)) {
|
||||
throw new InvalidArgumentException('bind attr must be string:' . $key);
|
||||
}
|
||||
|
||||
$key = $attr;
|
||||
}
|
||||
|
||||
if (null !== $this->getOrigin($key)) {
|
||||
throw new Exception('bind attr has exists:' . $key);
|
||||
}
|
||||
|
||||
if ($attr instanceof Closure) {
|
||||
$value = $attr($relation, $key, $this);
|
||||
} else {
|
||||
$value = $relation?->get($attr);
|
||||
}
|
||||
|
||||
$this->set($key, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param array $relations 关联名
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param bool $useSubQuery 子查询
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function relationCount(Query $query, array $relations, string $aggregate = 'sum', string $field = 'id', bool $useSubQuery = true): void
|
||||
{
|
||||
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 = Str::camel($relation);
|
||||
|
||||
if ($useSubQuery) {
|
||||
$count = $this->$relation()->getRelationCountQuery($closure, $aggregate, $field, $name);
|
||||
} else {
|
||||
$count = $this->$relation()->relationCount($this, $closure, $aggregate, $field, $name);
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
$name = Str::snake($relation) . '_' . $aggregate;
|
||||
}
|
||||
|
||||
if ($useSubQuery) {
|
||||
$query->field(['(' . $count . ')' => $name]);
|
||||
} else {
|
||||
$this->set($name, $count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HAS ONE 关联定义.
|
||||
*
|
||||
* @param string $model 模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $localKey 当前主键
|
||||
*
|
||||
* @return HasOne
|
||||
*/
|
||||
public function hasOne(string $model, string $foreignKey = '', string $localKey = ''): HasOne
|
||||
{
|
||||
// 记录当前关联信息
|
||||
$model = $this->parseRelationModel($model);
|
||||
$localKey = $localKey ?: $this->getPk();
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey($this->getName());
|
||||
|
||||
return new HasOne($this, $model, $foreignKey, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* BELONGS TO 关联定义.
|
||||
*
|
||||
* @param string $model 模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $localKey 关联主键
|
||||
*
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function belongsTo(string $model, string $foreignKey = '', string $localKey = ''): BelongsTo
|
||||
{
|
||||
// 记录当前关联信息
|
||||
$model = $this->parseRelationModel($model);
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey((new $model())->getName());
|
||||
$localKey = $localKey ?: (new $model())->getPk();
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
$relation = Str::snake($trace[1]['function']);
|
||||
|
||||
return new BelongsTo($this, $model, $foreignKey, $localKey, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* HAS MANY 关联定义.
|
||||
*
|
||||
* @param string $model 模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $localKey 当前主键
|
||||
*
|
||||
* @return HasMany
|
||||
*/
|
||||
public function hasMany(string $model, string $foreignKey = '', string $localKey = ''): HasMany
|
||||
{
|
||||
// 记录当前关联信息
|
||||
$model = $this->parseRelationModel($model);
|
||||
$localKey = $localKey ?: $this->getPk();
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey($this->getName());
|
||||
|
||||
return new HasMany($this, $model, $foreignKey, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* HAS MANY 远程关联定义.
|
||||
*
|
||||
* @param string $model 模型名
|
||||
* @param string $through 中间模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $throughKey 关联外键
|
||||
* @param string $localKey 当前主键
|
||||
* @param string $throughPk 中间表主键
|
||||
*
|
||||
* @return HasManyThrough
|
||||
*/
|
||||
public function hasManyThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasManyThrough
|
||||
{
|
||||
// 记录当前关联信息
|
||||
$model = $this->parseRelationModel($model);
|
||||
$through = $this->parseRelationModel($through);
|
||||
$localKey = $localKey ?: $this->getPk();
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey($this->getName());
|
||||
$throughKey = $throughKey ?: $this->getForeignKey((new $through())->getName());
|
||||
$throughPk = $throughPk ?: (new $through())->getPk();
|
||||
|
||||
return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk);
|
||||
}
|
||||
|
||||
/**
|
||||
* HAS ONE 远程关联定义.
|
||||
*
|
||||
* @param string $model 模型名
|
||||
* @param string $through 中间模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $throughKey 关联外键
|
||||
* @param string $localKey 当前主键
|
||||
* @param string $throughPk 中间表主键
|
||||
*
|
||||
* @return HasOneThrough
|
||||
*/
|
||||
public function hasOneThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasOneThrough
|
||||
{
|
||||
// 记录当前关联信息
|
||||
$model = $this->parseRelationModel($model);
|
||||
$through = $this->parseRelationModel($through);
|
||||
$localKey = $localKey ?: $this->getPk();
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey($this->getName());
|
||||
$throughKey = $throughKey ?: $this->getForeignKey((new $through())->getName());
|
||||
$throughPk = $throughPk ?: (new $through())->getPk();
|
||||
|
||||
return new HasOneThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk);
|
||||
}
|
||||
|
||||
/**
|
||||
* BELONGS TO MANY 关联定义.
|
||||
*
|
||||
* @param string $model 模型名
|
||||
* @param string $middle 中间表/模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $localKey 当前模型关联键
|
||||
*
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function belongsToMany(string $model, string $middle = '', string $foreignKey = '', string $localKey = ''): BelongsToMany
|
||||
{
|
||||
// 记录当前关联信息
|
||||
$model = $this->parseRelationModel($model);
|
||||
$name = Str::snake(class_basename($model));
|
||||
$middle = $middle ?: Str::snake($this->getName()) . '_' . $name;
|
||||
$foreignKey = $foreignKey ?: $name . '_id';
|
||||
$localKey = $localKey ?: $this->getForeignKey($this->getName());
|
||||
|
||||
return new BelongsToMany($this, $model, $middle, $foreignKey, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* MORPH One 关联定义.
|
||||
*
|
||||
* @param string $model 模型名
|
||||
* @param string|array $morph 多态字段信息
|
||||
* @param string $type 多态类型
|
||||
*
|
||||
* @return MorphOne
|
||||
*/
|
||||
public function morphOne(string $model, string | array | null $morph = null, string $type = ''): MorphOne
|
||||
{
|
||||
// 记录当前关联信息
|
||||
$model = $this->parseRelationModel($model);
|
||||
|
||||
if (is_null($morph)) {
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
$morph = Str::snake($trace[1]['function']);
|
||||
}
|
||||
|
||||
[$morphType, $foreignKey] = $this->parseMorph($morph);
|
||||
|
||||
$type = $type ?: get_class($this);
|
||||
|
||||
return new MorphOne($this, $model, $foreignKey, $morphType, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* MORPH MANY 关联定义.
|
||||
*
|
||||
* @param string $model 模型名
|
||||
* @param string|array $morph 多态字段信息
|
||||
* @param string $type 多态类型
|
||||
*
|
||||
* @return MorphMany
|
||||
*/
|
||||
public function morphMany(string $model, string | array | null $morph = null, string $type = ''): MorphMany
|
||||
{
|
||||
// 记录当前关联信息
|
||||
$model = $this->parseRelationModel($model);
|
||||
|
||||
if (is_null($morph)) {
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
$morph = Str::snake($trace[1]['function']);
|
||||
}
|
||||
|
||||
$type = $type ?: get_class($this);
|
||||
|
||||
[$morphType, $foreignKey] = $this->parseMorph($morph);
|
||||
|
||||
return new MorphMany($this, $model, $foreignKey, $morphType, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* MORPH TO 关联定义.
|
||||
*
|
||||
* @param string|array $morph 多态字段信息
|
||||
* @param array $alias 多态别名定义
|
||||
*
|
||||
* @return MorphTo
|
||||
*/
|
||||
public function morphTo(string | array | null $morph = null, array $alias = []): MorphTo
|
||||
{
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
$relation = Str::snake($trace[1]['function']);
|
||||
|
||||
if (is_null($morph)) {
|
||||
$morph = $relation;
|
||||
}
|
||||
|
||||
[$morphType, $foreignKey] = $this->parseMorph($morph);
|
||||
|
||||
return new MorphTo($this, $morphType, $foreignKey, $alias, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* MORPH TO MANY关联定义.
|
||||
*
|
||||
* @param string $model 模型名
|
||||
* @param string $middle 中间表名/模型名
|
||||
* @param string|array $morph 多态字段信息
|
||||
* @param string $localKey 当前模型关联键
|
||||
*
|
||||
* @return MorphToMany
|
||||
*/
|
||||
public function morphToMany(string $model, string $middle, string | array | null $morph = null, ?string $localKey = null): MorphToMany
|
||||
{
|
||||
if (is_null($morph)) {
|
||||
$morph = $middle;
|
||||
}
|
||||
|
||||
[$morphType, $morphKey] = $this->parseMorph($morph);
|
||||
|
||||
$model = $this->parseRelationModel($model);
|
||||
$name = Str::snake(class_basename($model));
|
||||
$localKey = $localKey ?: $this->getForeignKey($name);
|
||||
|
||||
return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* MORPH BY MANY关联定义.
|
||||
*
|
||||
* @param string $model 模型名
|
||||
* @param string $middle 中间表名/模型名
|
||||
* @param string|array $morph 多态字段信息
|
||||
* @param string $foreignKey 关联外键
|
||||
*
|
||||
* @return MorphToMany
|
||||
*/
|
||||
public function morphByMany(string $model, string $middle, string | array | null $morph = null, ?string $foreignKey = null): MorphToMany
|
||||
{
|
||||
if (is_null($morph)) {
|
||||
$morph = $middle;
|
||||
}
|
||||
|
||||
[$morphType, $morphKey] = $this->parseMorph($morph);
|
||||
|
||||
$model = $this->parseRelationModel($model);
|
||||
$foreignKey = $foreignKey ?: $this->getForeignKey($this->getName());
|
||||
|
||||
return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $foreignKey, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析多态
|
||||
*
|
||||
* @param string|array $morph
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseMorph(string | array $morph): array
|
||||
{
|
||||
if (is_array($morph)) {
|
||||
[$morphType, $foreignKey] = $morph;
|
||||
} else {
|
||||
$morphType = $morph . '_type';
|
||||
$foreignKey = $morph . '_id';
|
||||
}
|
||||
|
||||
return [$morphType, $foreignKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析模型的完整命名空间.
|
||||
*
|
||||
* @param string $model 模型名(或者完整类名)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRelationModel(string $model): string
|
||||
{
|
||||
if (!str_contains($model, '\\')) {
|
||||
$path = explode('\\', static::class);
|
||||
array_pop($path);
|
||||
array_push($path, Str::studly($model));
|
||||
$model = implode('\\', $path);
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型的默认外键名.
|
||||
*
|
||||
* @param string $name 模型名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getForeignKey(string $name): string
|
||||
{
|
||||
if (str_contains($name, '\\')) {
|
||||
$name = class_basename($name);
|
||||
}
|
||||
|
||||
return Str::snake($name) . '_id';
|
||||
}
|
||||
}
|
||||
219
vendor/topthink/think-orm/src/model/concern/SoftDelete.php
vendored
Normal file
219
vendor/topthink/think-orm/src/model/concern/SoftDelete.php
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\concern;
|
||||
|
||||
use Closure;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 数据软删除
|
||||
*
|
||||
* @mixin Model
|
||||
*
|
||||
* @method $this withTrashed()
|
||||
* @method $this onlyTrashed()
|
||||
*/
|
||||
trait SoftDelete
|
||||
{
|
||||
/**
|
||||
* 获取查询对象
|
||||
*
|
||||
* @param array|null $scope 设置不使用的全局查询范围
|
||||
* @return Query
|
||||
*/
|
||||
public function db(array | null $scope = []): Query
|
||||
{
|
||||
$query = parent::db($scope);
|
||||
$this->withNoTrashed($query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前实例是否被软删除.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function trashed(): bool
|
||||
{
|
||||
$field = $this->getDeleteTimeField();
|
||||
|
||||
if ($field && !empty($this->getOrigin($field))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function scopeWithTrashed(Query $query): void
|
||||
{
|
||||
$query->removeOption('soft_delete');
|
||||
}
|
||||
|
||||
public function scopeOnlyTrashed(Query $query): void
|
||||
{
|
||||
$field = $this->getDeleteTimeField(true);
|
||||
|
||||
if ($field) {
|
||||
$query->useSoftDelete($field, $this->getWithTrashedExp());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取软删除数据的查询条件.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getWithTrashedExp(): array
|
||||
{
|
||||
return is_null($this->getOption('defaultSoftDelete')) ? ['notnull', ''] : ['<>', $this->getOption('defaultSoftDelete')];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前的记录.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
if ($this->isEmpty() || false === $this->trigger('BeforeDelete')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$name = $this->getDeleteTimeField();
|
||||
$force = $this->isForce();
|
||||
|
||||
if ($name && !$force) {
|
||||
// 软删除
|
||||
$this->exists()->withEvent(false)->save([$name => $this->getDateTime($name)]);
|
||||
|
||||
$this->withEvent(true);
|
||||
|
||||
$this->trigger('AfterDelete');
|
||||
$this->exists(false);
|
||||
$this->clear();
|
||||
return true;
|
||||
}
|
||||
return parent::delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除记录.
|
||||
*
|
||||
* @param mixed $data 主键列表 支持闭包查询条件
|
||||
* @param bool $force 是否强制删除
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function destroy($data, bool $force = false): bool
|
||||
{
|
||||
// 传入空值(包括空字符串和空数组)的时候不会做任何的数据删除操作,但传入0则是有效的
|
||||
if (empty($data) && 0 !== $data) {
|
||||
return false;
|
||||
}
|
||||
$query = (new static())->db();
|
||||
|
||||
if ($force) {
|
||||
$query->removeOption('soft_delete');
|
||||
}
|
||||
|
||||
if (is_array($data) && key($data) !== 0) {
|
||||
$query->where($data);
|
||||
$data = [];
|
||||
} elseif ($data instanceof Closure) {
|
||||
call_user_func_array($data, [ &$query]);
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$resultSet = $query->select((array) $data);
|
||||
|
||||
foreach ($resultSet as $result) {
|
||||
/** @var Model $result */
|
||||
$result->force($force)->delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复被软删除的记录.
|
||||
*
|
||||
* @param array $where 更新条件
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function restore(array $where = []): bool
|
||||
{
|
||||
$name = $this->getDeleteTimeField();
|
||||
|
||||
if (!$name || false === $this->trigger('BeforeRestore')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$db = $this->getDbWhere($where);
|
||||
|
||||
// 恢复删除
|
||||
$db->useSoftDelete($name, $this->getWithTrashedExp())
|
||||
->update([$name => $this->getOption('defaultSoftDelete')]);
|
||||
|
||||
$this->trigger('AfterRestore');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取软删除字段.
|
||||
*
|
||||
* @param bool $read 是否查询操作 写操作的时候会自动去掉表别名
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function getDeleteTimeField(bool $read = false): bool | string
|
||||
{
|
||||
$field = $this->getOption('deleteTime', 'delete_time');
|
||||
|
||||
if (false === $field) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!str_contains($field, '.')) {
|
||||
$field = '__TABLE__.' . $field;
|
||||
}
|
||||
|
||||
if (!$read && str_contains($field, '.')) {
|
||||
$array = explode('.', $field);
|
||||
$field = array_pop($array);
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询的时候默认排除软删除数据.
|
||||
*
|
||||
* @param Query $query
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function withNoTrashed(Query $query): void
|
||||
{
|
||||
$field = $this->getDeleteTimeField(true);
|
||||
|
||||
if ($field) {
|
||||
$condition = is_null($this->getOption('defaultSoftDelete')) ? ['null', ''] : ['=', $this->getOption('defaultSoftDelete')];
|
||||
$query->useSoftDelete($field, $condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
vendor/topthink/think-orm/src/model/contract/EnumTransform.php
vendored
Normal file
10
vendor/topthink/think-orm/src/model/contract/EnumTransform.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\contract;
|
||||
|
||||
interface EnumTransform
|
||||
{
|
||||
public function value();
|
||||
}
|
||||
17
vendor/topthink/think-orm/src/model/contract/FieldTypeTransform.php
vendored
Normal file
17
vendor/topthink/think-orm/src/model/contract/FieldTypeTransform.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\contract;
|
||||
|
||||
use think\model\contract\Modelable as Model;
|
||||
|
||||
interface FieldTypeTransform
|
||||
{
|
||||
public static function get(mixed $value, Model $model): ?static;
|
||||
|
||||
/**
|
||||
* @return static|mixed
|
||||
*/
|
||||
public static function set($value, Model $model) : mixed;
|
||||
}
|
||||
9
vendor/topthink/think-orm/src/model/contract/Modelable.php
vendored
Normal file
9
vendor/topthink/think-orm/src/model/contract/Modelable.php
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\contract;
|
||||
|
||||
interface Modelable
|
||||
{
|
||||
}
|
||||
17
vendor/topthink/think-orm/src/model/contract/Typeable.php
vendored
Normal file
17
vendor/topthink/think-orm/src/model/contract/Typeable.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\contract;
|
||||
|
||||
use think\model\contract\Modelable as Model;
|
||||
|
||||
interface Typeable
|
||||
{
|
||||
public static function from(mixed $value, Model $model);
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function value();
|
||||
}
|
||||
350
vendor/topthink/think-orm/src/model/relation/BelongsTo.php
vendored
Normal file
350
vendor/topthink/think-orm/src/model/relation/BelongsTo.php
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\relation;
|
||||
|
||||
use Closure;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\helper\Str;
|
||||
use think\model\contract\Modelable as Model;
|
||||
|
||||
/**
|
||||
* BelongsTo关联类.
|
||||
*/
|
||||
class BelongsTo extends OneToOne
|
||||
{
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $localKey 关联主键
|
||||
* @param string $relation 关联名
|
||||
*/
|
||||
public function __construct(Model $parent, string $model, string $foreignKey, string $localKey, ?string $relation = null)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->foreignKey = $foreignKey;
|
||||
$this->localKey = $localKey;
|
||||
$this->query = (new $model())->db();
|
||||
$this->relation = $relation;
|
||||
|
||||
if (get_class($parent) == $model) {
|
||||
$this->selfRelation = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据.
|
||||
*
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包查询条件
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function getRelation(array $subRelation = [], ?Closure $closure = null)
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$foreignKey = $this->foreignKey;
|
||||
$relationModel = $this->query
|
||||
->removeWhereField($this->localKey)
|
||||
->where($this->localKey, $this->parent->$foreignKey)
|
||||
->relation($subRelation)
|
||||
->find();
|
||||
|
||||
if ($relationModel) {
|
||||
if (!empty($this->bindAttr)) {
|
||||
// 绑定关联属性
|
||||
$this->parent->bindRelationAttr($relationModel, $this->bindAttr);
|
||||
}
|
||||
} else {
|
||||
$default = $this->query->getOption('default_model');
|
||||
$relationModel = $this->getDefaultModel($default);
|
||||
}
|
||||
|
||||
return $relationModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联统计子查询.
|
||||
*
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 聚合字段别名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery(?Closure $closure = null, string $aggregate = 'count', string $field = 'id', &$name = ''): string
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
$alias = Str::snake(class_basename($this->model));
|
||||
$alias = $this->query->getAlias() ?: $alias . '_' . $aggregate;
|
||||
return $this->query
|
||||
->alias($alias)
|
||||
->whereExp($alias . '.' . $this->localKey, '=' . $this->parent->getTable(true) . '.' . $this->foreignKey)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function relationCount(Model $result, ?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null)
|
||||
{
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
if (!isset($result->$foreignKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->where($this->localKey, '=', $result->$foreignKey)
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string $operator 比较操作符
|
||||
* @param int $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', ?Query $query = null) : Query
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
|
||||
if ($this->isSelfRelation() && $alias == $relation) {
|
||||
$relation .= '_';
|
||||
}
|
||||
|
||||
return $query->alias($alias)
|
||||
->whereExists(function ($query) use ($table, $alias, $relation) {
|
||||
$query->table([$table => $relation])
|
||||
->field($relation . '.' . $this->localKey)
|
||||
->whereColumn($alias . '.' . $this->foreignKey, $relation . '.' . $this->localKey);
|
||||
|
||||
$this->getRelationSoftDelete($query, $relation);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null, string $joinType = '', ?Query $query = null, string $logic = '', string $relationAlias = ''): Query
|
||||
{
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$table = $this->query->getTable();
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
$fields = $this->getRelationQueryFields($fields, $alias);
|
||||
$relAlias = $relationAlias ?: $relation;
|
||||
|
||||
if ($this->isSelfRelation() && $alias == $relAlias) {
|
||||
$relAlias .= '_';
|
||||
}
|
||||
|
||||
$query->alias($alias)
|
||||
->via($alias)
|
||||
->field($fields)
|
||||
->join([$table => $relAlias], $alias . '.' . $this->foreignKey . '=' . $relAlias . '.' . $this->localKey, $joinType);
|
||||
|
||||
return $this->getRelationSoftDelete($query, $relAlias, $where, $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集).
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$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);
|
||||
$default = $this->query->getOption('default_model');
|
||||
$defaultModel = $this->getDefaultModel($default);
|
||||
|
||||
$range = array_unique($range);
|
||||
$data = $this->eagerlyWhere([
|
||||
[$localKey, 'in', $range],
|
||||
], $localKey, $subRelation, $closure, $cache, count($range) > 1 ? true : false);
|
||||
|
||||
// 动态绑定参数
|
||||
$bindAttr = $this->query->getOption('bind_attr');
|
||||
if ($bindAttr) {
|
||||
$this->bind($bindAttr);
|
||||
}
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
// 关联模型
|
||||
if (!isset($data[$result->$foreignKey])) {
|
||||
$relationModel = $defaultModel;
|
||||
} else {
|
||||
$relationModel = $data[$result->$foreignKey];
|
||||
}
|
||||
|
||||
// 设置关联属性
|
||||
if (!empty($this->bindAttr) && $relationModel) {
|
||||
$result->bindRelationAttr($relationModel, $this->bindAttr, $relation);
|
||||
} else {
|
||||
$result->setRelation($relation, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据).
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$this->query->removeWhereField($localKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$localKey, '=', $result->$foreignKey],
|
||||
], $localKey, $subRelation, $closure, $cache);
|
||||
|
||||
// 关联模型
|
||||
if (!isset($data[$result->$foreignKey])) {
|
||||
$default = $this->query->getOption('default_model');
|
||||
$relationModel = $this->getDefaultModel($default);
|
||||
} else {
|
||||
$relationModel = $data[$result->$foreignKey];
|
||||
}
|
||||
|
||||
// 动态绑定参数
|
||||
$bindAttr = $this->query->getOption('bind_attr');
|
||||
if ($bindAttr) {
|
||||
$this->bind($bindAttr);
|
||||
}
|
||||
|
||||
// 设置关联属性
|
||||
if (!empty($this->bindAttr) && $relationModel) {
|
||||
$result->bindRelationAttr($relationModel, $this->bindAttr, $relation);
|
||||
} else {
|
||||
$result->setRelation($relation, $relationModel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加关联数据.
|
||||
*
|
||||
* @param Model $model关联模型对象
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function associate(Model $model): Model
|
||||
{
|
||||
$this->parent->set($this->foreignKey, $model->getKey());
|
||||
$this->parent->save();
|
||||
|
||||
return $this->parent->setRelation($this->relation, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销关联数据.
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function dissociate(): Model
|
||||
{
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$this->parent->set($foreignKey, null);
|
||||
$this->parent->save();
|
||||
|
||||
return $this->parent->setRelation($this->relation, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery(): void
|
||||
{
|
||||
if (empty($this->baseQuery)) {
|
||||
if (isset($this->parent->{$this->foreignKey})) {
|
||||
// 关联查询带入关联条件
|
||||
$this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey});
|
||||
}
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
735
vendor/topthink/think-orm/src/model/relation/BelongsToMany.php
vendored
Normal file
735
vendor/topthink/think-orm/src/model/relation/BelongsToMany.php
vendored
Normal file
@@ -0,0 +1,735 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\relation;
|
||||
|
||||
use Closure;
|
||||
use think\Collection;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\db\Raw;
|
||||
use think\helper\Str;
|
||||
use think\model\contract\Modelable as Model;
|
||||
use think\model\Pivot;
|
||||
use think\model\Relation;
|
||||
|
||||
/**
|
||||
* 多对多关联类.
|
||||
*/
|
||||
class BelongsToMany extends Relation
|
||||
{
|
||||
/**
|
||||
* 中间表表名.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $middle;
|
||||
|
||||
/**
|
||||
* 中间表模型名称.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pivotName;
|
||||
|
||||
/**
|
||||
* 中间表模型对象
|
||||
*
|
||||
* @var Pivot
|
||||
*/
|
||||
protected $pivot;
|
||||
|
||||
/**
|
||||
* 中间表数据名称.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pivotDataName = 'pivot';
|
||||
|
||||
/**
|
||||
* 绑定的关联属性.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bindAttr = [];
|
||||
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $middle 中间表/模型名
|
||||
* @param string $foreignKey 关联模型外键
|
||||
* @param string $localKey 当前模型关联键
|
||||
*/
|
||||
public function __construct(Model $parent, string $model, string $middle, string $foreignKey, string $localKey)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->foreignKey = $foreignKey;
|
||||
$this->localKey = $localKey;
|
||||
|
||||
if (str_contains($middle, '\\')) {
|
||||
$this->pivotName = $middle;
|
||||
$this->middle = Str::snake(class_basename($middle));
|
||||
} else {
|
||||
$this->middle = $middle;
|
||||
}
|
||||
|
||||
$this->query = (new $model())->db();
|
||||
$this->pivot = $this->newPivot();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置中间表模型.
|
||||
*
|
||||
* @param $pivot
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function pivot(string $pivot)
|
||||
{
|
||||
$this->pivotName = $pivot;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置中间表数据名称.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function name(string $name)
|
||||
{
|
||||
$this->pivotDataName = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定关联表的属性到父模型属性.
|
||||
*
|
||||
* @param array $attr 要绑定的属性列表
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function bind(array $attr)
|
||||
{
|
||||
$this->bindAttr = $attr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化中间表模型.
|
||||
*
|
||||
* @param $data
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return Pivot
|
||||
*/
|
||||
protected function newPivot(array $data = []): Pivot
|
||||
{
|
||||
$class = $this->pivotName ?: Pivot::class;
|
||||
$pivot = new $class($data, $this->parent, $this->middle);
|
||||
|
||||
if ($pivot instanceof Pivot) {
|
||||
return $pivot;
|
||||
} else {
|
||||
throw new Exception('pivot model must extends: \think\model\Pivot');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据.
|
||||
*
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包查询条件
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getRelation(array $subRelation = [], ?Closure $closure = null): Collection
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
return $this->relation($subRelation)->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装Pivot模型.
|
||||
*
|
||||
* @param Model $result 模型对象
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function matchPivot(Model $result): array
|
||||
{
|
||||
$pivot = $result->getRelation('pivot');
|
||||
$bindAttr = $this->query->getOption('bind_attr');
|
||||
if (empty($bindAttr)) {
|
||||
$bindAttr = $this->bindAttr;
|
||||
}
|
||||
|
||||
foreach ($pivot as $attr => $val) {
|
||||
$pos = array_search($attr, $bindAttr);
|
||||
if (false !== $pos) {
|
||||
// 中间表属性绑定
|
||||
$key = !is_numeric($pos) ? $pos : $attr;
|
||||
if (null !== $result->getOrigin($key)) {
|
||||
throw new Exception('bind attr has exists:' . $attr);
|
||||
}
|
||||
$result->set($key, $val);
|
||||
}
|
||||
}
|
||||
|
||||
$result->setRelation($this->pivotDataName, $this->newPivot($pivot));
|
||||
|
||||
return $pivot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @access public
|
||||
* @param string $operator 比较操作符
|
||||
* @param integer $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query|null $query Query对象
|
||||
* @return Query
|
||||
*/
|
||||
public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', ?Query $query = null): Query
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$pivot = $this->pivot->getTable();
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
|
||||
if ('=' === $operator && 0 === $count) {
|
||||
return $query->alias($alias)
|
||||
->whereNotExists(function ($query) use ($pivot, $alias, $relation, $table) {
|
||||
$query->table([$pivot => 'pivot'])
|
||||
->field('pivot.' . $this->foreignKey)
|
||||
->join($table . ' ' . $relation, $relation . '.' . $this->query->getPk() . '= pivot.' . $this->foreignKey)
|
||||
->whereColumn($alias . '.' . $this->parent->getPk(), 'pivot.' . $this->localKey);
|
||||
|
||||
$this->getRelationSoftDelete($query, $relation);
|
||||
});
|
||||
}
|
||||
|
||||
$query->alias($alias)
|
||||
->field($model . '.*')
|
||||
->join([$pivot => 'pivot'], 'pivot.' . $this->localKey . '=' . $alias . '.' . $this->parent->getPk(), $joinType)
|
||||
->join($table . ' ' . $relation, $relation . '.' . $this->query->getPk() . '= pivot.' . $this->foreignKey, $joinType)
|
||||
->group($alias . '.' . $this->parent->getPk())
|
||||
->having('count(' . $id . ')' . $operator . $count);
|
||||
return $this->getRelationSoftDelete($query, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型
|
||||
* @access public
|
||||
* @param array|Closure $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query|null $query Query对象
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null, string $joinType = '', ?Query $query = null, string $logic = '', string $relationAlias = ''): Query
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$pivot = $this->pivot->getTable();
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
$fields = $this->getRelationQueryFields($fields, $alias);
|
||||
$relAlias = $relationAlias ?: $relation;
|
||||
|
||||
$query->alias($alias)
|
||||
->join([$pivot => 'pivot'], 'pivot.' . $this->localKey . '=' . $alias . '.' . $this->parent->getPk(), $joinType)
|
||||
->join([$table => $relAlias], $relAlias . '.' . $this->query->getPk() . '= pivot.' . $this->foreignKey, $joinType)
|
||||
->group($alias . '.' . $this->parent->getPk())
|
||||
->field($fields);
|
||||
|
||||
return $this->getRelationSoftDelete($query, $relAlias, $where, $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置中间表的查询条件.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集).
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$pk = $resultSet[0]->getPk();
|
||||
$range = [];
|
||||
|
||||
foreach ($resultSet as $result) {
|
||||
// 获取关联外键列表
|
||||
if (isset($result->$pk)) {
|
||||
$range[] = $result->$pk;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
// 查询关联数据
|
||||
$range = array_unique($range);
|
||||
$data = $this->eagerlyManyToMany([
|
||||
['pivot.' . $localKey, 'in', $range],
|
||||
], $subRelation, $closure, $cache, count($range) > 1 ? true : false);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
if (!isset($data[$result->$pk])) {
|
||||
$data[$result->$pk] = [];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $this->resultSetBuild($data[$result->$pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(单个数据).
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(Model $result, string $relation, array $subRelation, ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (is_string($pk) && isset($result->$pk)) {
|
||||
$pk = $result->$pk;
|
||||
// 查询管理数据
|
||||
$data = $this->eagerlyManyToMany([
|
||||
['pivot.' . $this->localKey, '=', $pk],
|
||||
], $subRelation, $closure, $cache);
|
||||
|
||||
// 关联数据封装
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function relationCount(Model $result, ?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null)
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (!isset($result->$pk)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$pk = $result->$pk;
|
||||
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
|
||||
['pivot.' . $this->localKey, '=', $pk],
|
||||
])->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联统计子查询.
|
||||
*
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery(?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null) : string
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
$alias = Str::snake(class_basename($this->model));
|
||||
$alias = $this->query->getAlias() ?: $alias . '_' . $aggregate;
|
||||
if (!str_contains($field, '.')) {
|
||||
$field = $alias . '.' . $field;
|
||||
}
|
||||
|
||||
$this->query->alias($alias);
|
||||
return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
|
||||
[
|
||||
'pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->getTable(true) . '.' . $this->parent->getPk()),
|
||||
],
|
||||
])->fetchSql()->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多对多 关联模型预查询.
|
||||
*
|
||||
* @param array $where 关联预查询条件
|
||||
* @param array $subRelation 子关联
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
* @param bool $collection 是否数据集查询
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyManyToMany(array $where, array $subRelation = [], ?Closure $closure = null, array $cache = [], bool $collection = false) : array
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$withLimit = $this->query->getOption('limit');
|
||||
if ($withLimit && $collection) {
|
||||
$this->query->removeOption('limit');
|
||||
}
|
||||
|
||||
if ($this->isOneofMany) {
|
||||
// 仅获取一条关联数据
|
||||
if (!$collection) {
|
||||
$this->query->limit(1);
|
||||
} else {
|
||||
$withLimit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
$list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)
|
||||
->with($subRelation)
|
||||
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
|
||||
->lazy();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
foreach ($list as $set) {
|
||||
$pivot = $this->matchPivot($set);
|
||||
$key = $pivot[$this->localKey];
|
||||
|
||||
if ($withLimit && isset($data[$key]) && count($data[$key]) >= $withLimit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[$key][] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* BELONGS TO MANY 关联查询.
|
||||
*
|
||||
* @param string $foreignKey 关联模型关联键
|
||||
* @param string $localKey 当前模型关联键
|
||||
* @param array $condition 关联查询条件
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query
|
||||
{
|
||||
// 关联查询封装
|
||||
if (empty($this->baseQuery)) {
|
||||
$tableName = $this->query->getTable(true);
|
||||
$table = $this->pivot->db()->getTable();
|
||||
$fields = $this->getQueryFields($tableName);
|
||||
|
||||
$this->query
|
||||
->field($fields)
|
||||
->tableField(true, $table, 'pivot', 'pivot__')
|
||||
->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $this->query->getPk())
|
||||
->where($condition);
|
||||
}
|
||||
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增)当前关联数据对象
|
||||
*
|
||||
* @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键
|
||||
* @param array $pivot 中间表额外数据
|
||||
*
|
||||
* @return array|Pivot
|
||||
*/
|
||||
public function save($data, array $pivot = [])
|
||||
{
|
||||
// 保存关联表/中间表数据
|
||||
return $this->attach($data, $pivot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存当前关联数据对象
|
||||
*
|
||||
* @param iterable $dataSet 数据集
|
||||
* @param array $pivot 中间表额外数据
|
||||
* @param bool $samePivot 额外数据是否相同
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function saveAll(iterable $dataSet, array $pivot = [], bool $samePivot = false)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($dataSet as $key => $data) {
|
||||
if (!$samePivot) {
|
||||
$pivotData = $pivot[$key] ?? [];
|
||||
} else {
|
||||
$pivotData = $pivot;
|
||||
}
|
||||
|
||||
$result[] = $this->attach($data, $pivotData);
|
||||
}
|
||||
|
||||
return empty($result) ? false : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 附加关联的一个中间表数据.
|
||||
*
|
||||
* @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键
|
||||
* @param array $pivot 中间表额外数据
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return array|Pivot
|
||||
*/
|
||||
public function attach($data, array $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) {
|
||||
// 根据关联表主键直接写入中间表
|
||||
$id = $data->getKey();
|
||||
}
|
||||
|
||||
if (!empty($id)) {
|
||||
// 保存中间表数据
|
||||
$pivot[$this->localKey] = $this->parent->getKey();
|
||||
|
||||
$ids = (array) $id;
|
||||
foreach ($ids as $id) {
|
||||
$pivot[$this->foreignKey] = $id;
|
||||
|
||||
$object = $this->newPivot();
|
||||
$object->replace()->save($pivot);
|
||||
$result[] = $object;
|
||||
}
|
||||
|
||||
if (count($result) == 1) {
|
||||
// 返回中间表模型对象
|
||||
$result = $result[0];
|
||||
}
|
||||
|
||||
return $result;
|
||||
} else {
|
||||
throw new Exception('miss relation data');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在关联数据.
|
||||
*
|
||||
* @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键
|
||||
*
|
||||
* @return Pivot|false
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除关联的一个中间表数据.
|
||||
*
|
||||
* @param int|array $data 数据 可以使用关联对象的主键
|
||||
* @param bool $relationDel 是否同时删除关联表数据
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function detach($data = null, bool $relationDel = false): int
|
||||
{
|
||||
if (is_array($data)) {
|
||||
$id = $data;
|
||||
} elseif (is_numeric($data) || is_string($data)) {
|
||||
// 根据关联表主键直接写入中间表
|
||||
$id = $data;
|
||||
} elseif ($data instanceof Model) {
|
||||
// 根据关联表主键直接写入中间表
|
||||
$id = $data->getKey();
|
||||
}
|
||||
|
||||
// 删除中间表数据
|
||||
$pivot = [];
|
||||
$pivot[] = [$this->localKey, '=', $this->parent->getKey()];
|
||||
|
||||
if (isset($id)) {
|
||||
$pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id];
|
||||
}
|
||||
|
||||
$result = $this->newPivot()->where($pivot)->delete();
|
||||
|
||||
// 删除关联表数据
|
||||
if (isset($id) && $relationDel) {
|
||||
$model = $this->model;
|
||||
$model::destroy($id);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据同步.
|
||||
*
|
||||
* @param array $ids
|
||||
* @param bool $detaching
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sync(array $ids, bool $detaching = true): array
|
||||
{
|
||||
$changes = [
|
||||
'attached' => [],
|
||||
'detached' => [],
|
||||
'updated' => [],
|
||||
];
|
||||
|
||||
$current = $this->pivot
|
||||
->where($this->localKey, $this->parent->getKey())
|
||||
->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->detach($id);
|
||||
$this->attach($id, $attributes);
|
||||
$changes['updated'][] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery(): void
|
||||
{
|
||||
if (empty($this->baseQuery)) {
|
||||
$foreignKey = $this->foreignKey;
|
||||
$localKey = $this->localKey;
|
||||
|
||||
$this->query->filter(function ($result, $options) {
|
||||
$this->matchPivot($result);
|
||||
});
|
||||
|
||||
// 关联查询
|
||||
if (null === $this->parent->getKey()) {
|
||||
$condition = ['pivot.' . $localKey, 'exp', new Raw('=' . $this->parent->getTable(true) . '.' . $this->parent->getPk())];
|
||||
} else {
|
||||
$condition = ['pivot.' . $localKey, '=', $this->parent->getKey()];
|
||||
}
|
||||
|
||||
$this->belongsToManyQuery($foreignKey, $localKey, [$condition]);
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
386
vendor/topthink/think-orm/src/model/relation/HasMany.php
vendored
Normal file
386
vendor/topthink/think-orm/src/model/relation/HasMany.php
vendored
Normal file
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\relation;
|
||||
|
||||
use Closure;
|
||||
use think\Collection;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\helper\Str;
|
||||
use think\model\contract\Modelable as Model;
|
||||
use think\model\Relation;
|
||||
|
||||
/**
|
||||
* 一对多关联类.
|
||||
*/
|
||||
class HasMany extends Relation
|
||||
{
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $localKey 当前模型主键
|
||||
*/
|
||||
public function __construct(Model $parent, string $model, string $foreignKey, string $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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据.
|
||||
*
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包查询条件
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getRelation(array $subRelation = [], ?Closure $closure = null): Collection
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->where($this->foreignKey, $this->parent->{$this->localKey})
|
||||
->relation($subRelation)
|
||||
->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询.
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$range = [];
|
||||
|
||||
foreach ($resultSet as $result) {
|
||||
// 获取关联外键列表
|
||||
if (isset($result->$localKey)) {
|
||||
$range[] = $result->$localKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
$range = array_unique($range);
|
||||
$data = $this->eagerlyOneToMany([
|
||||
[$this->foreignKey, 'in', $range],
|
||||
], $subRelation, $closure, $cache, count($range) > 1 ? true : false);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
$pk = $result->$localKey;
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询.
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if (isset($result->$localKey)) {
|
||||
$pk = $result->$localKey;
|
||||
$data = $this->eagerlyOneToMany([
|
||||
[$this->foreignKey, '=', $pk],
|
||||
], $subRelation, $closure, $cache);
|
||||
|
||||
// 关联数据封装
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function relationCount(Model $result, ?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if (!isset($result->$localKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->where($this->foreignKey, '=', $result->$localKey)
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联统计子查询.
|
||||
*
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery(?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null) : string
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
$alias = Str::snake(class_basename($this->model));
|
||||
$alias = $this->query->getAlias() ?: $alias . '_' . $aggregate;
|
||||
return $this->query->alias($alias)
|
||||
->whereExp($alias . '.' . $this->foreignKey, '=' . $this->parent->getTable(true) . '.' . $this->localKey)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一对多 关联模型预查询.
|
||||
*
|
||||
* @param array $where 关联预查询条件
|
||||
* @param array $subRelation 子关联
|
||||
* @param Closure $closure
|
||||
* @param array $cache 关联缓存
|
||||
* @param bool $collection 是否数据集查询
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyOneToMany(array $where, array $subRelation = [], ?Closure $closure = null, array $cache = [], bool $collection = false) : array
|
||||
{
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$this->query->removeWhereField($this->foreignKey);
|
||||
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
if ($closure) {
|
||||
$this->baseQuery = true;
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$withLimit = $this->query->getOption('limit');
|
||||
if ($withLimit && $collection) {
|
||||
$this->query->removeOption('limit');
|
||||
}
|
||||
|
||||
if ($this->isOneofMany) {
|
||||
if (!$collection) {
|
||||
$this->query->limit(1);
|
||||
} else {
|
||||
$withLimit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$list = $this->query
|
||||
->where($where)
|
||||
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
|
||||
->with($subRelation)
|
||||
->lazy();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
foreach ($list as $set) {
|
||||
$key = $set->$foreignKey;
|
||||
|
||||
if ($withLimit && isset($data[$key]) && count($data[$key]) >= $withLimit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[$key][] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增)当前关联数据对象
|
||||
*
|
||||
* @param array|Model $data 数据 可以使用数组 关联模型对象
|
||||
* @param bool $replace 是否自动识别更新和写入
|
||||
*
|
||||
* @return Model|false
|
||||
*/
|
||||
public function save(array | Model $data, bool $replace = true)
|
||||
{
|
||||
$model = $this->make();
|
||||
|
||||
return $model->replace($replace)->save($data) ? $model : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联对象实例.
|
||||
*
|
||||
* @param array|Model $data
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function make(array | Model $data = []): Model
|
||||
{
|
||||
if ($data instanceof Model) {
|
||||
$data = $data->getData();
|
||||
}
|
||||
|
||||
// 保存关联表数据
|
||||
$data[$this->foreignKey] = $this->parent->{$this->localKey};
|
||||
|
||||
return (new $this->model($data))->setSuffix($this->getModel()->getSuffix());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存当前关联数据对象
|
||||
*
|
||||
* @param iterable $dataSet 数据集
|
||||
* @param bool $replace 是否自动识别更新和写入
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function saveAll(iterable $dataSet, bool $replace = true)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($dataSet as $key => $data) {
|
||||
$result[] = $this->save($data, $replace);
|
||||
}
|
||||
|
||||
return empty($result) ? false : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string $operator 比较操作符
|
||||
* @param int $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', ?Query $query = null): Query
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
|
||||
return $query->alias($alias)
|
||||
->whereExists(function ($query) use ($alias, $id, $table, $operator, $count) {
|
||||
$table = $this->query->getTable();
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
|
||||
if ($this->isSelfRelation() && $alias == $relation) {
|
||||
$relation .= '_';
|
||||
}
|
||||
$query->table([$table => $relation])
|
||||
->field('count(' . $id . ') AS count')
|
||||
->whereColumn($relation . '.' . $this->foreignKey, $alias . '.' . $this->localKey)
|
||||
->having('count ' . $operator . ' ' . $count);
|
||||
|
||||
$this->getRelationSoftDelete($query, $relation);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null, string $joinType = '', ?Query $query = null, string $logic = '', string $relationAlias = ''): Query
|
||||
{
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$table = $this->query->getTable();
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
$fields = $this->getRelationQueryFields($fields, $alias);
|
||||
$relAlias = $relationAlias ?: $relation;
|
||||
|
||||
if ($this->isSelfRelation() && $alias == $relAlias) {
|
||||
$relAlias .= '_';
|
||||
}
|
||||
|
||||
$query->alias($alias)
|
||||
->via($alias)
|
||||
->group($alias . '.' . $this->localKey)
|
||||
->field($fields)
|
||||
->join([$table => $relAlias], $alias . '.' . $this->localKey . '=' . $relAlias . '.' . $this->foreignKey, $joinType);
|
||||
|
||||
return $this->getRelationSoftDelete($query, $relAlias, $where, $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery(): void
|
||||
{
|
||||
if (empty($this->baseQuery)) {
|
||||
if (isset($this->parent->{$this->localKey})) {
|
||||
// 关联查询带入关联条件
|
||||
$this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
|
||||
}
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
391
vendor/topthink/think-orm/src/model/relation/HasManyThrough.php
vendored
Normal file
391
vendor/topthink/think-orm/src/model/relation/HasManyThrough.php
vendored
Normal file
@@ -0,0 +1,391 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\relation;
|
||||
|
||||
use Closure;
|
||||
use think\Collection;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\helper\Str;
|
||||
use think\model\contract\Modelable as Model;
|
||||
use think\model\Relation;
|
||||
|
||||
/**
|
||||
* 远程一对多关联类.
|
||||
*/
|
||||
class HasManyThrough extends Relation
|
||||
{
|
||||
/**
|
||||
* 中间关联表外键.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $throughKey;
|
||||
|
||||
/**
|
||||
* 中间主键.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $throughPk;
|
||||
|
||||
/**
|
||||
* 中间表查询对象
|
||||
*
|
||||
* @var Query
|
||||
*/
|
||||
protected $through;
|
||||
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 关联模型名
|
||||
* @param string $through 中间模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $throughKey 中间关联外键
|
||||
* @param string $localKey 当前模型主键
|
||||
* @param string $throughPk 中间模型主键
|
||||
*/
|
||||
public function __construct(Model $parent, string $model, string $through, string $foreignKey, string $throughKey, string $localKey, string $throughPk)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->through = (new $through())->db();
|
||||
$this->foreignKey = $foreignKey;
|
||||
$this->throughKey = $throughKey;
|
||||
$this->localKey = $localKey;
|
||||
$this->throughPk = $throughPk;
|
||||
$this->query = (new $model())->db();
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据.
|
||||
*
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包查询条件
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getRelation(array $subRelation = [], ?Closure $closure = null)
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$this->baseQuery();
|
||||
|
||||
return $this->query->relation($subRelation)->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string $operator 比较操作符
|
||||
* @param int $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
* @return Query
|
||||
*/
|
||||
public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', ?Query $query = null): Query
|
||||
{
|
||||
// 子查询构建
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$table = $this->through->getTable();
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$relationTable = (new $this->model())->getTable();
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
|
||||
// 统计子查询
|
||||
$subQuery = $this->through
|
||||
->field('COUNT(' . $id . ')')
|
||||
->table($table)
|
||||
->join([$relationTable => $relation], $relation . '.' . $this->throughKey . '=' . $table . '.' . $this->throughPk, $joinType)
|
||||
->whereColumn($table . '.' . $this->throughPk, $model . '.' . $this->localKey);
|
||||
|
||||
$this->getRelationSoftDelete($subQuery, $relation);
|
||||
return $query->alias($alias)->where('(' . $subQuery->buildSql() . ') ' . $operator . ' ' . $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null, $joinType = '', ?Query $query = null, string $logic = '', string $relationAlias = ''): Query
|
||||
{
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$table = $this->through->getTable();
|
||||
$relationTable = (new $this->model())->getTable();
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
$relAlias = $relationAlias ?: $relation;
|
||||
|
||||
// EXISTS子查询
|
||||
$subQuery = $this->through
|
||||
->table($table)
|
||||
->join([$relationTable => $relAlias], $relAlias . '.' . $this->throughKey . '=' . $table . '.' . $this->throughPk, $joinType)
|
||||
->whereColumn($table . '.' . $this->throughPk, $alias . '.' . $this->localKey);
|
||||
|
||||
$this->getRelationSoftDelete($subQuery, $relAlias, $where, $logic);
|
||||
return $query->alias($alias)->whereExists($subQuery->buildSql());
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集).
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$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);
|
||||
$range = array_unique($range);
|
||||
$data = $this->eagerlyWhere([
|
||||
[$this->foreignKey, 'in', $range],
|
||||
], $foreignKey, $subRelation, $closure, $cache, count($range) > 1 ? true : false);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
$pk = $result->$localKey;
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
// 设置关联属性
|
||||
$result->setRelation($relation, $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据).
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
$pk = $result->$localKey;
|
||||
|
||||
$this->query->removeWhereField($foreignKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$foreignKey, '=', $pk],
|
||||
], $foreignKey, $subRelation, $closure, $cache);
|
||||
|
||||
// 关联数据封装
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联模型预查询.
|
||||
*
|
||||
* @param array $where 关联预查询条件
|
||||
* @param string $key 关联键名
|
||||
* @param array $subRelation 子关联
|
||||
* @param Closure $closure
|
||||
* @param array $cache 关联缓存
|
||||
* @param bool $collection 是否数据集查询
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyWhere(array $where, string $key, array $subRelation = [], ?Closure $closure = null, array $cache = [], bool $collection = false): array
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
$throughList = $this->through->where($where)->select();
|
||||
$keys = $throughList->column($this->throughPk, $this->throughPk);
|
||||
|
||||
if ($closure) {
|
||||
$this->baseQuery = true;
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$throughKey = $this->throughKey;
|
||||
|
||||
if ($this->baseQuery) {
|
||||
$throughKey = Str::snake(class_basename($this->model)) . '.' . $this->throughKey;
|
||||
}
|
||||
|
||||
$withLimit = $this->query->getOption('limit');
|
||||
if ($withLimit && $collection) {
|
||||
$this->query->removeOption('limit');
|
||||
}
|
||||
|
||||
if ($this->isOneofMany) {
|
||||
if (!$collection) {
|
||||
$this->query->limit(1);
|
||||
} else {
|
||||
$withLimit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$list = $this->query
|
||||
->where($throughKey, 'in', $keys)
|
||||
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
|
||||
->lazy();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
$keys = $throughList->column($this->foreignKey, $this->throughPk);
|
||||
|
||||
foreach ($list as $set) {
|
||||
$key = $keys[$set->{$this->throughKey}];
|
||||
|
||||
if ($withLimit && isset($data[$key]) && count($data[$key]) >= $withLimit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[$key][] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function relationCount(Model $result, ?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if (!isset($result->$localKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
$alias = Str::snake(class_basename($this->model));
|
||||
$alias = $this->query->getAlias() ?: $alias;
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$modelTable = $this->parent->getTable();
|
||||
|
||||
if (!str_contains($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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联统计子查询.
|
||||
*
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery(?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null) : string
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
$alias = Str::snake(class_basename($this->model));
|
||||
$alias = $this->query->getAlias() ?: $alias . '_' . $aggregate;
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$modelTable = $this->parent->getTable();
|
||||
|
||||
if (!str_contains($field, '.')) {
|
||||
$field = $alias . '.' . $field;
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->alias($alias)
|
||||
->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
|
||||
->whereColumn($throughTable . '.' . $this->foreignKey, $this->parent->getTable() . '.' . $this->localKey)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery() : void
|
||||
{
|
||||
if (empty($this->baseQuery) && $this->parent->getData()) {
|
||||
$alias = Str::snake(class_basename($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)
|
||||
->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey});
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
317
vendor/topthink/think-orm/src/model/relation/HasOne.php
vendored
Normal file
317
vendor/topthink/think-orm/src/model/relation/HasOne.php
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\relation;
|
||||
|
||||
use Closure;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\helper\Str;
|
||||
use think\model\contract\Modelable as Model;
|
||||
|
||||
/**
|
||||
* HasOne 关联类.
|
||||
*/
|
||||
class HasOne extends OneToOne
|
||||
{
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $foreignKey 关联外键
|
||||
* @param string $localKey 当前模型主键
|
||||
*/
|
||||
public function __construct(Model $parent, string $model, string $foreignKey, string $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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据.
|
||||
*
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包查询条件
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function getRelation(array $subRelation = [], ?Closure $closure = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
// 判断关联类型执行查询
|
||||
$relationModel = $this->query
|
||||
->removeWhereField($this->foreignKey)
|
||||
->where($this->foreignKey, $this->parent->$localKey)
|
||||
->relation($subRelation)
|
||||
->find();
|
||||
|
||||
if ($relationModel) {
|
||||
if (!empty($this->bindAttr)) {
|
||||
// 绑定关联属性
|
||||
$this->parent->bindRelationAttr($relationModel, $this->bindAttr);
|
||||
}
|
||||
} else {
|
||||
$default = $this->query->getOption('default_model');
|
||||
$relationModel = $this->getDefaultModel($default);
|
||||
}
|
||||
|
||||
return $relationModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联统计子查询.
|
||||
*
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery(?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null) : string
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
$alias = Str::snake(class_basename($this->model));
|
||||
$alias = $this->query->getAlias() ?: $alias . '_' . $aggregate;
|
||||
return $this->query
|
||||
->alias($alias)
|
||||
->whereExp($alias . '.' . $this->foreignKey, '=' . $this->parent->getTable(true) . '.' . $this->localKey)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function relationCount(Model $result, ?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if (!isset($result->$localKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->where($this->foreignKey, '=', $result->$localKey)
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string $operator 比较操作符
|
||||
* @param int $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', ?Query $query = null) : Query
|
||||
{
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$table = $this->query->getTable();
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
$method = (0 == $count && '=' == $operator) ? 'whereNotExists' : 'whereExists';
|
||||
|
||||
if ($this->isSelfRelation() && $alias == $relation) {
|
||||
$relation .= '_';
|
||||
}
|
||||
|
||||
return $query->alias($alias)->$method(function ($query) use ($table, $alias, $relation) {
|
||||
$query->table([$table => $relation])
|
||||
->field($relation . '.' . $this->foreignKey)
|
||||
->whereColumn($alias . '.' . $this->localKey, $relation . '.' . $this->foreignKey);
|
||||
$this->getRelationSoftDelete($query, $relation);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null, string $joinType = '', ?Query $query = null, string $logic = '', string $relationAlias = ''): Query
|
||||
{
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$table = $this->query->getTable();
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
$fields = $this->getRelationQueryFields($fields, $alias);
|
||||
$relAlias = $relationAlias ?: $relation;
|
||||
|
||||
if ($this->isSelfRelation() && $alias == $relAlias) {
|
||||
$relAlias .= '_';
|
||||
}
|
||||
|
||||
$query->alias($alias)
|
||||
->via($alias)
|
||||
->field($fields)
|
||||
->join([$table => $relAlias], $alias . '.' . $this->localKey . '=' . $relAlias . '.' . $this->foreignKey, $joinType);
|
||||
|
||||
|
||||
return $this->getRelationSoftDelete($query, $relAlias, $where, $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集).
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$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);
|
||||
$default = $this->query->getOption('default_model');
|
||||
$defaultModel = $this->getDefaultModel($default);
|
||||
|
||||
$range = array_unique($range);
|
||||
$data = $this->eagerlyWhere([
|
||||
[$foreignKey, 'in', $range],
|
||||
], $foreignKey, $subRelation, $closure, $cache, count($range) > 1 ? true : false);
|
||||
|
||||
// 动态绑定参数
|
||||
$bindAttr = $this->query->getOption('bind_attr');
|
||||
if ($bindAttr) {
|
||||
$this->bind($bindAttr);
|
||||
}
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
// 关联模型
|
||||
if (!isset($data[$result->$localKey])) {
|
||||
$relationModel = $defaultModel;
|
||||
} else {
|
||||
$relationModel = $data[$result->$localKey];
|
||||
}
|
||||
// 设置关联属性
|
||||
if (!empty($this->bindAttr) && $relationModel) {
|
||||
$result->bindRelationAttr($relationModel, $this->bindAttr, $relation);
|
||||
} else {
|
||||
$result->setRelation($relation, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据).
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$this->query->removeWhereField($foreignKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$foreignKey, '=', $result->$localKey],
|
||||
], $foreignKey, $subRelation, $closure, $cache);
|
||||
|
||||
// 关联模型
|
||||
if (!isset($data[$result->$localKey])) {
|
||||
$default = $this->query->getOption('default_model');
|
||||
$relationModel = $this->getDefaultModel($default);
|
||||
} else {
|
||||
$relationModel = $data[$result->$localKey];
|
||||
}
|
||||
|
||||
// 动态绑定参数
|
||||
$bindAttr = $this->query->getOption('bind_attr');
|
||||
if ($bindAttr) {
|
||||
$this->bind($bindAttr);
|
||||
}
|
||||
// 设置关联属性
|
||||
if (!empty($this->bindAttr) && $relationModel) {
|
||||
$result->bindRelationAttr($relationModel, $this->bindAttr, $relation);
|
||||
} else {
|
||||
$result->setRelation($relation, $relationModel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery(): void
|
||||
{
|
||||
if (empty($this->baseQuery)) {
|
||||
if (isset($this->parent->{$this->localKey})) {
|
||||
// 关联查询带入关联条件
|
||||
$this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
|
||||
}
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
164
vendor/topthink/think-orm/src/model/relation/HasOneThrough.php
vendored
Normal file
164
vendor/topthink/think-orm/src/model/relation/HasOneThrough.php
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\relation;
|
||||
|
||||
use Closure;
|
||||
use think\model\contract\Modelable as Model;
|
||||
|
||||
/**
|
||||
* 远程一对一关联类.
|
||||
*/
|
||||
class HasOneThrough extends HasManyThrough
|
||||
{
|
||||
/**
|
||||
* 延迟获取关联数据.
|
||||
*
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包查询条件
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function getRelation(array $subRelation = [], ?Closure $closure = null)
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$this->baseQuery();
|
||||
|
||||
$relationModel = $this->query->relation($subRelation)->find();
|
||||
|
||||
if ($relationModel) {
|
||||
} else {
|
||||
$default = $this->query->getOption('default_model');
|
||||
$relationModel = $this->getDefaultModel($default);
|
||||
}
|
||||
|
||||
return $relationModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集).
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$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);
|
||||
$default = $this->query->getOption('default_model');
|
||||
$defaultModel = $this->getDefaultModel($default);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$this->foreignKey, 'in', $range],
|
||||
], $foreignKey, $subRelation, $closure, $cache);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
// 关联模型
|
||||
if (!isset($data[$result->$localKey])) {
|
||||
$relationModel = $defaultModel;
|
||||
} else {
|
||||
$relationModel = $data[$result->$localKey];
|
||||
}
|
||||
|
||||
// 设置关联属性
|
||||
$result->setRelation($relation, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据).
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$this->query->removeWhereField($foreignKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$foreignKey, '=', $result->$localKey],
|
||||
], $foreignKey, $subRelation, $closure, $cache);
|
||||
|
||||
// 关联模型
|
||||
if (!isset($data[$result->$localKey])) {
|
||||
$default = $this->query->getOption('default_model');
|
||||
$relationModel = $this->getDefaultModel($default);
|
||||
} else {
|
||||
$relationModel = $data[$result->$localKey];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $relationModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联模型预查询.
|
||||
*
|
||||
* @param array $where 关联预查询条件
|
||||
* @param string $key 关联键名
|
||||
* @param array $subRelation 子关联
|
||||
* @param Closure $closure
|
||||
* @param array $cache 关联缓存
|
||||
* @param bool $collection 是否数据集查询
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyWhere(array $where, string $key, array $subRelation = [], ?Closure $closure = null, array $cache = [], bool $collection = false): array
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
$keys = $this->through->where($where)->column($this->throughPk, $this->foreignKey);
|
||||
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$list = $this->query
|
||||
->where($this->throughKey, 'in', $keys)
|
||||
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
|
||||
->select();
|
||||
|
||||
// 组装模型数据
|
||||
return array_map(function ($key) use ($list) {
|
||||
$set = $list->where($this->throughKey, '=', $key)->first();
|
||||
return $set ?: null;
|
||||
}, $keys);
|
||||
}
|
||||
}
|
||||
436
vendor/topthink/think-orm/src/model/relation/MorphMany.php
vendored
Normal file
436
vendor/topthink/think-orm/src/model/relation/MorphMany.php
vendored
Normal file
@@ -0,0 +1,436 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\relation;
|
||||
|
||||
use Closure;
|
||||
use think\Collection;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\helper\Str;
|
||||
use think\model\contract\Modelable as Model;
|
||||
use think\model\Relation;
|
||||
|
||||
/**
|
||||
* 多态一对多关联.
|
||||
*/
|
||||
class MorphMany extends Relation
|
||||
{
|
||||
/**
|
||||
* 多态关联外键.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphKey;
|
||||
|
||||
/**
|
||||
* 多态字段名.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphType;
|
||||
|
||||
/**
|
||||
* 多态类型.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $morphKey 关联外键
|
||||
* @param string $morphType 多态字段名
|
||||
* @param string $type 多态类型
|
||||
*/
|
||||
public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->type = $type;
|
||||
$this->morphKey = $morphKey;
|
||||
$this->morphType = $morphType;
|
||||
$this->query = (new $model())->db();
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据.
|
||||
*
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包查询条件
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getRelation(array $subRelation = [], ?Closure $closure = null): Collection
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$this->baseQuery();
|
||||
|
||||
return $this->query->relation($subRelation)->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string $operator 比较操作符
|
||||
* @param int $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', ?Query $query = null)
|
||||
{
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$table = $this->query->getTable();
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
|
||||
$query->alias($alias)
|
||||
->field($alias . '.*')
|
||||
->join([$table => $relation], $alias . '.' . $this->parent->getPk() . '=' . $relation . '.' . $this->morphKey)
|
||||
->where($relation . '.' . $this->morphType, '=', $this->type)
|
||||
->group($relation . '.' . $this->morphKey)
|
||||
->having('count(' . $id . ')' . $operator . $count);
|
||||
|
||||
return $this->getRelationSoftDelete($query, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null, string $joinType = '', ?Query $query = null, string $logic = '', string $relationAlias = '')
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$query = $query ?: $this->parent->db();
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
$fields = $this->getRelationQueryFields($fields, $alias);
|
||||
$relAlias = $relationAlias ?: $relation;
|
||||
|
||||
$query->alias($alias)
|
||||
->join([$table => $relAlias], $alias . '.' . $this->parent->getPk() . '=' . $relAlias . '.' . $this->morphKey, $joinType)
|
||||
->where($relAlias . '.' . $this->morphType, '=', $this->type)
|
||||
->group($relAlias . '.' . $this->morphKey)
|
||||
->field($fields);
|
||||
|
||||
return $this->getRelationSoftDelete($query, $relAlias, $where, $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询.
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$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', array_unique($range)],
|
||||
[$morphType, '=', $type],
|
||||
];
|
||||
$data = $this->eagerlyMorphToMany($where, $subRelation, $closure, $cache, true);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
if (!isset($data[$result->$pk])) {
|
||||
$data[$result->$pk] = [];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $this->resultSetBuild($data[$result->$pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询.
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (isset($result->$pk)) {
|
||||
$key = $result->$pk;
|
||||
$data = $this->eagerlyMorphToMany([
|
||||
[$this->morphKey, '=', $key],
|
||||
[$this->morphType, '=', $this->type],
|
||||
], $subRelation, $closure, $cache);
|
||||
|
||||
if (!isset($data[$key])) {
|
||||
$data[$key] = [];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $this->resultSetBuild($data[$key]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function relationCount(Model $result, ?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null)
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (!isset($result->$pk)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->where([
|
||||
[$this->morphKey, '=', $result->$pk],
|
||||
[$this->morphType, '=', $this->type],
|
||||
])
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联统计子查询.
|
||||
*
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery(?Closure $closure = null, string $aggregate = 'count', string $field = 'id', ? string &$name = null) : string
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
$alias = Str::snake(class_basename($this->model));
|
||||
$alias = $this->query->getAlias() ?: $alias . '_' . $aggregate;
|
||||
return $this->query
|
||||
->alias($alias)
|
||||
->whereColumn($alias . '.' . $this->morphKey, $this->parent->getTable(true) . '.' . $this->parent->getPk())
|
||||
->where($alias . '.' . $this->morphType, '=', $this->type)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多态一对多 关联模型预查询.
|
||||
*
|
||||
* @param array $where 关联预查询条件
|
||||
* @param array $subRelation 子关联
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
* @param bool $collection 是否数据集查询
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyMorphToMany(array $where, array $subRelation = [], ?Closure $closure = null, array $cache = [], bool $collection = false) : array
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
$this->query->removeOption('where');
|
||||
|
||||
if ($closure) {
|
||||
$this->baseQuery = true;
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$withLimit = $this->query->getOption('limit');
|
||||
if ($withLimit && $collection) {
|
||||
$this->query->removeOption('limit');
|
||||
}
|
||||
|
||||
if ($this->isOneofMany) {
|
||||
// 仅获取一条关联数据
|
||||
if (!$collection) {
|
||||
$this->query->limit(1);
|
||||
} else {
|
||||
$withLimit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$list = $this->query
|
||||
->where($where)
|
||||
->with($subRelation)
|
||||
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
|
||||
->lazy();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
$morphKey = $this->morphKey;
|
||||
foreach ($list as $set) {
|
||||
$key = $set->$morphKey;
|
||||
|
||||
if ($withLimit && isset($data[$key]) && count($data[$key]) >= $withLimit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[$key][] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增)当前关联数据对象
|
||||
*
|
||||
* @param array|Model $data 数据 可以使用数组 关联模型对象
|
||||
* @param bool $replace 是否自动识别更新和写入
|
||||
*
|
||||
* @return Model|false
|
||||
*/
|
||||
public function save(array | Model $data, bool $replace = true)
|
||||
{
|
||||
$model = $this->make();
|
||||
|
||||
return $model->replace($replace)->save($data) ? $model : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联对象实例.
|
||||
*
|
||||
* @param array|Model $data
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function make($data = []): Model
|
||||
{
|
||||
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))->setSuffix($this->getModel()->getSuffix());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存当前关联数据对象
|
||||
*
|
||||
* @param iterable $dataSet 数据集
|
||||
* @param bool $replace 是否自动识别更新和写入
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function saveAll(iterable $dataSet, bool $replace = true)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($dataSet as $key => $data) {
|
||||
$result[] = $this->save($data, $replace);
|
||||
}
|
||||
|
||||
return empty($result) ? false : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多态关联外键.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphKey()
|
||||
{
|
||||
return $this->morphKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多态字段名.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMorphType()
|
||||
{
|
||||
return $this->morphType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多态类型.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery(): void
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
393
vendor/topthink/think-orm/src/model/relation/MorphOne.php
vendored
Normal file
393
vendor/topthink/think-orm/src/model/relation/MorphOne.php
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\relation;
|
||||
|
||||
use Closure;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\helper\Str;
|
||||
use think\model\contract\Modelable as Model;
|
||||
use think\model\Relation;
|
||||
|
||||
/**
|
||||
* 多态一对一关联类.
|
||||
*/
|
||||
class MorphOne extends Relation
|
||||
{
|
||||
/**
|
||||
* 多态关联外键.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphKey;
|
||||
|
||||
/**
|
||||
* 多态字段.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphType;
|
||||
|
||||
/**
|
||||
* 多态类型.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* 绑定的关联属性.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bindAttr = [];
|
||||
|
||||
/**
|
||||
* 构造函数.
|
||||
*
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $morphKey 关联外键
|
||||
* @param string $morphType 多态字段名
|
||||
* @param string $type 多态类型
|
||||
*/
|
||||
public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->type = $type;
|
||||
$this->morphKey = $morphKey;
|
||||
$this->morphType = $morphType;
|
||||
$this->query = (new $model())->db();
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据.
|
||||
*
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包查询条件
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function getRelation(array $subRelation = [], ?Closure $closure = null)
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$this->baseQuery();
|
||||
|
||||
$relationModel = $this->query->relation($subRelation)->find();
|
||||
|
||||
if ($relationModel) {
|
||||
if (!empty($this->bindAttr)) {
|
||||
// 绑定关联属性
|
||||
$this->bindAttr($this->parent, $relationModel);
|
||||
}
|
||||
} else {
|
||||
$default = $this->query->getOption('default_model');
|
||||
$relationModel = $this->getDefaultModel($default);
|
||||
}
|
||||
|
||||
return $relationModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string $operator 比较操作符
|
||||
* @param int $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', ?Query $query = null)
|
||||
{
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$table = $this->query->getTable();
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
|
||||
$query->alias($alias)
|
||||
->field($alias . '.*')
|
||||
->join([$table => $relation], $alias . '.' . $this->parent->getPk() . '=' . $relation . '.' . $this->morphKey)
|
||||
->where($relation . '.' . $this->morphType, '=', $this->type)
|
||||
->group($relation . '.' . $this->morphKey)
|
||||
->having('count(' . $id . ')' . $operator . $count);
|
||||
|
||||
return $this->getRelationSoftDelete($query, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null, string $joinType = '', ?Query $query = null, string $logic = '', string $relationAlias = '')
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$relation = Str::snake(class_basename($this->model));
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
$fields = $this->getRelationQueryFields($fields, $alias);
|
||||
$relAlias = $relationAlias ?: $relation;
|
||||
|
||||
$query->alias($alias)
|
||||
->join([$table => $relAlias], $alias . '.' . $this->parent->getPk() . '=' . $relAlias . '.' . $this->morphKey, $joinType)
|
||||
->where($relAlias . '.' . $this->morphType, '=', $this->type)
|
||||
->group($relAlias . '.' . $this->morphKey)
|
||||
->field($fields);
|
||||
|
||||
return $this->getRelationSoftDelete($query, $relAlias, $where, $logic);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询.
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$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],
|
||||
], $subRelation, $closure, $cache);
|
||||
|
||||
$default = $this->query->getOption('default_model');
|
||||
$defaultModel = $this->getDefaultModel($default);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
if (!isset($data[$result->$pk])) {
|
||||
$relationModel = $defaultModel;
|
||||
} else {
|
||||
$relationModel = $data[$result->$pk];
|
||||
}
|
||||
|
||||
if (!empty($this->bindAttr)) {
|
||||
// 绑定关联属性
|
||||
$this->bindAttr($result, $relationModel);
|
||||
} else {
|
||||
// 设置关联属性
|
||||
$result->setRelation($relation, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询.
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (isset($result->$pk)) {
|
||||
$pk = $result->$pk;
|
||||
$data = $this->eagerlyMorphToOne([
|
||||
[$this->morphKey, '=', $pk],
|
||||
[$this->morphType, '=', $this->type],
|
||||
], $subRelation, $closure, $cache);
|
||||
|
||||
if (isset($data[$pk])) {
|
||||
$relationModel = $data[$pk];
|
||||
} else {
|
||||
$default = $this->query->getOption('default_model');
|
||||
$relationModel = $this->getDefaultModel($default);
|
||||
}
|
||||
|
||||
if (!empty($this->bindAttr)) {
|
||||
// 绑定关联属性
|
||||
$this->bindAttr($result, $relationModel);
|
||||
} else {
|
||||
// 设置关联属性
|
||||
$result->setRelation($relation, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 多态一对一 关联模型预查询.
|
||||
*
|
||||
* @param array $where 关联预查询条件
|
||||
* @param array $subRelation 子关联
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyMorphToOne(array $where, array $subRelation = [], ?Closure $closure = null, array $cache = []): array
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
if ($closure) {
|
||||
$this->baseQuery = true;
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$list = $this->query
|
||||
->where($where)
|
||||
->with($subRelation)
|
||||
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
|
||||
->lazy();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
$morphKey = $this->morphKey;
|
||||
foreach ($list as $set) {
|
||||
$data[$set->$morphKey] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增)当前关联数据对象
|
||||
*
|
||||
* @param array|Model $data 数据 可以使用数组 关联模型对象
|
||||
* @param bool $replace 是否自动识别更新和写入
|
||||
*
|
||||
* @return Model|false
|
||||
*/
|
||||
public function save(array | Model $data, bool $replace = true)
|
||||
{
|
||||
$model = $this->make();
|
||||
|
||||
return $model->replace($replace)->save($data) ? $model : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联对象实例.
|
||||
*
|
||||
* @param array|Model $data
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function make(array | Model $data = []): Model
|
||||
{
|
||||
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))->setSuffix($this->getModel()->getSuffix());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(进执行一次).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery(): void
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定关联表的属性到父模型属性.
|
||||
*
|
||||
* @param array $attr 要绑定的属性列表
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function bind(array $attr)
|
||||
{
|
||||
$this->bindAttr = $attr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取绑定属性.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getBindAttr(): array
|
||||
{
|
||||
return $this->bindAttr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定关联属性到父模型.
|
||||
*
|
||||
* @param Model $result 父模型对象
|
||||
* @param Model $model 关联模型对象
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function bindAttr(Model $result, ?Model $model = null): void
|
||||
{
|
||||
foreach ($this->bindAttr as $key => $attr) {
|
||||
$key = is_numeric($key) ? $attr : $key;
|
||||
$value = $result->getOrigin($key);
|
||||
|
||||
if (!is_null($value)) {
|
||||
throw new Exception('bind attr has exists:' . $key);
|
||||
}
|
||||
|
||||
$result->set($key, $model?->get($attr));
|
||||
}
|
||||
}
|
||||
}
|
||||
392
vendor/topthink/think-orm/src/model/relation/MorphTo.php
vendored
Normal file
392
vendor/topthink/think-orm/src/model/relation/MorphTo.php
vendored
Normal file
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\relation;
|
||||
|
||||
use BackedEnum;
|
||||
use Closure;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\db\Query;
|
||||
use think\helper\Str;
|
||||
use think\model\contract\Modelable as Model;
|
||||
use think\model\Relation;
|
||||
|
||||
/**
|
||||
* 多态关联类.
|
||||
*/
|
||||
class MorphTo extends Relation
|
||||
{
|
||||
/**
|
||||
* 多态关联外键.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphKey;
|
||||
|
||||
/**
|
||||
* 多态字段.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphType;
|
||||
|
||||
/**
|
||||
* 多态别名.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $alias = [];
|
||||
|
||||
/**
|
||||
* 关联名.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relation;
|
||||
|
||||
protected $queryCaller = [];
|
||||
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $morphType 多态字段名
|
||||
* @param string $morphKey 外键名
|
||||
* @param array $alias 多态别名定义
|
||||
* @param ?string $relation 关联名
|
||||
*/
|
||||
public function __construct(Model $parent, string $morphType, string $morphKey, array $alias = [], ?string $relation = null)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->morphType = $morphType;
|
||||
$this->morphKey = $morphKey;
|
||||
$this->alias = $alias;
|
||||
$this->relation = $relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的关联模型类的实例.
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function getModel(): Model
|
||||
{
|
||||
$morphType = $this->morphType;
|
||||
$model = $this->parseModel($this->parent->$morphType);
|
||||
|
||||
return new $model();
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟获取关联数据.
|
||||
*
|
||||
* @param array $subRelation 子关联名
|
||||
* @param ?Closure $closure 闭包查询条件
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function getRelation(array $subRelation = [], ?Closure $closure = null)
|
||||
{
|
||||
$morphKey = $this->morphKey;
|
||||
$morphType = $this->morphType;
|
||||
|
||||
// 多态模型
|
||||
$model = $this->parseModel($this->parent->$morphType);
|
||||
|
||||
// 主键数据
|
||||
$pk = $this->parent->$morphKey;
|
||||
|
||||
return class_exists($model) ? $this->buildQuery((new $model())->relation($subRelation))->find($pk) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param string $operator 比较操作符
|
||||
* @param int $count 个数
|
||||
* @param string $id 关联表的统计字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', ?Query $query = null)
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据关联条件查询当前模型.
|
||||
*
|
||||
* @param mixed $where 查询条件(数组或者闭包)
|
||||
* @param mixed $fields 字段
|
||||
* @param string $joinType JOIN类型
|
||||
* @param ?Query $query Query对象
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null, string $joinType = '', ?Query $query = null, string $logic = '')
|
||||
{
|
||||
$model = Str::snake(class_basename($this->parent));
|
||||
$types = $this->parent->distinct()->column($this->morphType);
|
||||
$query = $query ?: $this->parent->db();
|
||||
$alias = $query->getAlias() ?: $model;
|
||||
|
||||
return $query->alias($alias)
|
||||
->where(function (Query $query) use ($types, $where, $alias, $logic) {
|
||||
foreach ($types as $type) {
|
||||
if ($type) {
|
||||
$query->whereExists(function (Query $query) use ($type, $where, $alias, $logic) {
|
||||
$class = $this->parseModel($type);
|
||||
/** @var Model $model */
|
||||
$model = new $class();
|
||||
|
||||
$table = $model->getTable();
|
||||
$logic = 'OR' == $logic ? 'whereOr' : 'where';
|
||||
$query
|
||||
->table($table)
|
||||
->where($alias . '.' . $this->morphType, $type)
|
||||
->whereColumn($alias . '.' . $this->morphKey, $table . '.' . $model->getPk())
|
||||
->$logic($where);
|
||||
}, 'OR');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析模型的完整命名空间.
|
||||
*
|
||||
* @param string $model 模型名(或者完整类名)
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
protected function parseModel($model): string
|
||||
{
|
||||
if ($model instanceof BackedEnum) {
|
||||
$model = $model->value;
|
||||
}
|
||||
|
||||
if (isset($this->alias[$model])) {
|
||||
$model = $this->alias[$model];
|
||||
}
|
||||
|
||||
if (!str_contains($model, '\\')) {
|
||||
$path = explode('\\', get_class($this->parent));
|
||||
array_pop($path);
|
||||
array_push($path, Str::studly($model));
|
||||
$model = implode('\\', $path);
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置多态别名.
|
||||
*
|
||||
* @param array $alias 别名定义
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlias(array $alias)
|
||||
{
|
||||
$this->alias = $alias;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除关联查询参数.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeOption(string $option = '')
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询.
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param ?Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$morphKey = $this->morphKey;
|
||||
$morphType = $this->morphType;
|
||||
$range = [];
|
||||
|
||||
foreach ($resultSet as $result) {
|
||||
// 获取关联外键列表
|
||||
if (!empty($result->$morphKey)) {
|
||||
$range[$result->$morphType][] = $result->$morphKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
foreach ($range as $key => $val) {
|
||||
// 多态类型映射
|
||||
$model = $this->parseModel($key);
|
||||
$data = [];
|
||||
if (class_exists($model)) {
|
||||
$obj = new $model();
|
||||
if (!is_null($closure)) {
|
||||
$obj = $closure($obj);
|
||||
}
|
||||
$pk = $obj->getPk();
|
||||
$list = $obj->with($subRelation)
|
||||
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
|
||||
->select($val);
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询.
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param ?Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
// 多态类型映射
|
||||
$model = $this->parseModel($result->{$this->morphType});
|
||||
|
||||
$this->eagerlyMorphToOne($model, $relation, $result, $subRelation, $cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param ?Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function relationCount(Model $result, ?Closure $closure = null, string $aggregate = 'count', string $field = '*')
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 多态MorphTo 关联模型预查询.
|
||||
*
|
||||
* @param string $model 关联模型对象
|
||||
* @param string $relation 关联名
|
||||
* @param Model $result
|
||||
* @param array $subRelation 子关联
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function eagerlyMorphToOne(string $model, string $relation, Model $result, array $subRelation = [], array $cache = []): void
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
$pk = $this->parent->{$this->morphKey};
|
||||
$data = null;
|
||||
|
||||
if (class_exists($model)) {
|
||||
$data = (new $model())->with($subRelation)
|
||||
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
|
||||
->find($pk);
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $data ?: null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加关联数据.
|
||||
*
|
||||
* @param Model $model 关联模型对象
|
||||
* @param string $type 多态类型
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function associate(Model $model, string $type = ''): Model
|
||||
{
|
||||
$morphKey = $this->morphKey;
|
||||
$morphType = $this->morphType;
|
||||
$pk = $model->getPk();
|
||||
|
||||
$this->parent->set($morphKey, $model->$pk);
|
||||
$this->parent->set($morphType, $type ?: get_class($model));
|
||||
$this->parent->save();
|
||||
|
||||
return $this->parent->setRelation($this->relation, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销关联数据.
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function dissociate(): Model
|
||||
{
|
||||
$morphKey = $this->morphKey;
|
||||
$morphType = $this->morphType;
|
||||
|
||||
$this->parent->set($morphKey, null);
|
||||
$this->parent->set($morphType, null);
|
||||
$this->parent->save();
|
||||
|
||||
return $this->parent->setRelation($this->relation, null);
|
||||
}
|
||||
|
||||
protected function buildQuery(Query $query)
|
||||
{
|
||||
foreach ($this->queryCaller as $caller) {
|
||||
call_user_func_array([$query, $caller[0]], $caller[1]);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$this->queryCaller[] = [$method, $args];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
497
vendor/topthink/think-orm/src/model/relation/MorphToMany.php
vendored
Normal file
497
vendor/topthink/think-orm/src/model/relation/MorphToMany.php
vendored
Normal file
@@ -0,0 +1,497 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\relation;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use think\db\BaseQuery as Query;
|
||||
use think\db\Raw;
|
||||
use think\model\contract\Modelable as Model;
|
||||
use think\model\Pivot;
|
||||
|
||||
/**
|
||||
* 多态多对多关联.
|
||||
*/
|
||||
class MorphToMany extends BelongsToMany
|
||||
{
|
||||
/**
|
||||
* 多态关系的模型名映射别名的数组.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $morphMap = [];
|
||||
|
||||
/**
|
||||
* 多态字段名.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphType;
|
||||
|
||||
/**
|
||||
* 多态模型名.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $morphClass;
|
||||
|
||||
/**
|
||||
* 是否反向关联.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $inverse;
|
||||
|
||||
/**
|
||||
* 架构函数.
|
||||
*
|
||||
* @param Model $parent 上级模型对象
|
||||
* @param string $model 模型名
|
||||
* @param string $middle 中间表名/模型名
|
||||
* @param string $morphKey 关联外键
|
||||
* @param string $morphType 多态字段名
|
||||
* @param string $localKey 当前模型关联键
|
||||
* @param bool $inverse 反向关联
|
||||
*/
|
||||
public function __construct(Model $parent, string $model, string $middle, string $morphType, string $morphKey, string $localKey, bool $inverse = false)
|
||||
{
|
||||
$this->morphType = $morphType;
|
||||
$this->inverse = $inverse;
|
||||
$this->morphClass = $inverse ? $model : get_class($parent);
|
||||
|
||||
if (isset(static::$morphMap[$this->morphClass])) {
|
||||
$this->morphClass = static::$morphMap[$this->morphClass];
|
||||
}
|
||||
|
||||
$foreignKey = $inverse ? $morphKey : $localKey;
|
||||
$localKey = $inverse ? $localKey : $morphKey;
|
||||
|
||||
parent::__construct($parent, $model, $middle, $foreignKey, $localKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集).
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$pk = $resultSet[0]->getPk();
|
||||
$range = [];
|
||||
|
||||
foreach ($resultSet as $result) {
|
||||
// 获取关联外键列表
|
||||
if (isset($result->$pk)) {
|
||||
$range[] = $result->$pk;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
// 查询关联数据
|
||||
$data = $this->eagerlyManyToMany([
|
||||
['pivot.' . $this->localKey, 'in', array_unique($range)],
|
||||
['pivot.' . $this->morphType, '=', $this->morphClass],
|
||||
], $subRelation, $closure, $cache, true);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
if (!isset($data[$result->$pk])) {
|
||||
$data[$result->$pk] = [];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $this->resultSetBuild($data[$result->$pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(单个数据).
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(Model $result, string $relation, array $subRelation, ?Closure $closure = null, array $cache = []): void
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (isset($result->$pk)) {
|
||||
$pk = $result->$pk;
|
||||
// 查询管理数据
|
||||
$data = $this->eagerlyManyToMany([
|
||||
['pivot.' . $this->localKey, '=', $pk],
|
||||
['pivot.' . $this->morphType, '=', $this->morphClass],
|
||||
], $subRelation, $closure, $cache);
|
||||
|
||||
// 关联数据封装
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
$result->setRelation($relation, $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function relationCount(Model $result, ?Closure $closure = null, string $aggregate = 'count', string $field = '*', ?string &$name = null)
|
||||
{
|
||||
$pk = $result->getPk();
|
||||
|
||||
if (!isset($result->$pk)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
|
||||
['pivot.' . $this->localKey, '=', $result->$pk],
|
||||
['pivot.' . $this->morphType, '=', $this->morphClass],
|
||||
])->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关联统计子查询.
|
||||
*
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery(?Closure $closure = null, string $aggregate = 'count', string $field = '*', ?string &$name = null): string
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query, $name);
|
||||
}
|
||||
|
||||
return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
|
||||
['pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->getTable(true) . '.' . $this->parent->getPk())],
|
||||
['pivot.' . $this->morphType, '=', $this->morphClass],
|
||||
])->fetchSql()->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* BELONGS TO MANY 关联查询.
|
||||
*
|
||||
* @param string $foreignKey 关联模型关联键
|
||||
* @param string $localKey 当前模型关联键
|
||||
* @param array $condition 关联查询条件
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query
|
||||
{
|
||||
// 关联查询封装
|
||||
$tableName = $this->query->getTable();
|
||||
$table = $this->pivot->db()->getTable();
|
||||
$fields = $this->getQueryFields($tableName);
|
||||
|
||||
$query = $this->query
|
||||
->field($fields)
|
||||
->tableField(true, $table, 'pivot', 'pivot__');
|
||||
|
||||
if (empty($this->baseQuery)) {
|
||||
$relationFk = $this->query->getPk();
|
||||
$query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk)
|
||||
->where($condition);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多对多 关联模型预查询.
|
||||
*
|
||||
* @param array $where 关联预查询条件
|
||||
* @param array $subRelation 子关联
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
* @param bool $collection 是否数据集查询
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyManyToMany(array $where, array $subRelation = [], ?Closure $closure = null, array $cache = [], bool $collection = false): array
|
||||
{
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$withLimit = $this->query->getOption('limit');
|
||||
if ($withLimit && $collection) {
|
||||
$this->query->removeOption('limit');
|
||||
}
|
||||
|
||||
if ($this->isOneofMany) {
|
||||
// 仅获取一条关联数据
|
||||
if (!$collection) {
|
||||
$this->query->limit(1);
|
||||
} else {
|
||||
$withLimit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
$list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)
|
||||
->with($subRelation)
|
||||
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
|
||||
->lazy();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
foreach ($list as $set) {
|
||||
$pivot = $set->getRelation('pivot');
|
||||
$key = $pivot[$this->localKey];
|
||||
|
||||
if ($withLimit && isset($data[$key]) && count($data[$key]) >= $withLimit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$set->setRelation($this->pivotDataName, $this->newPivot($pivot));
|
||||
|
||||
$data[$key][] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 附加关联的一个中间表数据.
|
||||
*
|
||||
* @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键
|
||||
* @param array $pivot 中间表额外数据
|
||||
*
|
||||
* @return array|Pivot
|
||||
*/
|
||||
public function attach($data, array $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) {
|
||||
// 根据关联表主键直接写入中间表
|
||||
$id = $data->getKey();
|
||||
}
|
||||
|
||||
if (!empty($id)) {
|
||||
// 保存中间表数据
|
||||
$pivot[$this->localKey] = $this->parent->getKey();
|
||||
$pivot[$this->morphType] = $this->morphClass;
|
||||
|
||||
$result = [];
|
||||
foreach ((array) $ids as $id) {
|
||||
$pivot[$this->foreignKey] = $id;
|
||||
$object = $this->newPivot();
|
||||
$object->replace()->save($pivot);
|
||||
|
||||
$result[] = $object;
|
||||
}
|
||||
|
||||
if (count($result) == 1) {
|
||||
// 返回中间表模型对象
|
||||
$result = $result[0];
|
||||
}
|
||||
|
||||
return $result;
|
||||
} else {
|
||||
throw new Exception('miss relation data');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在关联数据.
|
||||
*
|
||||
* @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键
|
||||
*
|
||||
* @return Pivot|false
|
||||
*/
|
||||
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->morphType, $this->morphClass)
|
||||
->where($this->foreignKey, $id)
|
||||
->find();
|
||||
|
||||
return $pivot ?: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除关联的一个中间表数据.
|
||||
*
|
||||
* @param int|array $data 数据 可以使用关联对象的主键
|
||||
* @param bool $relationDel 是否同时删除关联表数据
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function detach($data = null, bool $relationDel = false): int
|
||||
{
|
||||
if (is_array($data)) {
|
||||
$id = $data;
|
||||
} elseif (is_numeric($data) || is_string($data)) {
|
||||
// 根据关联表主键直接写入中间表
|
||||
$id = $data;
|
||||
} elseif ($data instanceof Model) {
|
||||
// 根据关联表主键直接写入中间表
|
||||
$id = $data->getKey();
|
||||
}
|
||||
|
||||
// 删除中间表数据
|
||||
$pivot = [
|
||||
[$this->localKey, '=', $this->parent->getKey()],
|
||||
[$this->morphType, '=', $this->morphClass],
|
||||
];
|
||||
|
||||
if (isset($id)) {
|
||||
$pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id];
|
||||
}
|
||||
|
||||
$result = $this->newPivot()->where($pivot)->delete();
|
||||
|
||||
// 删除关联表数据
|
||||
if (isset($id) && $relationDel) {
|
||||
$model = $this->model;
|
||||
$model::destroy($id);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据同步.
|
||||
*
|
||||
* @param array $ids
|
||||
* @param bool $detaching
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sync(array $ids, bool $detaching = true): array
|
||||
{
|
||||
$changes = [
|
||||
'attached' => [],
|
||||
'detached' => [],
|
||||
'updated' => [],
|
||||
];
|
||||
|
||||
$current = $this->pivot
|
||||
->where($this->localKey, $this->parent->getKey())
|
||||
->where($this->morphType, $this->morphClass)
|
||||
->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->detach($id);
|
||||
$this->attach($id, $attributes);
|
||||
$changes['updated'][] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function baseQuery(): void
|
||||
{
|
||||
if (empty($this->baseQuery)) {
|
||||
$foreignKey = $this->foreignKey;
|
||||
$localKey = $this->localKey;
|
||||
|
||||
// 关联查询
|
||||
$this->belongsToManyQuery($foreignKey, $localKey, [
|
||||
['pivot.' . $localKey, '=', $this->parent->getKey()],
|
||||
['pivot.' . $this->morphType, '=', $this->morphClass],
|
||||
]);
|
||||
|
||||
$this->baseQuery = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置或获取多态关系的模型名映射别名的数组.
|
||||
*
|
||||
* @param array|null $map
|
||||
* @param bool $merge
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function morphMap(?array $map = null, $merge = true): array
|
||||
{
|
||||
if (is_array($map)) {
|
||||
static::$morphMap = $merge && static::$morphMap
|
||||
? $map + static::$morphMap : $map;
|
||||
}
|
||||
|
||||
return static::$morphMap;
|
||||
}
|
||||
}
|
||||
347
vendor/topthink/think-orm/src/model/relation/OneToOne.php
vendored
Normal file
347
vendor/topthink/think-orm/src/model/relation/OneToOne.php
vendored
Normal file
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2025 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\BaseQuery as Query;
|
||||
use think\db\exception\DbException as Exception;
|
||||
use think\db\exception\InvalidArgumentException;
|
||||
use think\helper\Str;
|
||||
use think\model\contract\Modelable as Model;
|
||||
use think\model\Relation;
|
||||
|
||||
/**
|
||||
* 一对一关联基础类.
|
||||
*/
|
||||
abstract class OneToOne extends Relation
|
||||
{
|
||||
/**
|
||||
* JOIN类型.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $joinType = 'INNER';
|
||||
|
||||
/**
|
||||
* 绑定的关联属性.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bindAttr = [];
|
||||
|
||||
/**
|
||||
* 关联名.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relation;
|
||||
|
||||
/**
|
||||
* 获取一对多关联的最新一条数据.
|
||||
*
|
||||
* @param string $field 排序字段
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function firstOfMany(string $field = '')
|
||||
{
|
||||
return $this->first($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一对多关联的最旧一条数据.
|
||||
*
|
||||
* @param string $field 排序字段
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function lastOfMany(string $field = '')
|
||||
{
|
||||
return $this->last($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置join类型.
|
||||
*
|
||||
* @param string $type JOIN类型
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function joinType(string $type)
|
||||
{
|
||||
$this->joinType = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(JOIN方式).
|
||||
*
|
||||
* @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, string $relation, $field = true, string $joinType = '', ?Closure $closure = null, bool $first = false): void
|
||||
{
|
||||
$name = Str::snake(class_basename($this->parent));
|
||||
|
||||
if ($first) {
|
||||
$table = $query->getTable();
|
||||
$query->table([$table => $name]);
|
||||
|
||||
if ($query->getOption('field')) {
|
||||
$masterField = $query->getOption('field');
|
||||
$query->removeOption('field');
|
||||
} else {
|
||||
$masterField = true;
|
||||
}
|
||||
|
||||
$query->tableField($masterField, $table, $name);
|
||||
}
|
||||
|
||||
// 预载入封装
|
||||
$joinTable = $this->query->getTable();
|
||||
$joinAlias = Str::snake($relation);
|
||||
$joinType = $joinType ?: $this->joinType;
|
||||
if (true !== $field) {
|
||||
$joinField = $field;
|
||||
} elseif ($this->query->getOption('field')) {
|
||||
$joinField = $this->query->getOption('field');
|
||||
} else {
|
||||
$joinField = $field;
|
||||
}
|
||||
|
||||
$query->via($joinAlias);
|
||||
|
||||
if ($this instanceof BelongsTo) {
|
||||
$foreignKeyExp = $this->foreignKey;
|
||||
|
||||
if (!str_contains($foreignKeyExp, '.')) {
|
||||
$foreignKeyExp = $name . '.' . $this->foreignKey;
|
||||
}
|
||||
|
||||
$joinOn = $foreignKeyExp . '=' . $joinAlias . '.' . $this->localKey;
|
||||
} else {
|
||||
$foreignKeyExp = $this->foreignKey;
|
||||
|
||||
if (!str_contains($foreignKeyExp, '.')) {
|
||||
$foreignKeyExp = $joinAlias . '.' . $this->foreignKey;
|
||||
}
|
||||
|
||||
$joinOn = $name . '.' . $this->localKey . '=' . $foreignKeyExp;
|
||||
}
|
||||
|
||||
if ($closure) {
|
||||
// 执行闭包查询
|
||||
$closure($query);
|
||||
|
||||
// 使用field指定获取关联的字段
|
||||
$withField = $query->getOption('field');
|
||||
if ($withField) {
|
||||
$joinField = $withField;
|
||||
}
|
||||
$query->removeOption('field');
|
||||
}
|
||||
|
||||
$query->join([$joinTable => $joinAlias], $joinOn, $joinType)
|
||||
->tableField($joinField, $joinTable, $joinAlias, $joinAlias . '__');
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集).
|
||||
*
|
||||
* @param array $resultSet
|
||||
* @param string $relation
|
||||
* @param array $subRelation
|
||||
* @param Closure $closure
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], ?Closure $closure = null);
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据).
|
||||
*
|
||||
* @param Model $result
|
||||
* @param string $relation
|
||||
* @param array $subRelation
|
||||
* @param Closure $closure
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null);
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据集).
|
||||
*
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
* @param bool $join 是否为JOIN方式
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = [], bool $join = false): void
|
||||
{
|
||||
if ($join) {
|
||||
// 模型JOIN关联组装
|
||||
foreach ($resultSet as $result) {
|
||||
$this->match($this->model, $relation, $result);
|
||||
}
|
||||
} else {
|
||||
// IN查询
|
||||
$this->eagerlySet($resultSet, $relation, $subRelation, $closure, $cache);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询(数据).
|
||||
*
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @param array $cache 关联缓存
|
||||
* @param bool $join 是否为JOIN方式
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null, array $cache = [], bool $join = false): void
|
||||
{
|
||||
if ($join) {
|
||||
// 模型JOIN关联组装
|
||||
$this->match($this->model, $relation, $result);
|
||||
} else {
|
||||
// IN查询
|
||||
$this->eagerlyOne($result, $relation, $subRelation, $closure, $cache);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增)当前关联数据对象
|
||||
*
|
||||
* @param array|Model $data 数据 可以使用数组 关联模型对象
|
||||
* @param bool $replace 是否自动识别更新和写入
|
||||
*
|
||||
* @return Model|false
|
||||
*/
|
||||
public function save(array | Model $data, bool $replace = true)
|
||||
{
|
||||
$model = $this->make();
|
||||
|
||||
return $model->replace($replace)->save($data) ? $model : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联对象实例.
|
||||
*
|
||||
* @param array|Model $data
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function make(array | Model $data = []): Model
|
||||
{
|
||||
if ($data instanceof Model) {
|
||||
$data = $data->getData();
|
||||
}
|
||||
|
||||
// 保存关联表数据
|
||||
$data[$this->foreignKey] = $this->parent->{$this->localKey};
|
||||
|
||||
return (new $this->model($data))->setSuffix($this->getModel()->getSuffix());
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定关联表的属性到父模型属性.
|
||||
*
|
||||
* @param array $attr 要绑定的属性列表
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function bind(array $attr)
|
||||
{
|
||||
$this->bindAttr = $attr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一对一 关联模型预查询拼装.
|
||||
*
|
||||
* @param string $model 模型名称
|
||||
* @param string $relation 关联名
|
||||
* @param Model $result 模型对象实例
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function match(string $model, string $relation, Model $result): void
|
||||
{
|
||||
$data = $result->getRelation($relation);
|
||||
if (!empty($data)) {
|
||||
if ($this->bindAttr) {
|
||||
$result->bindRelationAttr($data, $this->bindAttr);
|
||||
} else {
|
||||
$relationModel = new $model($data);
|
||||
$result->setRelation($relation, $relationModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 一对一 关联模型预查询(IN方式).
|
||||
*
|
||||
* @param array $where 关联预查询条件
|
||||
* @param string $key 关联键名
|
||||
* @param array $subRelation 子关联
|
||||
* @param Closure $closure
|
||||
* @param array $cache 关联缓存
|
||||
* @param bool $collection 是否数据集查询
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyWhere(array $where, string $key, array $subRelation = [], ?Closure $closure = null, array $cache = [], bool $collection = false)
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
if ($closure) {
|
||||
$this->baseQuery = true;
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
if ($collection) {
|
||||
$this->query->removeOption('limit');
|
||||
} else {
|
||||
$this->query->limit(1);
|
||||
}
|
||||
|
||||
$list = $this->query
|
||||
->where($where)
|
||||
->with($subRelation)
|
||||
->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null)
|
||||
->lazy();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
foreach ($list as $set) {
|
||||
if (!isset($data[$set->$key])) {
|
||||
$data[$set->$key] = $set;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
19
vendor/topthink/think-orm/src/model/type/Date.php
vendored
Normal file
19
vendor/topthink/think-orm/src/model/type/Date.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\type;
|
||||
|
||||
use think\model\contract\Modelable;
|
||||
|
||||
class Date extends DateTime
|
||||
{
|
||||
protected $data;
|
||||
|
||||
public static function from(mixed $value, Modelable $model)
|
||||
{
|
||||
$static = new static();
|
||||
$static->data($value, 'Y-m-d');
|
||||
return $static;
|
||||
}
|
||||
}
|
||||
74
vendor/topthink/think-orm/src/model/type/DateTime.php
vendored
Normal file
74
vendor/topthink/think-orm/src/model/type/DateTime.php
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\type;
|
||||
|
||||
use Stringable;
|
||||
use think\model\contract\Modelable;
|
||||
use think\model\contract\Typeable;
|
||||
|
||||
class DateTime implements Typeable
|
||||
{
|
||||
protected $data;
|
||||
protected $format;
|
||||
|
||||
public static function from(mixed $value, Modelable $model)
|
||||
{
|
||||
$static = new static();
|
||||
$static->data($value, $model->getDateFormat());
|
||||
return $static;
|
||||
}
|
||||
|
||||
public function data($time, $format)
|
||||
{
|
||||
if ($format) {
|
||||
if (class_exists($format)) {
|
||||
$time = $time instanceof $format ? $time : new $format($time);
|
||||
$this->format = 'Y-m-d H:i:s.u';
|
||||
} else {
|
||||
if (is_object($time)) {
|
||||
} elseif (is_numeric($time)) {
|
||||
$time = (new \DateTime())->setTimestamp((int) $time);
|
||||
} elseif (strpos('.', $time)) {
|
||||
$time = \DateTime::createFromFormat('Y-m-d H:i:s.u', $time);
|
||||
} else {
|
||||
$time = $time ? (new \DateTime($time)) : null;
|
||||
}
|
||||
$this->format = $format;
|
||||
}
|
||||
}
|
||||
$this->data = $time;
|
||||
}
|
||||
|
||||
public function setFormat(string $format)
|
||||
{
|
||||
$this->format = $format;
|
||||
}
|
||||
|
||||
public function format(string $format = '')
|
||||
{
|
||||
if ($this->data instanceof Stringable) {
|
||||
return $this->data->__toString();
|
||||
}
|
||||
|
||||
if (is_null($this->data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->data->format($format ?: $this->format);
|
||||
}
|
||||
|
||||
public function value()
|
||||
{
|
||||
return $this->format();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->value();
|
||||
}
|
||||
}
|
||||
43
vendor/topthink/think-orm/src/model/type/Json.php
vendored
Normal file
43
vendor/topthink/think-orm/src/model/type/Json.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace think\model\type;
|
||||
|
||||
use think\model\contract\Modelable;
|
||||
use think\model\contract\Typeable;
|
||||
|
||||
class Json implements Typeable
|
||||
{
|
||||
protected $data;
|
||||
|
||||
public static function from(mixed $value, Modelable $model)
|
||||
{
|
||||
$static = new static();
|
||||
$static->data($value, $model->isJsonAssoc());
|
||||
return $static;
|
||||
}
|
||||
|
||||
public function data($data, ?bool $assoc)
|
||||
{
|
||||
if (is_string($data) && json_validate($data)) {
|
||||
$data = json_decode($data, $assoc);
|
||||
} elseif (empty($data)) {
|
||||
$data = [];
|
||||
}
|
||||
$this->data = is_string($data) ? [$data] : $data;
|
||||
}
|
||||
|
||||
public function value()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this->data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user