添加网站文件

This commit is contained in:
2025-12-22 13:59:40 +08:00
commit 117aaf83d1
19468 changed files with 2111999 additions and 0 deletions

View File

@@ -0,0 +1,656 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\model\concern;
use InvalidArgumentException;
use think\db\Expression;
use think\Exception;
use think\Loader;
use think\model\Relation;
trait Attribute
{
/**
* 数据表主键 复合主键使用数组定义
* @var string|array
*/
protected $pk = 'id';
/**
* 数据表字段信息 留空则自动获取
* @var array
*/
protected $field = [];
/**
* JSON数据表字段
* @var array
*/
protected $json = [];
/**
* JSON数据取出是否需要转换为数组
* @var bool
*/
protected $jsonAssoc = false;
/**
* JSON数据表字段类型
* @var array
*/
protected $jsonType = [];
/**
* 数据表废弃字段
* @var array
*/
protected $disuse = [];
/**
* 数据表只读字段
* @var array
*/
protected $readonly = [];
/**
* 数据表字段类型
* @var array
*/
protected $type = [];
/**
* 当前模型数据
* @var array
*/
private $data = [];
/**
* 修改器执行记录
* @var array
*/
private $set = [];
/**
* 原始数据
* @var array
*/
private $origin = [];
/**
* 动态获取器
* @var array
*/
private $withAttr = [];
/**
* 获取模型对象的主键
* @access public
* @return string|array
*/
public function getPk()
{
return $this->pk;
}
/**
* 判断一个字段名是否为主键字段
* @access public
* @param string $key 名称
* @return bool
*/
protected function isPk($key)
{
$pk = $this->getPk();
if (is_string($pk) && $pk == $key) {
return true;
} elseif (is_array($pk) && in_array($key, $pk)) {
return true;
}
return false;
}
/**
* 获取模型对象的主键值
* @access public
* @return integer
*/
public function getKey()
{
$pk = $this->getPk();
if (is_string($pk) && array_key_exists($pk, $this->data)) {
return $this->data[$pk];
}
return;
}
/**
* 设置允许写入的字段
* @access public
* @param array|string|true $field 允许写入的字段 如果为true只允许写入数据表字段
* @return $this
*/
public function allowField($field)
{
if (is_string($field)) {
$field = explode(',', $field);
}
$this->field = $field;
return $this;
}
/**
* 设置只读字段
* @access public
* @param array|string $field 只读字段
* @return $this
*/
public function readonly($field)
{
if (is_string($field)) {
$field = explode(',', $field);
}
$this->readonly = $field;
return $this;
}
/**
* 设置数据对象值
* @access public
* @param mixed $data 数据或者属性名
* @param mixed $value 值
* @return $this
*/
public function data($data, $value = null)
{
if (is_string($data)) {
$this->data[$data] = $value;
return $this;
}
// 清空数据
$this->data = [];
if (is_object($data)) {
$data = get_object_vars($data);
}
if ($this->disuse) {
// 废弃字段
foreach ((array) $this->disuse as $key) {
if (array_key_exists($key, $data)) {
unset($data[$key]);
}
}
}
if (true === $value) {
// 数据对象赋值
foreach ($data as $key => $value) {
$this->setAttr($key, $value, $data);
}
} elseif (is_array($value)) {
foreach ($value as $name) {
if (isset($data[$name])) {
$this->data[$name] = $data[$name];
}
}
} else {
$this->data = $data;
}
return $this;
}
/**
* 批量设置数据对象值
* @access public
* @param mixed $data 数据
* @param bool $set 是否需要进行数据处理
* @return $this
*/
public function appendData($data, $set = false)
{
if ($set) {
// 进行数据处理
foreach ($data as $key => $value) {
$this->setAttr($key, $value, $data);
}
} else {
if (is_object($data)) {
$data = get_object_vars($data);
}
$this->data = array_merge($this->data, $data);
}
return $this;
}
/**
* 获取对象原始数据 如果不存在指定字段返回null
* @access public
* @param string $name 字段名 留空获取全部
* @return mixed
*/
public function getOrigin($name = null)
{
if (is_null($name)) {
return $this->origin;
}
return array_key_exists($name, $this->origin) ? $this->origin[$name] : null;
}
/**
* 获取对象原始数据 如果不存在指定字段返回false
* @access public
* @param string $name 字段名 留空获取全部
* @return mixed
* @throws InvalidArgumentException
*/
public function getData($name = null)
{
if (is_null($name)) {
return $this->data;
} elseif (array_key_exists($name, $this->data)) {
return $this->data[$name];
} elseif (array_key_exists($name, $this->relation)) {
return $this->relation[$name];
}
throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}
/**
* 获取变化的数据 并排除只读数据
* @access public
* @return array
*/
public function getChangedData()
{
if ($this->force) {
$data = $this->data;
} else {
$data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
if ((empty($a) || empty($b)) && $a !== $b) {
return 1;
}
return is_object($a) || $a != $b ? 1 : 0;
});
}
if (!empty($this->readonly)) {
// 只读字段不允许更新
foreach ($this->readonly as $key => $field) {
if (isset($data[$field])) {
unset($data[$field]);
}
}
}
return $data;
}
/**
* 修改器 设置数据对象值
* @access public
* @param string $name 属性名
* @param mixed $value 属性值
* @param array $data 数据
* @return void
*/
public function setAttr($name, $value, $data = [])
{
if (isset($this->set[$name])) {
return;
}
if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
// 自动写入的时间戳字段
$value = $this->autoWriteTimestamp($name);
} else {
// 检测修改器
$method = 'set' . Loader::parseName($name, 1) . 'Attr';
if (method_exists($this, $method)) {
$origin = $this->data;
$value = $this->$method($value, array_merge($this->data, $data));
$this->set[$name] = true;
if (is_null($value) && $origin !== $this->data) {
return;
}
} elseif (isset($this->type[$name])) {
// 类型转换
$value = $this->writeTransform($value, $this->type[$name]);
}
}
// 设置数据对象属性
$this->data[$name] = $value;
}
/**
* 是否需要自动写入时间字段
* @access public
* @param bool $auto
* @return $this
*/
public function isAutoWriteTimestamp($auto)
{
$this->autoWriteTimestamp = $auto;
return $this;
}
/**
* 自动写入时间戳
* @access protected
* @param string $name 时间戳字段
* @return mixed
*/
protected function autoWriteTimestamp($name)
{
if (isset($this->type[$name])) {
$type = $this->type[$name];
if (strpos($type, ':')) {
list($type, $param) = explode(':', $type, 2);
}
switch ($type) {
case 'datetime':
case 'date':
$value = $this->formatDateTime('Y-m-d H:i:s.u');
break;
case 'timestamp':
case 'integer':
default:
$value = time();
break;
}
} elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
'datetime',
'date',
'timestamp',
])) {
$value = $this->formatDateTime('Y-m-d H:i:s.u');
} else {
$value = time();
}
return $value;
}
/**
* 数据写入 类型转换
* @access protected
* @param mixed $value 值
* @param string|array $type 要转换的类型
* @return mixed
*/
protected function writeTransform($value, $type)
{
if (is_null($value)) {
return;
}
if ($value instanceof Expression) {
return $value;
}
if (is_array($type)) {
list($type, $param) = $type;
} elseif (strpos($type, ':')) {
list($type, $param) = explode(':', $type, 2);
}
switch ($type) {
case 'integer':
$value = (int) $value;
break;
case 'float':
if (empty($param)) {
$value = (float) $value;
} else {
$value = (float) number_format($value, $param, '.', '');
}
break;
case 'boolean':
$value = (bool) $value;
break;
case 'timestamp':
if (!is_numeric($value)) {
$value = strtotime($value);
}
break;
case 'datetime':
$value = is_numeric($value) ? $value : strtotime($value);
$value = $this->formatDateTime('Y-m-d H:i:s.u', $value);
break;
case 'object':
if (is_object($value)) {
$value = json_encode($value, JSON_FORCE_OBJECT);
}
break;
case 'array':
$value = (array) $value;
case 'json':
$option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE;
$value = json_encode($value, $option);
break;
case 'serialize':
$value = serialize($value);
break;
}
return $value;
}
/**
* 获取器 获取数据对象的值
* @access public
* @param string $name 名称
* @param array $item 数据
* @return mixed
* @throws InvalidArgumentException
*/
public function getAttr($name, &$item = null)
{
try {
$notFound = false;
$value = $this->getData($name);
} catch (InvalidArgumentException $e) {
$notFound = true;
$value = null;
}
// 检测属性获取器
$fieldName = Loader::parseName($name);
$method = 'get' . Loader::parseName($name, 1) . 'Attr';
if (isset($this->withAttr[$fieldName])) {
if ($notFound && $relation = $this->isRelationAttr($name)) {
$modelRelation = $this->$relation();
$value = $this->getRelationData($modelRelation);
}
$closure = $this->withAttr[$fieldName];
$value = $closure($value, $this->data);
} elseif (method_exists($this, $method)) {
if ($notFound && $relation = $this->isRelationAttr($name)) {
$modelRelation = $this->$relation();
$value = $this->getRelationData($modelRelation);
}
$value = $this->$method($value, $this->data);
} elseif (isset($this->type[$name])) {
// 类型转换
$value = $this->readTransform($value, $this->type[$name]);
} elseif ($this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
'datetime',
'date',
'timestamp',
])) {
$value = $this->formatDateTime($this->dateFormat, $value);
} else {
$value = $this->formatDateTime($this->dateFormat, $value, true);
}
} elseif ($notFound) {
$value = $this->getRelationAttribute($name, $item);
}
return $value;
}
/**
* 获取关联属性值
* @access protected
* @param string $name 属性名
* @param array $item 数据
* @return mixed
*/
protected function getRelationAttribute($name, &$item)
{
$relation = $this->isRelationAttr($name);
if ($relation) {
$modelRelation = $this->$relation();
if ($modelRelation instanceof Relation) {
$value = $this->getRelationData($modelRelation);
if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) {
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($item[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$item[$key] = $value ? $value->getAttr($attr) : null;
}
}
return false;
}
// 保存关联对象值
$this->relation[$name] = $value;
return $value;
}
}
throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}
/**
* 数据读取 类型转换
* @access protected
* @param mixed $value 值
* @param string|array $type 要转换的类型
* @return mixed
*/
protected function readTransform($value, $type)
{
if (is_null($value)) {
return;
}
if (is_array($type)) {
list($type, $param) = $type;
} elseif (strpos($type, ':')) {
list($type, $param) = explode(':', $type, 2);
}
switch ($type) {
case 'integer':
$value = (int) $value;
break;
case 'float':
if (empty($param)) {
$value = (float) $value;
} else {
$value = (float) number_format($value, $param, '.', '');
}
break;
case 'boolean':
$value = (bool) $value;
break;
case 'timestamp':
if (!is_null($value)) {
$format = !empty($param) ? $param : $this->dateFormat;
$value = $this->formatDateTime($format, $value, true);
}
break;
case 'datetime':
if (!is_null($value)) {
$format = !empty($param) ? $param : $this->dateFormat;
$value = $this->formatDateTime($format, $value);
}
break;
case 'json':
$value = json_decode($value, true);
break;
case 'array':
$value = empty($value) ? [] : json_decode($value, true);
break;
case 'object':
$value = empty($value) ? new \stdClass() : json_decode($value);
break;
case 'serialize':
try {
$value = unserialize($value);
} catch (\Exception $e) {
$value = null;
}
break;
default:
if (false !== strpos($type, '\\')) {
// 对象类型
$value = new $type($value);
}
}
return $value;
}
/**
* 设置数据字段获取器
* @access public
* @param string|array $name 字段名
* @param callable $callback 闭包获取器
* @return $this
*/
public function withAttribute($name, $callback = null)
{
if (is_array($name)) {
foreach ($name as $key => $val) {
$key = Loader::parseName($key);
$this->withAttr[$key] = $val;
}
} else {
$name = Loader::parseName($name);
$this->withAttr[$name] = $callback;
}
return $this;
}
}

View File

@@ -0,0 +1,273 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\model\concern;
use think\Collection;
use think\Exception;
use think\Loader;
use think\Model;
use think\model\Collection as ModelCollection;
/**
* 模型数据转换处理
*/
trait Conversion
{
/**
* 数据输出显示的属性
* @var array
*/
protected $visible = [];
/**
* 数据输出隐藏的属性
* @var array
*/
protected $hidden = [];
/**
* 数据输出需要追加的属性
* @var array
*/
protected $append = [];
/**
* 数据集对象名
* @var string
*/
protected $resultSetType;
/**
* 设置需要附加的输出属性
* @access public
* @param array $append 属性列表
* @param bool $override 是否覆盖
* @return $this
*/
public function append(array $append = [], $override = false)
{
$this->append = $override ? $append : array_merge($this->append, $append);
return $this;
}
/**
* 设置附加关联对象的属性
* @access public
* @param string $attr 关联属性
* @param string|array $append 追加属性名
* @return $this
* @throws Exception
*/
public function appendRelationAttr($attr, $append)
{
if (is_string($append)) {
$append = explode(',', $append);
}
$relation = Loader::parseName($attr, 1, false);
if (isset($this->relation[$relation])) {
$model = $this->relation[$relation];
} else {
$model = $this->getRelationData($this->$relation());
}
if ($model instanceof Model) {
foreach ($append as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$this->data[$key] = $model->getAttr($attr);
}
}
}
return $this;
}
/**
* 设置需要隐藏的输出属性
* @access public
* @param array $hidden 属性列表
* @param bool $override 是否覆盖
* @return $this
*/
public function hidden(array $hidden = [], $override = false)
{
$this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden);
return $this;
}
/**
* 设置需要输出的属性
* @access public
* @param array $visible
* @param bool $override 是否覆盖
* @return $this
*/
public function visible(array $visible = [], $override = false)
{
$this->visible = $override ? $visible : array_merge($this->visible, $visible);
return $this;
}
/**
* 转换当前模型对象为数组
* @access public
* @return array
*/
public function toArray()
{
$item = [];
$hasVisible = false;
foreach ($this->visible as $key => $val) {
if (is_string($val)) {
if (strpos($val, '.')) {
list($relation, $name) = explode('.', $val);
$this->visible[$relation][] = $name;
} else {
$this->visible[$val] = true;
$hasVisible = true;
}
unset($this->visible[$key]);
}
}
foreach ($this->hidden as $key => $val) {
if (is_string($val)) {
if (strpos($val, '.')) {
list($relation, $name) = explode('.', $val);
$this->hidden[$relation][] = $name;
} else {
$this->hidden[$val] = true;
}
unset($this->hidden[$key]);
}
}
// 合并关联数据
$data = array_merge($this->data, $this->relation);
foreach ($data as $key => $val) {
if ($val instanceof Model || $val instanceof ModelCollection) {
// 关联模型对象
if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
$val->visible($this->visible[$key]);
} elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
$val->hidden($this->hidden[$key]);
}
// 关联模型对象
if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
$item[$key] = $val->toArray();
}
} elseif (isset($this->visible[$key])) {
$item[$key] = $this->getAttr($key);
} elseif (!isset($this->hidden[$key]) && !$hasVisible) {
$item[$key] = $this->getAttr($key);
}
}
// 追加属性(必须定义获取器)
if (!empty($this->append)) {
foreach ($this->append as $key => $name) {
if (is_array($name)) {
// 追加关联对象属性
$relation = $this->getRelation($key);
if (!$relation) {
$relation = $this->getAttr($key);
if ($relation) {
$relation->visible($name);
}
}
$item[$key] = $relation ? $relation->append($name)->toArray() : [];
} elseif (strpos($name, '.')) {
list($key, $attr) = explode('.', $name);
// 追加关联对象属性
$relation = $this->getRelation($key);
if (!$relation) {
$relation = $this->getAttr($key);
if ($relation) {
$relation->visible([$attr]);
}
}
$item[$key] = $relation ? $relation->append([$attr])->toArray() : [];
} else {
$item[$name] = $this->getAttr($name, $item);
}
}
}
return $item;
}
/**
* 转换当前模型对象为JSON字符串
* @access public
* @param integer $options json参数
* @return string
*/
public function toJson($options = JSON_UNESCAPED_UNICODE)
{
return json_encode($this->toArray(), $options);
}
/**
* 移除当前模型的关联属性
* @access public
* @return $this
*/
public function removeRelation()
{
$this->relation = [];
return $this;
}
public function __toString()
{
return $this->toJson();
}
// JsonSerializable
public function jsonSerialize()
{
return $this->toArray();
}
/**
* 转换数据集为数据集对象
* @access public
* @param array|Collection $collection 数据集
* @param string $resultSetType 数据集类
* @return Collection
*/
public function toCollection($collection, $resultSetType = null)
{
$resultSetType = $resultSetType ?: $this->resultSetType;
if ($resultSetType && false !== strpos($resultSetType, '\\')) {
$collection = new $resultSetType($collection);
} else {
$collection = new ModelCollection($collection);
}
return $collection;
}
}

View File

@@ -0,0 +1,238 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\model\concern;
use think\Container;
use think\Loader;
/**
* 模型事件处理
*/
trait ModelEvent
{
/**
* 模型回调
* @var array
*/
private static $event = [];
/**
* 模型事件观察
* @var array
*/
protected static $observe = ['before_write', 'after_write', 'before_insert', 'after_insert', 'before_update', 'after_update', 'before_delete', 'after_delete', 'before_restore', 'after_restore'];
/**
* 绑定模型事件观察者类
* @var array
*/
protected $observerClass;
/**
* 是否需要事件响应
* @var bool
*/
private $withEvent = true;
/**
* 注册回调方法
* @access public
* @param string $event 事件名
* @param callable $callback 回调方法
* @param bool $override 是否覆盖
* @return void
*/
public static function event($event, $callback, $override = false)
{
$class = static::class;
if ($override) {
self::$event[$class][$event] = [];
}
self::$event[$class][$event][] = $callback;
}
/**
* 清除回调方法
* @access public
* @return void
*/
public static function flushEvent()
{
self::$event[static::class] = [];
}
/**
* 注册一个模型观察者
*
* @param object|string $class
* @return void
*/
public static function observe($class)
{
self::flushEvent();
foreach (static::$observe as $event) {
$eventFuncName = Loader::parseName($event, 1, false);
if (method_exists($class, $eventFuncName)) {
static::event($event, [$class, $eventFuncName]);
}
}
}
/**
* 当前操作的事件响应
* @access protected
* @param bool $event 是否需要事件响应
* @return $this
*/
public function withEvent($event)
{
$this->withEvent = $event;
return $this;
}
/**
* 触发事件
* @access protected
* @param string $event 事件名
* @return bool
*/
protected function trigger($event)
{
$class = static::class;
if ($this->withEvent && isset(self::$event[$class][$event])) {
foreach (self::$event[$class][$event] as $callback) {
$result = Container::getInstance()->invoke($callback, [$this]);
if (false === $result) {
return false;
}
}
}
return true;
}
/**
* 模型before_insert事件快捷方法
* @access protected
* @param callable $callback
* @param bool $override
*/
protected static function beforeInsert($callback, $override = false)
{
self::event('before_insert', $callback, $override);
}
/**
* 模型after_insert事件快捷方法
* @access protected
* @param callable $callback
* @param bool $override
*/
protected static function afterInsert($callback, $override = false)
{
self::event('after_insert', $callback, $override);
}
/**
* 模型before_update事件快捷方法
* @access protected
* @param callable $callback
* @param bool $override
*/
protected static function beforeUpdate($callback, $override = false)
{
self::event('before_update', $callback, $override);
}
/**
* 模型after_update事件快捷方法
* @access protected
* @param callable $callback
* @param bool $override
*/
protected static function afterUpdate($callback, $override = false)
{
self::event('after_update', $callback, $override);
}
/**
* 模型before_write事件快捷方法
* @access protected
* @param callable $callback
* @param bool $override
*/
protected static function beforeWrite($callback, $override = false)
{
self::event('before_write', $callback, $override);
}
/**
* 模型after_write事件快捷方法
* @access protected
* @param callable $callback
* @param bool $override
*/
protected static function afterWrite($callback, $override = false)
{
self::event('after_write', $callback, $override);
}
/**
* 模型before_delete事件快捷方法
* @access protected
* @param callable $callback
* @param bool $override
*/
protected static function beforeDelete($callback, $override = false)
{
self::event('before_delete', $callback, $override);
}
/**
* 模型after_delete事件快捷方法
* @access protected
* @param callable $callback
* @param bool $override
*/
protected static function afterDelete($callback, $override = false)
{
self::event('after_delete', $callback, $override);
}
/**
* 模型before_restore事件快捷方法
* @access protected
* @param callable $callback
* @param bool $override
*/
protected static function beforeRestore($callback, $override = false)
{
self::event('before_restore', $callback, $override);
}
/**
* 模型after_restore事件快捷方法
* @access protected
* @param callable $callback
* @param bool $override
*/
protected static function afterRestore($callback, $override = false)
{
self::event('after_restore', $callback, $override);
}
}

View File

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

View File

@@ -0,0 +1,246 @@
<?php
namespace think\model\concern;
use think\db\Query;
/**
* 数据软删除
*/
trait SoftDelete
{
/**
* 是否包含软删除数据
* @var bool
*/
protected $withTrashed = false;
/**
* 判断当前实例是否被软删除
* @access public
* @return boolean
*/
public function trashed()
{
$field = $this->getDeleteTimeField();
if ($field && !empty($this->getOrigin($field))) {
return true;
}
return false;
}
/**
* 查询软删除数据
* @access public
* @return Query
*/
public static function withTrashed()
{
$model = new static();
return $model->withTrashedData(true)->db(false);
}
/**
* 是否包含软删除数据
* @access protected
* @param bool $withTrashed 是否包含软删除数据
* @return $this
*/
protected function withTrashedData($withTrashed)
{
$this->withTrashed = $withTrashed;
return $this;
}
/**
* 只查询软删除数据
* @access public
* @return Query
*/
public static function onlyTrashed()
{
$model = new static();
$field = $model->getDeleteTimeField(true);
if ($field) {
return $model
->db(false)
->useSoftDelete($field, $model->getWithTrashedExp());
}
return $model->db(false);
}
/**
* 获取软删除数据的查询条件
* @access protected
* @return array
*/
protected function getWithTrashedExp()
{
return is_null($this->defaultSoftDelete) ?
['notnull', ''] : ['<>', $this->defaultSoftDelete];
}
/**
* 删除当前的记录
* @access public
* @return bool
*/
public function delete($force = false)
{
if (!$this->isExists() || false === $this->trigger('before_delete', $this)) {
return false;
}
$force = $force ?: $this->isForce();
$name = $this->getDeleteTimeField();
if ($name && !$force) {
// 软删除
$this->data($name, $this->autoWriteTimestamp($name));
$result = $this->isUpdate()->withEvent(false)->save();
$this->withEvent(true);
} else {
// 读取更新条件
$where = $this->getWhere();
// 删除当前模型数据
$result = $this->db(false)
->where($where)
->removeOption('soft_delete')
->delete();
}
// 关联删除
if (!empty($this->relationWrite)) {
$this->autoRelationDelete();
}
$this->trigger('after_delete', $this);
$this->exists(false);
return true;
}
/**
* 删除记录
* @access public
* @param mixed $data 主键列表 支持闭包查询条件
* @param bool $force 是否强制删除
* @return bool
*/
public static function destroy($data, $force = false)
{
// 传入空不执行删除但是0可以删除
if (empty($data) && 0 !== $data) {
return false;
}
// 包含软删除数据
$query = (new static())->db(false);
if (is_array($data) && key($data) !== 0) {
$query->where($data);
$data = null;
} elseif ($data instanceof \Closure) {
call_user_func_array($data, [ & $query]);
$data = null;
} elseif (is_null($data)) {
return false;
}
$resultSet = $query->select($data);
if ($resultSet) {
foreach ($resultSet as $data) {
$data->force($force)->delete();
}
}
return true;
}
/**
* 恢复被软删除的记录
* @access public
* @param array $where 更新条件
* @return bool
*/
public function restore($where = [])
{
$name = $this->getDeleteTimeField();
if ($name) {
if (false === $this->trigger('before_restore')) {
return false;
}
if (empty($where)) {
$pk = $this->getPk();
$where[] = [$pk, '=', $this->getData($pk)];
}
// 恢复删除
$this->db(false)
->where($where)
->useSoftDelete($name, $this->getWithTrashedExp())
->update([$name => $this->defaultSoftDelete]);
$this->trigger('after_restore');
return true;
}
return false;
}
/**
* 获取软删除字段
* @access protected
* @param bool $read 是否查询操作 写操作的时候会自动去掉表别名
* @return string|false
*/
protected function getDeleteTimeField($read = false)
{
$field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time';
if (false === $field) {
return false;
}
if (false === strpos($field, '.')) {
$field = '__TABLE__.' . $field;
}
if (!$read && strpos($field, '.')) {
$array = explode('.', $field);
$field = array_pop($array);
}
return $field;
}
/**
* 查询的时候默认排除软删除数据
* @access protected
* @param Query $query
* @return void
*/
protected function withNoTrashed($query)
{
$field = $this->getDeleteTimeField(true);
if ($field) {
$condition = is_null($this->defaultSoftDelete) ? ['null', ''] : ['=', $this->defaultSoftDelete];
$query->useSoftDelete($field, $condition);
}
}
}

View File

@@ -0,0 +1,92 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\model\concern;
use DateTime;
/**
* 自动时间戳
*/
trait TimeStamp
{
/**
* 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型
* @var bool|string
*/
protected $autoWriteTimestamp;
/**
* 创建时间字段 false表示关闭
* @var false|string
*/
protected $createTime = 'create_time';
/**
* 更新时间字段 false表示关闭
* @var false|string
*/
protected $updateTime = 'update_time';
/**
* 时间字段显示格式
* @var string
*/
protected $dateFormat;
/**
* 时间日期字段格式化处理
* @access protected
* @param mixed $format 日期格式
* @param mixed $time 时间日期表达式
* @param bool $timestamp 是否进行时间戳转换
* @return mixed
*/
protected function formatDateTime($format, $time = 'now', $timestamp = false)
{
if (empty($time)) {
return;
}
if (false === $format) {
return $time;
} elseif (false !== strpos($format, '\\')) {
return new $format($time);
}
if ($timestamp) {
$dateTime = new DateTime();
$dateTime->setTimestamp($time);
} else {
$dateTime = new DateTime($time);
}
return $dateTime->format($format);
}
/**
* 检查时间字段写入
* @access protected
* @return void
*/
protected function checkTimeStampWrite()
{
// 自动写入创建时间和更新时间
if ($this->autoWriteTimestamp) {
if ($this->createTime && !isset($this->data[$this->createTime])) {
$this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime);
}
if ($this->updateTime && !isset($this->data[$this->updateTime])) {
$this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
}
}
}
}