[TOC]
* * * * *
# 1 数据模型
>>数据模型(Model) 主要实现对单个数据表的操作,包括
>数据表整体操作
>数据表字段操作
>数据表的数据操作
## 1-1 数据模型的创建
### Model->__construct()
>>数据模型的构造函数,创建数据模型对象
~~~
public function __construct($data = [])
{
if (is_object($data)) {
$this->data = get_object_vars($data);
} else {
$this->data = $data;
}
// 当前类名
$this->class = get_class($this);
if (empty($this->name)) {
// 当前模型名
$this->name = basename(str_replace('\\', '/', $this->class));
}
if (is_null($this->autoWriteTimestamp)) {
// 自动写入时间戳
$this->autoWriteTimestamp = $this->db()->getConfig('auto_timestamp');
}
// 执行初始化操作
$this->initialize();
}
~~~
### $model->initialize()
>>模型的初始化操作,使用标志位确保只进行一次初始化
~~~
protected function initialize()
{
$class = get_class($this);
if (!isset(static::$initialized[$class])) {
static::$initialized[$class] = true;
static::init();
}
}
~~~
### Model::init()
>>数据表模型的初始化接口
~~~
protected static function init()
{}
~~~
## 1-2 数据库的连接
### $model->db()
>>数据库连接 ,建立模型使用的数据库连接对象
~~~
public function db()
{
$model = $this->class;
if (!isset(self::$links[$model])) {
// 设置当前模型 确保查询返回模型对象
$query = Db::connect($this->connection)->model($model);
// 设置当前数据表和模型名
if (!empty($this->table)) {
$query->setTable($this->table);
} else {
$query->name($this->name);
}
if (!empty($this->field)) {
if (true === $this->field) {
$type = $query->getTableInfo('', 'type');
} else {
$type = [];
foreach ((array) $this->field as $key => $val) {
if (is_int($key)) {
$key = $val;
$val = 'varchar';
}
$type[$key] = $val;
}
}
$query->setFieldType($type);
$this->field = array_keys($type);
$query->allowField($this->field);
}
if (!empty($this->pk)) {
$query->pk($this->pk);
}
self::$links[$model] = $query;
}
// 返回当前模型的数据库查询对象
return self::$links[$model];
}
~~~
# 2 数据表操作
## 2-1数据表主键操作
### $model->getPk()
>>获取模型对应数据表的主键
~~~
public function getPk($name = '')
{
if (!empty($name)) {
$table = $this->db()->getTable($name);
return $this->db()->getPk($table);
} elseif (empty($this->pk)) {
$this->pk = $this->db()->getPk();
}
return $this->pk;
}
~~~
### $model->isPk()
>>判断一个字段是否为数据表的主键
~~~
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;
}
~~~
# 3 数据表字段操作
## 3-1 字段的输出控制
### $model->hidden()
>> 设置隐藏输出的字段
~~~
public function hidden($hidden = [])
{
$this->hidden = $hidden;
return $this;
}
~~~
### $model->visible()
>> 设置需要输出的字段
~~~
public function visible($visible = [])
{
$this->visible = $visible;
return $this;
}
~~~
### $model->append()
>> 设置追加输出的字段
~~~
public function append($append = [])
{
$this->append = $append;
return $this;
}
~~~
## 3-2 字段的写入操作
### $model->allowField()
>>设置允许写入的字段
~~~
public function allowField($field)
{
if (true === $field) {
$field = $this->db()->getTableInfo('', 'type');
$this->db()->setFieldType($field);
$field = array_keys($field);
}
$this->field = $field;
return $this;
}
~~~
### $model->auto()
>>设置自动完成的字段
~~~
public function auto($fields)
{
$this->auto = $fields;
return $this;
}
~~~
### $model->validate()
>> 设置需要验证的字段
~~~
public function validate($rule = true, $msg = [])
{
if (is_array($rule)) {
$this->validate = [
'rule' => $rule,
'msg' => $msg,
];
} else {
$this->validate = true === $rule ? $this->name : $rule;
}
return $this;
}
~~~
# 4 数据操作
## 4-1 获取或设置模型的数据
### $model->data()
~~~
public function data($data, $value = null)
{
if (is_string($data)) {
$this->data[$data] = $value;
} else {
// 清空数据
$this->data = [];
if (is_object($data)) {
$data = get_object_vars($data);
}
if (true === $value) {
// 数据对象赋值
foreach ($data as $key => $value) {
$this->setAttr($key, $value, $data);
}
} else {
$this->data = $data;
}
}
return $this;
}
~~~
## 4-2 获取对象原始数据
### $model->getData()
~~~
public function getData($name = null)
{
if (is_null($name)) {
return $this->data;
} elseif (array_key_exists($name, $this->data)) {
return $this->data[$name];
} else {
throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name);
}
}
~~~
## 4-3 数据转换
### $model->toArray()
>> 模型数据转换为数组格式
~~~
public function toArray()
{
$item = [];
//过滤属性
if (!empty($this->visible)) {
$data = array_intersect_key($this->data, array_flip($this->visible));
} elseif (!empty($this->hidden)) {
$data = array_diff_key($this->data, array_flip($this->hidden));
} else {
$data = $this->data;
}
foreach ($data as $key => $val) {
if ($val instanceof Model || $val instanceof Collection) {
// 关联模型对象
$item[$key] = $val->toArray();
} elseif (is_array($val) && reset($val) instanceof Model) {
// 关联模型数据集
$arr = [];
foreach ($val as $k => $value) {
$arr[$k] = $value->toArray();
}
$item[$key] = $arr;
} else {
// 模型属性
$item[$key] = $this->getAttr($key);
}
}
// 追加属性(必须定义获取器)
if (!empty($this->append)) {
foreach ($this->append as $name) {
$item[$name] = $this->getAttr($name);
}
}
return !empty($item) ? $item : [];
}
~~~
### $model->toJson()
>>模型数据转换为json格式
~~~
public function toJson($options = JSON_UNESCAPED_UNICODE)
{
return json_encode($this->toArray(), $options);
}
~~~
### $model-readTransform()
>> 数据读取时 类型自动转换
~~~
protected function readTransform($value, $type)
{
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':
$format = !empty($param) ? $param : $this->dateFormat;
$value = date($format, $value);
break;
case 'datetime':
$format = !empty($param) ? $param : $this->dateFormat;
$value = date($format, strtotime($value));
break;
case 'json':
$value = json_decode($value, true);
break;
case 'array':
$value = is_null($value) ? [] : json_decode($value, true);
break;
case 'object':
$value = empty($value) ? new \stdClass() : json_decode($value);
break;
case 'serialize':
$value = unserialize($value);
break;
}
return $value;
}
~~~
### $model->getAttr()
>> 数据获取 自动转换获取器
~~~
public function getAttr($name)
{
try {
$notFound = false;
$value = $this->getData($name);
} catch (InvalidArgumentException $e) {
$notFound = true;
$value = null;
}
// 检测属性获取器
$method = 'get' . Loader::parseName($name, 1) . 'Attr';
if (method_exists($this, $method)) {
$value = $this->$method($value, $this->data);
} elseif (isset($this->type[$name])) {
// 类型转换
$value = $this->readTransform($value, $this->type[$name]);
} elseif ($notFound) {
if (method_exists($this, $name) && !method_exists('\think\Model', $name)) {
// 不存在该字段 获取关联数据
$value = $this->relation()->getRelation($name);
// 保存关联对象值
$this->data[$name] = $value;
} else {
throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name);
}
}
return $value;
}
~~~
### $model->writeTransform()
>> 数据写入 类型转换
~~~
protected function writeTransform($value, $type)
{
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':
$format = !empty($param) ? $param : $this->dateFormat;
$value = date($format, is_numeric($value) ? $value : strtotime($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;
}
~~~
### $model->setAttr()
>>数据写入 自动转换 修改器
~~~
public function setAttr($name, $value, $data = [])
{
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)) {
$value = $this->$method($value, array_merge($data, $this->data));
} elseif (isset($this->type[$name])) {
// 类型转换
$value = $this->writeTransform($value, $this->type[$name]);
}
}
// 标记字段更改
if (!isset($this->data[$name]) || ($this->data[$name] != $value && !in_array($name, $this->change))) {
$this->change[] = $name;
}
// 设置数据对象属性
$this->data[$name] = $value;
return $this;
}
~~~
### $model->autoWrite()
>>自动写入时间戳
~~~
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':
$format = !empty($param) ? $param : $this->dateFormat;
$value = date($format, $_SERVER['REQUEST_TIME']);
break;
case 'timestamp':
case 'int':
$value = $_SERVER['REQUEST_TIME'];
break;
}
} elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), ['datetime', 'date', 'timestamp'])) {
$value = date($this->dateFormat, $_SERVER['REQUEST_TIME']);
} else {
$value = $_SERVER['REQUEST_TIME'];
}
return $value;
}
~~~
## 4-4 模型数据与数据表的操作
### Model::create()
>>写入数据
~~~
public static function create($data = [])
{
$model = new static();
$model->isUpdate(false)->save($data, []);
return $model;
}
~~~
### Model::update()
>>更新数据
~~~
public static function update($data = [], $where = [])
{
$model = new static();
$result = $model->isUpdate(true)->save($data, $where);
return $model;
}
~~~
### Model::destroy()
>>删除数据
~~~
public static function destroy($data)
{
$model = new static();
$query = $model->db();
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 0;
}
$resultSet = $query->select($data);
$count = 0;
if ($resultSet) {
foreach ($resultSet as $data) {
$result = $data->delete();
$count += $result;
}
}
return $count;
}
~~~
### $model->save()
>>更新数据
~~~
public function save($data = [], $where = [], $sequence = null)
{
if (!empty($data)) {
// 数据自动验证
if (!$this->validateData($data)) {
return false;
}
// 数据对象赋值
foreach ($data as $key => $value) {
$this->setAttr($key, $value, $data);
}
if (!empty($where)) {
$this->isUpdate = true;
}
}
// 检测字段
if (!empty($this->field)) {
foreach ($this->data as $key => $val) {
if (!in_array($key, $this->field) && !array_key_exists($key, $this->field)) {
unset($this->data[$key]);
}
}
}
// 数据自动完成
$this->autoCompleteData($this->auto);
// 自动写入更新时间
if ($this->autoWriteTimestamp && $this->updateTime) {
$this->setAttr($this->updateTime, null);
}
// 事件回调
if (false === $this->trigger('before_write', $this)) {
return false;
}
if ($this->isUpdate) {
// 自动更新
$this->autoCompleteData($this->update);
// 事件回调
if (false === $this->trigger('before_update', $this)) {
return false;
}
// 去除没有更新的字段
$data = [];
foreach ($this->data as $key => $val) {
if (in_array($key, $this->change) || $this->isPk($key)) {
$data[$key] = $val;
}
}
if (!empty($this->readonly)) {
// 只读字段不允许更新
foreach ($this->readonly as $key => $field) {
if (isset($data[$field])) {
unset($data[$field]);
}
}
}
if (empty($where) && !empty($this->updateWhere)) {
$where = $this->updateWhere;
}
if (!empty($where)) {
$pk = $this->getPk();
if (is_string($pk) && isset($data[$pk])) {
unset($data[$pk]);
}
}
$result = $this->db()->where($where)->update($data);
// 清空change
$this->change = [];
// 更新回调
$this->trigger('after_update', $this);
} else {
// 自动写入
$this->autoCompleteData($this->insert);
// 自动写入创建时间
if ($this->autoWriteTimestamp && $this->createTime) {
$this->setAttr($this->createTime, null);
}
if (false === $this->trigger('before_insert', $this)) {
return false;
}
$result = $this->db()->insert($this->data);
// 获取自动增长主键
if ($result) {
$insertId = $this->db()->getLastInsID($sequence);
$pk = $this->getPk();
if (is_string($pk) && $insertId) {
$this->data[$pk] = $insertId;
}
}
// 标记为更新
$this->isUpdate = true;
// 清空change
$this->change = [];
// 新增回调
$this->trigger('after_insert', $this);
}
// 写入回调
$this->trigger('after_write', $this);
return $result;
}
~~~
### $model->saveAll()
>>更新多条记录
~~~
public function saveAll($dataSet, $replace = true)
{
if ($this->validate) {
// 数据批量验证
$validate = $this->validate;
foreach ($dataSet as $data) {
if (!$this->validate($validate)->validateData($data)) {
return false;
}
}
}
$result = [];
$db = $this->db();
$db->startTrans();
try {
$pk = $this->getPk();
if (is_string($pk) && $replace) {
$auto = true;
}
foreach ($dataSet as $key => $data) {
if (!empty($auto) && isset($data[$pk])) {
$result[$key] = self::update($data);
} else {
$result[$key] = self::create($data);
}
}
$db->commit();
return $result;
} catch (\Exception $e) {
$db->rollback();
throw $e;
}
}
~~~
### $model->delte()
>>删除当前记录
~~~
public function delete()
{
if (false === $this->trigger('before_delete', $this)) {
return false;
}
$result = $this->db()->delete($this->data);
$this->trigger('after_delete', $this);
return $result;
}
~~~
### $model->get()
>> 查找单条记录
~~~
public static function get($data = null, $with = [], $cache = false)
{
$query = static::parseQuery($data, $with, $cache);
return $query->find($data);
}
~~~
### $model->all()
>>查找多条记录
~~~
public static function all($data = null, $with = [], $cache = false)
{
$query = static::parseQuery($data, $with, $cache);
return $query->select($data);
}
~~~
# 5 数据表关联操作
## 5-1 关联查询对象创建
### $model->relation()
>>创建关联对象
~~~
protected function relation($relation = null)
{
if (!is_null($relation)) {
// 执行关联查询
return $this->db()->relation($relation);
}
// 获取关联对象实例
if (is_null($this->relation)) {
$this->relation = new Relation($this);
}
return $this->relation;
}
~~~
## 5-2 表的关联操作
### $model->hasOne()
>>一对一关联
~~~
public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER')
{
// 记录当前关联信息
$model = $this->parseModel($model);
$localKey = $localKey ?: $this->getPk();
$foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id';
return $this->relation()->hasOne($model, $foreignKey, $localKey, $alias, $joinType);
}
~~~
### $model->hasMany()
>>一对多
~~~
public function hasMany($model, $foreignKey = '', $localKey = '', $alias = [])
{
// 记录当前关联信息
$model = $this->parseModel($model);
$localKey = $localKey ?: $this->getPk();
$foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id';
return $this->relation()->hasMany($model, $foreignKey, $localKey, $alias);
}
~~~
### $model->hasManyThrough()
>> 远程一对多
~~~
public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '', $alias = [])
{
// 记录当前关联信息
$model = $this->parseModel($model);
$through = $this->parseModel($through);
$localKey = $localKey ?: $this->getPk();
$foreignKey = $foreignKey ?: Loader::parseName($this->name) . '_id';
$name = Loader::parseName(basename(str_replace('\\', '/', $through)));
$throughKey = $throughKey ?: $name . '_id';
return $this->relation()->hasManyThrough($model, $through, $foreignKey, $throughKey, $localKey, $alias);
}
~~~
### $model->belongsToMany()
>> 多对多关联
~~~
public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '', $alias = [])
{
// 记录当前关联信息
$model = $this->parseModel($model);
$name = Loader::parseName(basename(str_replace('\\', '/', $model)));
$table = $table ?: $this->db()->getTable(Loader::parseName($this->name) . '_' . $name);
$foreignKey = $foreignKey ?: $name . '_id';
$localKey = $localKey ?: Loader::parseName($this->name) . '_id';
return $this->relation()->belongsToMany($model, $table, $foreignKey, $localKey, $alias);
}
~~~
### $model->belongTo()
>> 相对关联
~~~
public function belongsTo($model, $foreignKey = '', $otherKey = '', $alias = [], $joinType = 'INNER')
{
// 记录当前关联信息
$model = $this->parseModel($model);
$foreignKey = $foreignKey ?: Loader::parseName(basename(str_replace('\\', '/', $model))) . '_id';
$otherKey = $otherKey ?: (new $model)->getPk();
return $this->relation()->belongsTo($model, $foreignKey, $otherKey, $alias, $joinType);
}
~~~
## 5-3 数据的关联操作
>>数据的关联操作与普通模型的数据操作相同
>在建立好关联对象后,即可使用普通数据操作来操作关联数据
# 6 操作拦截
### $model->__call()
>>对于不存在Model的方法则调用$query数据库对象的方法
~~~
public function __call($method, $args)
{
$query = $this->db();
// 全局作用域
if (static::$useGlobalScope && method_exists($this, 'base')) {
call_user_func_array('static::base', [ & $query]);
}
if (method_exists($this, 'scope' . $method)) {
// 动态调用命名范围
$method = 'scope' . $method;
array_unshift($args, $query);
call_user_func_array([$this, $method], $args);
return $this;
} else {
return call_user_func_array([$query, $method], $args);
}
}
~~~
### Model::__callStatic()
>> 对于不存在Model的静态方法则调用连接对象的静态方法。
~~~
public static function __callStatic($method, $params)
{
$model = get_called_class();
if (!isset(self::$links[$model])) {
self::$links[$model] = (new static())->db();
}
$query = self::$links[$model];
// 全局作用域
if (static::$useGlobalScope && method_exists($model, 'base')) {
call_user_func_array('static::base', [ & $query]);
}
return call_user_func_array([$query, $method], $params);
}
~~~
### $model->__set()
>>属性修改操作
~~~
public function __set($name, $value)
{
$this->setAttr($name, $value);
}
~~~
### $model->__get()
>>属性获取操作
~~~
public function __get($name)
{
return $this->getAttr($name);
}
~~~
### $model->_unset()
>>销毁数据对象的值
~~~
public function __unset($name)
{
unset($this->data[$name]);
}
~~~
### $model->__isset()
>>属性值检测
~~~
public function __isset($name)
{
try {
if (array_key_exists($name, $this->data)) {
return true;
} else {
$this->getAttr($name);
return true;
}
} catch (InvalidArgumentException $e) {
return false;
}
}
~~~
# 7 接口操作
## 7-1 JsonSerializable
### $model->jsonSerialize()
~~~
public function jsonSerialize()
{
return $this->toArray();
}
~~~
## 7-2 ArrayAccess
### $model->offsetSet()
~~~
public function offsetSet($name, $value)
{
$this->setAttr($name, $value);
}
~~~
### $model->offsetExists()
~~~
public function offsetExists($name)
{
return $this->__isset($name);
}
~~~
### $model->offsetUnset()
~~~
public function offsetUnset($name)
{
$this->__unset($name);
}
~~~
### $model->offsetGet()
~~~
public function offsetGet($name)
{
return $this->getAttr($name);
}
~~~
- 框架简介
- 简介
- 框架目录
- 根目录
- 应用目录
- 核心目录
- 扩展目录
- 其他目录
- 框架流程
- 启动流程
- 请求流程
- 响应流程
- 框架结构
- 应用组织
- 网络请求
- 路由组织
- 数据验证
- 数据模型(M)
- 数据库连接(Connection)
- 数据库(Db)
- 查询构造(Builder)
- 数据库查询(Query)
- 模型(Model)
- 模板视图(V)
- 视图(View)
- 模板引擎(Think)
- 模板标签库(TagLib)
- 控制器(C)
- 网络响应
- 配置与缓存
- 配置操作
- 缓存操作
- cookie与session
- Cookie操作
- Session操作
- 自动加载
- 钩子注册
- 文件上传
- 分页控制
- 控制台
- 自动构建
- 日志异常调试
- 异常处理
- 代码调试
- 日志记录
- 框架使用
- 1 环境搭建(Server)
- 2 网络请求(Request)
- 3 请求路由(Route)
- 4 响应输出(Response)
- 5 业务处理(Controller)
- 6 数据存取(Model)
- 7 Web界面(View)