Files
OA/vendor/topthink/think-orm/src/Model.php
T
2026-07-23 10:37:26 +08:00

905 lines
24 KiB
PHP

<?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;
use ArrayAccess;
use Closure;
use JsonSerializable;
use think\contract\Arrayable;
use think\contract\Jsonable;
use think\db\BaseQuery as Query;
use think\db\Express;
use think\exception\ValidateException;
use think\model\Collection;
use think\model\contract\Modelable;
use think\model\View;
use WeakMap;
/**
* Class Model.
* @mixin \think\db\Query
*
* @method static void onAfterRead(Model $model) after_read事件定义
* @method static mixed onBeforeInsert(Model $model) before_insert事件定义
* @method static void onAfterInsert(Model $model) after_insert事件定义
* @method static mixed onBeforeUpdate(Model $model) before_update事件定义
* @method static void onAfterUpdate(Model $model) after_update事件定义
* @method static mixed onBeforeWrite(Model $model) before_write事件定义
* @method static void onAfterWrite(Model $model) after_write事件定义
* @method static mixed onBeforeDelete(Model $model) before_write事件定义
* @method static void onAfterDelete(Model $model) after_delete事件定义
* @method static void onBeforeRestore(Model $model) before_restore事件定义
* @method static void onAfterRestore(Model $model) after_restore事件定义
*/
abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonable, Modelable
{
use model\concern\Attribute;
use model\concern\AutoWriteData;
use model\concern\Conversion;
use model\concern\DbConnect;
use model\concern\ModelEvent;
use model\concern\RelationShip;
private static ?WeakMap $weakMap = null;
/**
* 服务注入.
*
* @var Closure[]
*/
protected static array $_maker = [];
/**
* 设置服务注入.
*/
public static function maker(Closure $maker): void
{
static::$_maker[] = $maker;
}
/**
* 设置容器对象的依赖注入方法.(用于兼容)
*
* @param callable $callable 依赖注入方法
*
* @return void
*/
public static function setInvoker(callable $callable): void
{
}
/**
* 调用反射执行模型方法 支持参数绑定.
*
* @param mixed $method
* @param array $vars 参数
*
* @return mixed
*/
public function invoke($method, array $vars = [])
{
if (is_string($method)) {
$method = [$this, $method];
}
$invoker = $this->getOption('invoker');
if ($invoker) {
return $invoker($method instanceof Closure ? $method : Closure::fromCallable($method), $vars);
}
return call_user_func_array($method, $vars);
}
/**
* 架构函数.
*
* @param array|object $data 实体模型数据
*/
public function __construct(array | object $data = [])
{
// 获取实体模型参数
$options = array_merge($this->getBaseOptions(), $this->getOptions());
if (!self::$weakMap) {
self::$weakMap = new WeakMap;
}
self::$weakMap[$this] = [
'get' => [],
'data' => [],
'origin' => [],
'relation' => [],
'together' => [],
'allow' => [],
'withAttr' => [],
'schema' => $options['schema'] ?? [],
'updateTime' => $options['updateTime'] ?? 'update_time',
'createTime' => $options['createTime'] ?? 'create_time',
'suffix' => $options['suffix'] ?? '',
'validate' => $options['validate'] ?? $this->parseValidate(),
'type' => $options['type'] ?? [],
'readonly' => $options['readonly'] ?? [],
'disuse' => $options['disuse'] ?? [],
'hidden' => $options['hidden'] ?? [],
'visible' => $options['visible'] ?? [],
'append' => $options['append'] ?? [],
'mapping' => $options['mapping'] ?? [],
'strict' => $options['strict'] ?? true,
'bindAttr' => $options['bindAttr'] ?? [],
'autoRelation' => $options['autoRelation'] ?? [],
];
// 设置额外参数
$this->setOptions(array_diff_key($options, self::$weakMap[$this]));
// 模型初始化
$this->initialize();
// 初始化数据
$this->initializeData($data);
}
protected function initialize()
{
if (!empty(static::$_maker)) {
foreach (static::$_maker as $maker) {
call_user_func($maker, $this);
}
}
// 初始化模型
$this->init();
}
/**
* 初始化模型.
*
* @return void
*/
protected function init()
{}
/**
* 定义基础配置参数.
*
* @return array
*/
protected function getBaseOptions(): array
{
return [];
}
/**
* 在实体模型中定义 返回相关配置参数.
*
* @return array
*/
protected function getOptions(): array
{
return [];
}
/**
* 批量设置模型参数
* @param array $options 值
* @return void
*/
public function setOptions(array $options): void
{
foreach ($options as $name => $value) {
$this->setOption($name, $value);
}
}
/**
* 设置模型参数
*
* @param string $name 参数名
* @param mixed $value 值
*
* @return $this
*/
public function setOption(string $name, $value)
{
self::$weakMap[$this][$name] = $value;
if (property_exists($this, $name)) {
$this->$name = $value;
}
return $this;
}
/**
* 获取模型参数
*
* @param string $name 参数名
* @param mixed $default 默认值
*
* @return mixed
*/
public function getOption(string $name, $default = null)
{
// 兼容读取3.0版本的属性参数定义
if (property_exists($this, $name) && isset($this->$name)) {
return $this->$name;
}
return self::$weakMap[$this][$name] ?? $default;
}
private function setWeakData(string $key, string $name, $value): void
{
self::$weakMap[$this][$key][$name] = $value;
}
private function getWeakData(string $key, string $name, $default = null)
{
return self::$weakMap[$this][$key][$name] ?? $default;
}
/**
* 创建新的模型实例.
*
* @param array|object $data
* @param array $options
*
* @return Model|Entity
*/
public function newInstance(array | object $data = [], array $options = [])
{
$model = new static($data);
if (!empty($data)) {
$model->exists(true);
}
if ($this->getEntity()) {
// 存在对应实体模型实例
return $this->getEntity()->newInstance($model, $options);
}
return $this->fetchModel($model, $options);
}
/**
* 获取克隆的模型实例.
*
* @return static
*/
public function clone()
{
$model = new static();
self::$weakMap[$model] = self::$weakMap[$this];
return $model;
}
/**
* 获取实际模型实例.
*
* @param Model $model
*
* @return Modelable
*/
protected function fetchModel(Model $model, array $options = []): Modelable
{
$class = $model->getOption('entityClass', str_replace('\\model\\', '\\entity\\', static::class));
if (class_exists($class) && is_subclass_of($class, Entity::class)) {
return new $class($model, $options);
}
return $model;
}
public function entity(Entity $entity): void
{
$this->setOption('entity', $entity);
}
public function getEntity(): ?Entity
{
return $this->getOption('entity');
}
/**
* 解析对应验证类.
*
* @return string
*/
protected function parseValidate(): string
{
$auto = $this->getOption('autoValidate', false);
$validate = $auto && str_contains(static::class, '\\model\\') ? str_replace('\\model\\', '\\validate\\', static::class) : '';
return $validate && class_exists($validate) ? $validate : '';
}
/**
* 设置验证场景.
*
* @param string|array $scene 场景名或数组
* @return $this
*/
public function scene(string|array $scene)
{
return $this->setOption('scene', $scene);
}
/**
* 验证模型数据.
*
* @param array $data 数据
* @param array $allow 需要验证的字段
*
* @throws ValidateException
* @return void
*/
protected function validate(array $data, array $allow = []): void
{
$validater = $this->getOption('validate');
if (!empty($validater)) {
validate($validater)
->scene($this->getOption('scene') ?: $allow)
->check($data);
}
}
/**
* 保存模型实例数据.
*
* @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 (!empty($data)) {
// 初始化模型数据
$this->initializeData($data, true);
}
if (false === $this->trigger('BeforeWrite')) {
return false;
}
if (true === $where) {
$isUpdate = false;
$where = [];
} elseif (!empty($where)) {
$isUpdate = true;
} else {
$isUpdate = $this->isExists() ? true : false;
}
if (false === $this->trigger($isUpdate ? 'BeforeUpdate' : 'BeforeInsert')) {
return false;
}
[$data, $relations, $allow] = $this->validateAndFilterData($isUpdate);
if (empty($data)) {
// 保存关联数据
if ($isUpdate && $this->getOption('together')) {
$this->relationSave($relations, $isUpdate);
}
return true;
}
// 自动写入数据
$this->autoWriteData($data, $isUpdate, $allow);
$db = $this->getDbWhere($where);
$result = $db->field($allow)
->removeOption('data')
->save($data, !$isUpdate);
if (!$isUpdate) {
$this->exists(true);
// 写入自增键值
$key = $db->getAutoInc();
$val = $db->getLastInsID();
if ($key && $val) {
$this->setData($key, $val);
}
} elseif ($refresh) {
// 刷新数据
$this->refresh();
}
$this->trigger($isUpdate ? 'AfterUpdate' : 'AfterInsert');
$this->trigger('AfterWrite');
// 保存关联数据
if ($this->getOption('together')) {
$this->relationSave($relations, $isUpdate);
}
// 重置原始数据
$this->refreshOrigin();
return true;
}
/**
* 验证和过滤数据
* @param bool $isUpdate 是否更新
* @return array [$data, $relations, $allow]
*/
protected function validateAndFilterData(bool $isUpdate): array
{
$data = $this->getData();
$origin = $this->getOrigin();
$allow = $this->getOption('allow') ?: array_keys($this->getFields());
$readonly = $this->getOption('readonly');
$disuse = $this->getOption('disuse');
$allow = array_diff($allow, $disuse, $isUpdate ? $readonly : []);
$together = $this->getOption('together');
// 验证数据
$this->validate($data, $allow);
$relations = [];
foreach ($data as $name => &$val) {
if ($val instanceof Modelable || in_array($name, $together)) {
$relations[$name] = $val;
unset($data[$name]);
} elseif ($val instanceof Collection || (!empty($allow) && !in_array($name, $allow))) {
unset($data[$name]);
} elseif ($isUpdate && !$this->isForce() && $this->isNotRequireUpdate($name, $val, $origin)) {
unset($data[$name]);
} else {
$val = $this->setAttrOfWith($name, $val);
}
}
return [$data, $relations, $allow];
}
/**
* 数据检查.
* @param array $data 数据
* @param bool $isUpdate 是否更新
* @return void
*/
protected function checkData(array &$data, bool $isUpdate): void
{
}
protected function getDbWhere($where = [])
{
$db = $this->db();
// 检查条件
if (!empty($where)) {
$db->where($where);
} elseif ($this->getKey()) {
$db->setKey($this->getKey());
} else {
$db->where($this->getOrigin());
}
if ($this->isForce()) {
$db->removeOption('soft_delete');
}
return $db;
}
/**
* 检查字段是否有更新(主键无需更新).
*
* @param string $name 字段
* @param mixed $val 值
* @param array $origin 原始数据
* @return bool
*/
protected function isNotRequireUpdate(string $name, $val, array $origin): bool
{
return (array_key_exists($name, $origin) && $val === $origin[$name]) || $this->getPk() == $name;
}
/**
* 获取更新数据.
*
* @return array
*/
public function getChangedData(): array
{
$data = $this->getData();
$origin = $this->getOrigin();
$change = [];
foreach ($data as $name => $val) {
if (!array_key_exists($name, $origin) || $val !== $origin[$name]) {
$change[$name] = $val;
}
}
return $change;
}
/**
* 判断数据是否有更新.
*
* @param string $name 字段
* @return bool
*/
public function isChange(string $name): bool
{
return $this->getData($name) !== $this->getOrigin($name);
}
/**
* 刷新模型数据.
*
* @return static
*/
public function refresh()
{
if ($this->isExists() && $this->getKey()) {
$data = $this->db()->find($this->getKey())->getData();
$this->data($data);
}
return $this;
}
/**
* 保存多个数据到当前数据对象
*
* @param iterable $dataSet 数据
* @param bool $replace 是否replace
*
* @return Collection
*/
public static function saveAll(iterable $dataSet, bool $replace = true): Collection
{
$result = [];
$model = new static;
$pk = $model->getPk();
foreach ($dataSet as $key => $data) {
$model = new static;
if ($replace) {
$exists = true;
foreach ((array) $pk as $field) {
if (is_string($field) && !isset($data[$field])) {
$exists = false;
}
}
} else {
$exists = false;
}
$model->replace($replace)->exists($exists)->save($data);
$result[$key] = $model->fetchModel($model);
}
return $model->toCollection($result);
}
/**
* 删除模型数据.
*
* @return bool
*/
public function delete(): bool
{
if ($this->isEmpty() || false === $this->trigger('BeforeDelete')) {
return false;
}
foreach ($this->getData() as $name => $val) {
if ($val instanceof Model || $val instanceof Collection) {
$relations[$name] = $val;
}
}
$result = $this->getDbWhere()->delete();
$this->trigger('AfterDelete');
if ($result && !empty($relations)) {
// 删除关联数据
$this->relationDelete($relations);
}
$this->exists(false);
$this->clear();
return true;
}
/**
* 写入数据.
*
* @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($suffix)) {
$model->setSuffix($suffix);
}
$model->allowField($allowField)->replace($replace)->save($data, true);
return $model->fetchModel($model);
}
/**
* 更新数据.
*
* @param array|object $data 数据
* @param mixed $where 更新条件
* @param array $allowField 允许字段
* @param string $suffix 数据表后缀
* @param bool $refresh 是否刷新数据
* @return Modelable
*/
public static function update(array | object $data, $where = [], array $allowField = [], string $suffix = '', bool $refresh = false): Modelable
{
$model = new static();
if (!empty($suffix)) {
$model->setSuffix($suffix);
}
$model->allowField($allowField)->exists(true)->save($data, $where, $refresh);
return $model->fetchModel($model);
}
/**
* 删除记录.
*
* @param mixed $data 主键列表 支持闭包查询条件
* @param bool $force 是否强制删除
* @param array $together 关联删除
*
* @return bool
*/
public static function destroy($data, bool $force = false, array $together = []): bool
{
if (empty($data) && 0 !== $data) {
return false;
}
$db = (new static())->db();
if (is_array($data) && key($data) !== 0) {
$db->where($data);
$data = [];
} elseif ($data instanceof Closure) {
$data($db);
$data = [];
}
$resultSet = $db->select((array) $data);
foreach ($resultSet as $result) {
$result->force($force)->together($together)->delete();
}
return true;
}
/**
* 字段值增长
*
* @param string $field 字段名
* @param float|int $step 增长值
* @param int $lazyTime 延迟时间(秒)
*
* @return $this
*/
public function inc(string $field, float|int $step = 1, int $lazyTime = 0)
{
return $this->set($field, new Express('+', $step, $lazyTime));
}
/**
* 字段值减少.
*
* @param string $field 字段名
* @param float|int $step 增长值
* @param int $lazyTime 延迟时间(秒)
*
* @return $this
*/
public function dec(string $field, float|int $step = 1, int $lazyTime = 0)
{
return $this->set($field, new Express('-', $step, $lazyTime));
}
/**
* 查询缓存 数据为空不缓存.
*
* @param mixed $key 缓存key
* @param int|\DateTime $expire 缓存有效期
* @param string|array $tag 缓存标签
*
* @return $this
*/
public function setCache($key = true, $expire = null, $tag = null)
{
return $this->setOption('cache', [$key, $expire, $tag]);
}
/**
* 允许写入字段.
*
* @param array $allow 允许字段
*
* @return $this
*/
public function allowField(array $allow)
{
return $this->setOption('allow', $allow);
}
/**
* 动态设置只读字段.
*
* @param array $fields 只读字段
*
* @return $this
*/
public function readonly(array $fields)
{
return $this->setOption('readonly', $fields);
}
/**
* 强制写入或删除
*
* @param bool $force 强制更新
*
* @return $this
*/
public function force(bool $force = true)
{
return $this->setOption('force', $force);
}
/**
* 判断数据是否强制写入或删除.
*
* @return bool
*/
public function isForce(): bool
{
return $this->getOption('force', false);
}
/**
* 获取属性 支持获取器
*
* @param string $name 名称
*
* @return mixed
*/
public function __get(string $name)
{
return $this->get($name);
}
/**
* 设置数据 支持类型自动转换
*
* @param string $name 名称
* @param mixed $value 值
*
* @return void
*/
public function __set(string $name, $value): void
{
if ($value instanceof Modelable && $bind = $this->getAttrOfBind($this->getOption('bindAttr'), $name)) {
// 关联属性绑定
$this->bindRelationAttr($value, $bind);
} else {
$this->set($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
{
$name = $this->getRealFieldName($name);
$this->setWeakData('data', $name, null);
$this->setWeakData('get', $name, null);
}
public function __toString()
{
return $this->toJson();
}
public function __debugInfo()
{
return [
'data' => $this->getOption('data'),
'origin' => $this->getOption('origin'),
'schema' => $this->getOption('schema'),
];
}
// JsonSerializable
public function jsonSerialize(): array
{
return $this->toArray();
}
/**
* 序列化模型对象
*
* @return array
*/
public function __serialize(): array
{
$removeKeys = ['invoker', 'db', 'event'];
return array_diff_key(self::$weakMap[$this], array_flip($removeKeys));
}
/**
* 反序列化模型对象
*
* @param array $data
* @return void
*/
public function __unserialize(array $data)
{
if (!self::$weakMap) {
self::$weakMap = new WeakMap;
}
self::$weakMap[$this] = $data;
// 重新初始化
$this->initialize();
}
/**
* 克隆模型实例
*
* @return void
*/
public function __clone()
{
throw new InvalidArgumentException('use $modelObj->clone() replace clone $modelObj');
}
// ArrayAccess
public function offsetSet(mixed $name, mixed $value): void
{
$this->set($name, $value);
}
public function offsetGet(mixed $name): mixed
{
return $this->get($name);
}
public function offsetExists(mixed $name): bool
{
return $this->__isset($name);
}
public function offsetUnset(mixed $name): void
{
$this->__unset($name);
}
}