// +---------------------------------------------------------------------- namespace think; use InvalidArgumentException; use think\db\Query; /** * Class Model * @package think * @mixin Query * @method $this scope(string|array $scope) static 查询范围 * @method $this where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 * @method $this whereRaw(string $where, array $bind = [], string $logic = 'AND') static 表达式查询 * @method $this whereExp(string $field, string $condition, array $bind = [], string $logic = 'AND') static 字段表达式查询 * @method $this when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询 * @method $this join(mixed $join, mixed $condition = null, string $type = 'INNER', array $bind = []) static JOIN查询 * @method $this view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 * @method $this with(mixed $with, callable $callback = null) static 关联预载入 * @method $this count(string $field = '*') static Count统计查询 * @method $this min(string $field, bool $force = true) static Min统计查询 * @method $this max(string $field, bool $force = true) static Max统计查询 * @method $this sum(string $field) static SUM统计查询 * @method $this avg(string $field) static Avg统计查询 * @method $this field(mixed $field, boolean $except = false, string $tableName = '', string $prefix = '', string $alias = '') static 指定查询字段 * @method $this fieldRaw(string $field) static 指定查询字段 * @method $this union(mixed $union, boolean $all = false) static UNION查询 * @method $this limit(mixed $offset, integer $length = null) static 查询LIMIT * @method $this order(mixed $field, string $order = null) static 查询ORDER * @method $this orderRaw(string $field, array $bind = []) static 查询ORDER * @method $this cache(mixed $key = null, integer|\DateTime $expire = null, string $tag = null) static 设置查询缓存 * @method mixed value(string $field, mixed $default = null) static 获取某个字段的值 * @method array column(string $field, string $key = '') static 获取某个列的值 * @method $this find(mixed $data = null) static 查询单个记录 * @method $this findOrFail(mixed $data = null) 查询单个记录 * @method Collection|$this[] select(mixed $data = null) static 查询多个记录 * @method $this get(mixed $data = null, mixed $with = [], bool $cache = false, bool $failException = false) static 查询单个记录 支持关联预载入 * @method $this getOrFail(mixed $data = null, mixed $with = [], bool $cache = false) static 查询单个记录 不存在则抛出异常 * @method $this findOrEmpty(mixed $data = null) static 查询单个记录 不存在则返回空模型 * @method Collection|$this[] all(mixed $data = null, mixed $with = [], bool $cache = false) static 查询多个记录 支持关联预载入 * @method $this withAttr(array $name, \Closure $closure = null) static 动态定义获取器 * @method $this withJoin(string|array $with, string $joinType = '') static * @method $this withCount(string|array $relation, bool $subQuery = true) static 关联统计 * @method $this withSum(string|array $relation, string $field, bool $subQuery = true) static 关联SUM统计 * @method $this withMax(string|array $relation, string $field, bool $subQuery = true) static 关联MAX统计 * @method $this withMin(string|array $relation, string $field, bool $subQuery = true) static 关联Min统计 * @method $this withAvg(string|array $relation, string $field, bool $subQuery = true) static 关联Avg统计 * @method Paginator|$this paginate(int|array $listRows = null, int|bool $simple = false, array $config = []) static 分页 */ abstract class Model implements \JsonSerializable, \ArrayAccess { use model\concern\Attribute; use model\concern\RelationShip; use model\concern\ModelEvent; use model\concern\TimeStamp; use model\concern\Conversion; /** * 是否存在数据 * @var bool */ private $exists = false; /** * 是否Replace * @var bool */ private $replace = false; /** * 是否强制更新所有数据 * @var bool */ private $force = false; /** * 更新条件 * @var array */ private $updateWhere; /** * 数据库配置信息 * @var array|string */ protected $connection = []; /** * 数据库查询对象类名 * @var string */ protected $query; /** * 模型名称 * @var string */ protected $name; /** * 数据表名称 * @var string */ protected $table; /** * 写入自动完成定义 * @var array */ protected $auto = []; /** * 新增自动完成定义 * @var array */ protected $insert = []; /** * 更新自动完成定义 * @var array */ protected $update = []; /** * 初始化过的模型. * @var array */ protected static $initialized = []; /** * 是否从主库读取(主从分布式有效) * @var array */ protected static $readMaster; /** * 查询对象实例 * @var Query */ protected $queryInstance; /** * 错误信息 * @var mixed */ protected $error; /** * 软删除字段默认值 * @var mixed */ protected $defaultSoftDelete; /** * 全局查询范围 * @var array */ protected $globalScope = []; /** * 架构函数 * @access public * @param array|object $data 数据 */ public function __construct($data = []) { if (is_object($data)) { $this->data = get_object_vars($data); } else { $this->data = $data; } if ($this->disuse) { // 废弃字段 foreach ((array) $this->disuse as $key) { if (array_key_exists($key, $this->data)) { unset($this->data[$key]); } } } // 记录原始数据 $this->origin = $this->data; $config = Db::getConfig(); if (empty($this->name)) { // 当前模型名 $name = str_replace('\\', '/', static::class); $this->name = basename($name); if (Container::pull('config')->get('class_suffix')) { $suffix = basename(dirname($name)); $this->name = substr($this->name, 0, -strlen($suffix)); } } if (is_null($this->autoWriteTimestamp)) { // 自动写入时间戳 $this->autoWriteTimestamp = $config['auto_timestamp']; } if (is_null($this->dateFormat)) { // 设置时间戳格式 $this->dateFormat = $config['datetime_format']; } if (is_null($this->resultSetType)) { $this->resultSetType = $config['resultset_type']; } if (!empty($this->connection) && is_array($this->connection)) { // 设置模型的数据库连接 $this->connection = array_merge($config, $this->connection); } if ($this->observerClass) { // 注册模型观察者 static::observe($this->observerClass); } // 执行初始化操作 $this->initialize(); } /** * 获取当前模型名称 * @access public * @return string */ public function getName() { return $this->name; } /** * 是否从主库读取数据(主从分布有效) * @access public * @param bool $all 是否所有模型有效 * @return $this */ public function readMaster($all = false) { $model = $all ? '*': static::class; static::$readMaster[$model] = true; return $this; } /** * 创建新的模型实例 * @access public * @param array|object $data 数据 * @param bool $isUpdate 是否为更新 * @param mixed $where 更新条件 * @return Model */ public function newInstance($data = [], $isUpdate = false, $where = null) { return (new static($data))->isUpdate($isUpdate, $where); } /** * 创建模型的查询对象 * @access protected * @return Query */ protected function buildQuery() { // 设置当前模型 确保查询返回模型对象 $query = Db::connect($this->connection, false, $this->query); $query->model($this) ->name($this->name) ->json($this->json, $this->jsonAssoc) ->setJsonFieldType($this->jsonType); if (isset(static::$readMaster['*']) || isset(static::$readMaster[static::class])) { $query->master(true); } // 设置当前数据表和模型名 if (!empty($this->table)) { $query->table($this->table); } if (!empty($this->pk)) { $query->pk($this->pk); } return $query; } /** * 获取当前模型的数据库查询对象 * @access public * @param Query $query 查询对象实例 * @return $this */ public function setQuery($query) { $this->queryInstance = $query; return $this; } /** * 获取当前模型的数据库查询对象 * @access public * @param bool|array $useBaseQuery 是否调用全局查询范围(或者指定查询范围名称) * @return Query */ public function db($useBaseQuery = true) { if ($this->queryInstance) { return $this->queryInstance; } $query = $this->buildQuery(); // 软删除 if (property_exists($this, 'withTrashed') && !$this->withTrashed) { $this->withNoTrashed($query); } // 全局作用域 if (true === $useBaseQuery && method_exists($this, 'base')) { call_user_func_array([$this, 'base'], [ & $query]); } $globalScope = is_array($useBaseQuery) && $useBaseQuery ? $useBaseQuery : $this->globalScope; if ($globalScope && false !== $useBaseQuery) { $query->scope($globalScope); } // 返回当前模型的数据库查询对象 return $query; } /** * 初始化模型 * @access protected * @return void */ protected function initialize() { if (!isset(static::$initialized[static::class])) { static::$initialized[static::class] = true; static::init(); } } /** * 初始化处理 * @access protected * @return void */ protected static function init() {} /** * 数据自动完成 * @access protected * @param array $auto 要自动更新的字段列表 * @return void */ protected function autoCompleteData($auto = []) { foreach ($auto as $field => $value) { if (is_integer($field)) { $field = $value; $value = null; } if (!isset($this->data[$field])) { $default = null; } else { $default = $this->data[$field]; } $this->setAttr($field, !is_null($value) ? $value : $default); } } /** * 更新是否强制写入数据 而不做比较 * @access public * @param bool $force * @return $this */ public function force($force = true) { $this->force = $force; return $this; } /** * 判断force * @access public * @return bool */ public function isForce() { return $this->force; } /** * 新增数据是否使用Replace * @access public * @param bool $replace * @return $this */ public function replace($replace = true) { $this->replace = $replace; return $this; } /** * 设置数据是否存在 * @access public * @param bool $exists * @return $this */ public function exists($exists) { $this->exists = $exists; return $this; } /** * 判断数据是否存在数据库 * @access public * @return bool */ public function isExists() { return $this->exists; } /** * 判断模型是否为空 * @access public * @return bool */ public function isEmpty() { return empty($this->data); } /** * 保存当前数据对象 * @access public * @param array $data 数据 * @param array $where 更新条件 * @param string $sequence 自增序列名 * @return bool */ public function save($data = [], $where = [], $sequence = null) { if (is_string($data)) { $sequence = $data; $data = []; } if (!$this->checkBeforeSave($data, $where)) { return false; } $result = $this->exists ? $this->updateData($where) : $this->insertData($sequence); if (false === $result) { return false; } // 写入回调 $this->trigger('after_write'); // 重新记录原始数据 $this->origin = $this->data; $this->set = []; return true; } /** * 写入之前检查数据 * @access protected * @param array $data 数据 * @param array $where 保存条件 * @return bool */ protected function checkBeforeSave($data, $where) { if (!empty($data)) { // 数据对象赋值 foreach ($data as $key => $value) { $this->setAttr($key, $value, $data); } if (!empty($where)) { $this->exists = true; $this->updateWhere = $where; } } // 数据自动完成 $this->autoCompleteData($this->auto); // 事件回调 if (false === $this->trigger('before_write')) { return false; } return true; } /** * 检查数据是否允许写入 * @access protected * @param array $append 自动完成的字段列表 * @return array */ protected function checkAllowFields(array $append = []) { // 检测字段 if (empty($this->field) || true === $this->field) { $query = $this->db(false); $table = $this->table ?: $query->getTable(); $this->field = $query->getConnection()->getTableFields($table); $field = $this->field; } else { $field = array_merge($this->field, $append); if ($this->autoWriteTimestamp) { array_push($field, $this->createTime, $this->updateTime); } } if ($this->disuse) { // 废弃字段 $field = array_diff($field, (array) $this->disuse); } return $field; } /** * 更新写入数据 * @access protected * @param mixed $where 更新条件 * @return bool */ protected function updateData($where) { // 自动更新 $this->autoCompleteData($this->update); // 事件回调 if (false === $this->trigger('before_update')) { return false; } // 获取有更新的数据 $data = $this->getChangedData(); if (empty($data)) { // 关联更新 if (!empty($this->relationWrite)) { $this->autoRelationUpdate(); } return true; } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { // 自动写入更新时间 $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); $this->data[$this->updateTime] = $data[$this->updateTime]; } if (empty($where) && !empty($this->updateWhere)) { $where = $this->updateWhere; } // 检查允许字段 $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->update)); // 保留主键数据 foreach ($this->data as $key => $val) { if ($this->isPk($key)) { $data[$key] = $val; } } $pk = $this->getPk(); $array = []; foreach ((array) $pk as $key) { if (isset($data[$key])) { $array[] = [$key, '=', $data[$key]]; unset($data[$key]); } } if (!empty($array)) { $where = $array; } foreach ((array) $this->relationWrite as $name => $val) { if (is_array($val)) { foreach ($val as $key) { if (isset($data[$key])) { unset($data[$key]); } } } } // 模型更新 $db = $this->db(false); $db->startTrans(); try { $db->where($where) ->strict(false) ->field($allowFields) ->update($data); // 关联更新 if (!empty($this->relationWrite)) { $this->autoRelationUpdate(); } $db->commit(); // 更新回调 $this->trigger('after_update'); return true; } catch (\Exception $e) { $db->rollback(); throw $e; } } /** * 新增写入数据 * @access protected * @param string $sequence 自增序列名 * @return bool */ protected function insertData($sequence) { // 自动写入 $this->autoCompleteData($this->insert); // 时间戳自动写入 $this->checkTimeStampWrite(); if (false === $this->trigger('before_insert')) { return false; } // 检查允许字段 $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->insert)); $db = $this->db(false); $db->startTrans(); try { $result = $db->strict(false) ->field($allowFields) ->insert($this->data, $this->replace, false, $sequence); // 获取自动增长主键 if ($result && $insertId = $db->getLastInsID($sequence)) { $pk = $this->getPk(); foreach ((array) $pk as $key) { if (!isset($this->data[$key]) || '' == $this->data[$key]) { $this->data[$key] = $insertId; } } } // 关联写入 if (!empty($this->relationWrite)) { $this->autoRelationInsert(); } $db->commit(); // 标记为更新 $this->exists = true; // 新增回调 $this->trigger('after_insert'); return true; } catch (\Exception $e) { $db->rollback(); throw $e; } } /** * 字段值(延迟)增长 * @access public * @param string $field 字段名 * @param integer $step 增长值 * @param integer $lazyTime 延时时间(s) * @return bool * @throws Exception */ public function setInc($field, $step = 1, $lazyTime = 0) { // 读取更新条件 $where = $this->getWhere(); // 事件回调 if (false === $this->trigger('before_update')) { return false; } $result = $this->db(false) ->where($where) ->setInc($field, $step, $lazyTime); if (true !== $result) { $this->data[$field] += $step; } // 更新回调 $this->trigger('after_update'); return true; } /** * 字段值(延迟)减少 * @access public * @param string $field 字段名 * @param integer $step 减少值 * @param integer $lazyTime 延时时间(s) * @return bool * @throws Exception */ public function setDec($field, $step = 1, $lazyTime = 0) { // 读取更新条件 $where = $this->getWhere(); // 事件回调 if (false === $this->trigger('before_update')) { return false; } $result = $this->db(false) ->where($where) ->setDec($field, $step, $lazyTime); if (true !== $result) { $this->data[$field] -= $step; } // 更新回调 $this->trigger('after_update'); return true; } /** * 获取当前的更新条件 * @access protected * @return mixed */ protected function getWhere() { // 删除条件 $pk = $this->getPk(); $where = []; if (is_string($pk) && isset($this->data[$pk])) { $where[] = [$pk, '=', $this->data[$pk]]; } elseif (is_array($pk)) { foreach ($pk as $field) { if (isset($this->data[$field])) { $where[] = [$field, '=', $this->data[$field]]; } } } if (empty($where)) { $where = empty($this->updateWhere) ? null : $this->updateWhere; } return $where; } /** * 保存多个数据到当前数据对象 * @access public * @param array $dataSet 数据 * @param boolean $replace 是否自动识别更新和写入 * @return Collection * @throws \Exception */ public function saveAll($dataSet, $replace = true) { $db = $this->db(false); $db->startTrans(); try { $pk = $this->getPk(); if (is_string($pk) && $replace) { $auto = true; } $result = []; foreach ($dataSet as $key => $data) { if ($this->exists || (!empty($auto) && isset($data[$pk]))) { $result[$key] = self::update($data, [], $this->field); } else { $result[$key] = self::create($data, $this->field, $this->replace); } } $db->commit(); return $this->toCollection($result); } catch (\Exception $e) { $db->rollback(); throw $e; } } /** * 是否为更新数据 * @access public * @param mixed $update * @param mixed $where * @return $this */ public function isUpdate($update = true, $where = null) { if (is_bool($update)) { $this->exists = $update; if (!empty($where)) { $this->updateWhere = $where; } } else { $this->exists = true; $this->updateWhere = $update; } return $this; } /** * 删除当前的记录 * @access public * @return bool */ public function delete() { if (!$this->exists || false === $this->trigger('before_delete')) { return false; } // 读取更新条件 $where = $this->getWhere(); $db = $this->db(false); $db->startTrans(); try { // 删除当前模型数据 $db->where($where)->delete(); // 关联删除 if (!empty($this->relationWrite)) { $this->autoRelationDelete(); } $db->commit(); $this->trigger('after_delete'); $this->exists = false; return true; } catch (\Exception $e) { $db->rollback(); throw $e; } } /** * 设置自动完成的字段( 规则通过修改器定义) * @access public * @param array $fields 需要自动完成的字段 * @return $this */ public function auto($fields) { $this->auto = $fields; return $this; } /** * 写入数据 * @access public * @param array $data 数据数组 * @param array|true $field 允许字段 * @param bool $replace 使用Replace * @return static */ public static function create($data = [], $field = null, $replace = false) { $model = new static(); if (!empty($field)) { $model->allowField($field); } $model->isUpdate(false)->replace($replace)->save($data, []); return $model; } /** * 更新数据 * @access public * @param array $data 数据数组 * @param array $where 更新条件 * @param array|true $field 允许字段 * @return static */ public static function update($data = [], $where = [], $field = null) { $model = new static(); if (!empty($field)) { $model->allowField($field); } $model->isUpdate(true)->save($data, $where); return $model; } /** * 删除记录 * @access public * @param mixed $data 主键列表 支持闭包查询条件 * @return bool */ public static function destroy($data) { if (empty($data) && 0 !== $data) { return false; } $model = new static(); $query = $model->db(); if (is_array($data) && key($data) !== 0) { $query->where($data); $data = null; } elseif ($data instanceof \Closure) { $data($query); $data = null; } $resultSet = $query->select($data); if ($resultSet) { foreach ($resultSet as $data) { $data->delete(); } } return true; } /** * 获取错误信息 * @access public * @return mixed */ public function getError() { return $this->error; } /** * 解序列化后处理 */ public function __wakeup() { $this->initialize(); } public function __debugInfo() { return [ 'data' => $this->data, 'relation' => $this->relation, ]; } /** * 修改器 设置数据对象的值 * @access public * @param string $name 名称 * @param mixed $value 值 * @return void */ public function __set($name, $value) { $this->setAttr($name, $value); } /** * 获取器 获取数据对象的值 * @access public * @param string $name 名称 * @return mixed */ public function __get($name) { return $this->getAttr($name); } /** * 检测数据对象的值 * @access public * @param string $name 名称 * @return boolean */ public function __isset($name) { try { return !is_null($this->getAttr($name)); } catch (InvalidArgumentException $e) { return false; } } /** * 销毁数据对象的值 * @access public * @param string $name 名称 * @return void */ public function __unset($name) { unset($this->data[$name], $this->relation[$name]); } #[\ReturnTypeWillChange] public function offsetSet($name, $value) { $this->setAttr($name, $value); } #[\ReturnTypeWillChange] public function offsetExists($name): bool { return $this->__isset($name); } #[\ReturnTypeWillChange] public function offsetUnset($name) { $this->__unset($name); } #[\ReturnTypeWillChange] public function offsetGet($name) { return $this->getAttr($name); } /** * 设置是否使用全局查询范围 * @access public * @param bool|array $use 是否启用全局查询范围(或者用数组指定查询范围名称) * @return Query */ public static function useGlobalScope($use) { $model = new static(); return $model->db($use); } public function __call($method, $args) { if ('withattr' == strtolower($method)) { return call_user_func_array([$this, 'withAttribute'], $args); } return call_user_func_array([$this->db(), $method], $args); } public static function __callStatic($method, $args) { $model = new static(); return call_user_func_array([$model->db(), $method], $args); } }