<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace Think;
/**
* ThinkPHP Model模型类
* 实现了ORM和ActiveRecords模式
*/
class Model {
// 操作状态
const MODEL_INSERT = 1; // 插入模型数据
const MODEL_UPDATE = 2; // 更新模型数据
const MODEL_BOTH = 3; // 包含上面两种方式
const MUST_VALIDATE = 1; // 必须验证
const EXISTS_VALIDATE = 0; // 表单存在字段则验证
const VALUE_VALIDATE = 2; // 表单值不为空则验证
// 开始的 变量 定义
// 据说 const 比 那里的 define 里面 效果要好一些, 貌似在 class里面只能用 const 定义的。
// 当前数据库操作对象
protected $db = null; // 这里要 获取 数据库操作对象
// 数据库对象池
private $_db = array(); // 操作对象池 估计是给到分布式 连接使用的
// 主键名称
protected $pk = 'id'; // 就是这里,给定了默认的 那个。
// 主键是否自动增长
protected $autoinc = false; // 默认 是否 会自动 增长
// 数据表前缀
protected $tablePrefix = null;// 数据库表前缀,然而这个基本上都被自己重新定义了
// 模型名称
protected $name = ''; // 模型的开始
// 数据库名称
protected $dbName = '';// 数据库 名称了
//数据库配置
protected $connection = '';// 数据库 配置
// 数据表名(不包含表前缀)
protected $tableName = ''; // 数据表
// 实际数据表名(包含表前缀)
protected $trueTableName = '';// 组合后表名
// 最近错误信息
protected $error = '';// 错误信息
// 字段信息
protected $fields = array();// 可以获取到字段信息
// 数据信息
protected $data = array(); // 产生出来的 数据
// 查询表达式参数
protected $options = array();// 参数 开始
protected $_validate = array(); // 自动验证定义
protected $_auto = array(); // 自动完成定义
protected $_map = array(); // 字段映射定义 用到的不多
protected $_scope = array(); // 命名范围定义 这里没怎么用到
// 是否自动检测数据表字段信息
protected $autoCheckFields = true; // 是否开启自己的自动检测
// 是否批处理验证
protected $patchValidate = false;// 是否处理验证
// 链操作方法列表
protected $methods = array('strict','order','alias','having','group','lock','distinct','auto','filter','validate','result','token','index','force');
// 链式操作的 白名单
// 第一阶段: 代码 变量 常量 定义
// 总结:到了最后的这个,发现了 其实在类里面,也是先准备一个 各种数据的,开始。
/**
* 得到当前的数据对象名称
* @access public
* @return string
*/
public function getModelName() {
if(empty($this->name)){
// 继承 自己的 get_class($this)
$name = substr(get_class($this),0,-strlen(C('DEFAULT_M_LAYER'))); // 'DEFAULT_M_LAYER' => 'Model', // 默认的模型层名称
if ( $pos = strrpos($name,'\\') ) {
//有命名空间
$this->name = substr($name,$pos+1);// 获取其 对象
}else{
$this->name = $name;
}
}
return $this->name;
}
// 回调方法 初始化模型
protected function _initialize() {
}
/**
* 切换当前的数据库连接
* @access public
* @param integer $linkNum 连接序号
* @param mixed $config 数据库连接信息
* @param boolean $force 强制重新连接
* @return Model
* $this->db(0,empty($this->connection)?$connection:$this->connection,true);
*/
public function db($linkNum='',$config='',$force=false) {
if('' === $linkNum && $this->db) {
return $this->db;
}// 这种就是返回 当前的 数据库了,普通的调用是没有的
if(!isset($this->_db[$linkNum]) || $force ) { // 如果没有当前序号的连接, 或者可以强制 重启连接
// 创建一个新的实例
if(!empty($config) && is_string($config) && false === strpos($config,'/')) { // 支持读取配置参数
$config = C($config);
}// 重新 配置 $config
$this->_db[$linkNum] = Db::getInstance($config); // 返回数据库 的连接
// 获取个 数据库的 实例化
}elseif(NULL === $config){ // 如果配置为空的
$this->_db[$linkNum]->close(); // 关闭数据库连接
unset($this->_db[$linkNum]);
return ;
}
// 进行数据库连接 的实例化
// 切换数据库连接
$this->db = $this->_db[$linkNum];
$this->_after_db(); // 钩子函数
// 字段检测
if(!empty($this->name) && $this->autoCheckFields) $this->_checkTableInfo();
// 开始了 那里的 检测表
return $this;
}
// 数据库切换后回调方法
protected function _after_db() {
}
/**
* 自动检测数据表信息
* @access protected
* @return void
*/
protected function _checkTableInfo() {
// 如果不是Model类 自动记录数据表信息
// 只在第一次执行记录
if(empty($this->fields)) { // 没有记录开始
// 如果数据表字段没有定义则自动获取
if(C('DB_FIELDS_CACHE')) { // 'DB_FIELDS_CACHE' => true, // 启用字段缓存
$db = $this->dbName?:C('DB_NAME'); // 'DB_NAME' => '', // 数据库名
$fields = F('_fields/'.strtolower($db.'.'.$this->tablePrefix.$this->name));// 读取其缓存 数值
// 快速文件数据读取和保存 针对简单类型数据 字符串、数组
if($fields) {
$this->fields = $fields;
if(!empty($fields['_pk'])){
$this->pk = $fields['_pk'];
}
return ;
}// 更新 当前的 存储
}
// 每次都会读取数据表信息
$this->flush(); // 刷新一下 缓存
}
}
/**
* 获取字段信息并缓存
* @access public
* @return void
*/
public function flush() {
// 缓存不存在则查询数据表信息
$this->db->setModel($this->name);
$fields = $this->db->getFields($this->getTableName());
if(!$fields) { // 无法获取字段信息
return false;
}
$this->fields = array_keys($fields);
unset($this->fields['_pk']);
foreach ($fields as $key=>$val){
// 记录字段类型
$type[$key] = $val['type'];
if($val['primary']) {
// 增加复合主键支持
if (isset($this->fields['_pk']) && $this->fields['_pk'] != null) {
if (is_string($this->fields['_pk'])) {
$this->pk = array($this->fields['_pk']);
$this->fields['_pk'] = $this->pk;
}
$this->pk[] = $key;
$this->fields['_pk'][] = $key;
} else {
$this->pk = $key;
$this->fields['_pk'] = $key;
}
if($val['autoinc']) $this->autoinc = true;
}
}
// 记录字段类型信息
$this->fields['_type'] = $type;
// 2008-3-7 增加缓存开关控制
if(C('DB_FIELDS_CACHE')){
// 永久缓存数据表信息
$db = $this->dbName?:C('DB_NAME');
F('_fields/'.strtolower($db.'.'.$this->tablePrefix.$this->name),$this->fields);
}
}// 就是 刷新个字段呗。
/**
* 架构函数
* 取得DB类的实例对象 字段检查
* @access public
* @param string $name 模型名称
* @param string $tablePrefix 表前缀
* @param mixed $connection 数据库连接信息
*/
public function __construct($name='',$tablePrefix='',$connection='') {
// 其实 我们经常使用的 M();这里的 应该就是实例化这里的
// 模型初始化
$this->_initialize(); // 是空的。 此处没有任何代码,留出了很多代码接口,
// 获取模型名称 M()
if(!empty($name)) {
if(strpos($name,'.')) { // 支持 数据库名.模型名的 定义
list($this->dbName,$this->name) = explode('.',$name); // 应该是 数据库名 数据表名
}else{
$this->name = $name;
}
}elseif(empty($this->name)){
$this->name = $this->getModelName();
}
// 产生 两个
// $this->name 这个其实是数据表 名字
// $this->dbName 数据库名称
// 设置表前缀
if(is_null($tablePrefix)) {
// 前缀为Null表示没有前缀
$this->tablePrefix = '';
}elseif('' != $tablePrefix) {
$this->tablePrefix = $tablePrefix;
}elseif(!isset($this->tablePrefix)){ // 实际中,会执行这个
$this->tablePrefix = C('DB_PREFIX');// 这个才是
}
// 这里的代码执行了
// 数据库初始化操作
// 获取数据库操作对象
// 当前模型有独立的数据库连接信息
$this->db(0,empty($this->connection)?$connection:$this->connection,true); // 进行连接 操作。
//切换当前的数据库连接
}
// 总结:你一个函数 折腾出这么多事情来,呵呵
/**
* 设置数据对象的值
* @access public
* @param string $name 名称
* @param mixed $value 值
* @return void
*/
public function __set($name,$value) {
// 设置数据对象属性
$this->data[$name] = $value;
}// 普通 默认设置
/**
* 获取数据对象的值
* @access public
* @param string $name 名称
* @return mixed
*/
public function __get($name) {
return isset($this->data[$name])?$this->data[$name]:null;
}// 普通 默认设置
/**
* 检测数据对象的值
* @access public
* @param string $name 名称
* @return boolean
*/
public function __isset($name) {
return isset($this->data[$name]);
}// 普通 默认设置
/**
* 销毁数据对象的值
* @access public
* @param string $name 名称
* @return void
*/
public function __unset($name) {
unset($this->data[$name]);
}// 普通 默认设置
/**
* 利用__call方法实现一些特殊的Model方法
* @access public
* @param string $method 方法名称
* @param array $args 调用参数
* @return mixed
*/
public function __call($method,$args) {
if(in_array(strtolower($method),$this->methods,true)) { // 链式 操作里面的代码
// 连贯操作的实现
$this->options[strtolower($method)] = $args[0];
return $this;
}elseif(in_array(strtolower($method),array('count','sum','min','max','avg'),true)){ // 统计代码
// 统计查询的实现
$field = isset($args[0])?$args[0]:'*';
return $this->getField(strtoupper($method).'('.$field.') AS tp_'.$method);
}elseif(strtolower(substr($method,0,5))=='getby') { // 特殊的一个调用
// 根据某个字段获取记录
$field = parse_name(substr($method,5));
$where[$field] = $args[0];
return $this->where($where)->find();
}elseif(strtolower(substr($method,0,10))=='getfieldby') {
// 这个可以
// 根据某个字段获取记录的某个值
$name = parse_name(substr($method,10));
$where[$name] =$args[0];
return $this->where($where)->getField($args[1]);
}elseif(isset($this->_scope[$method])){
// 命名范围的单独调用支持
return $this->scope($method,$args[0]);
}else{
E(__CLASS__.':'.$method.L('_METHOD_NOT_EXIST_'));
return;
}
}// 普通 默认设置
// 符合标准 就执行
/**
* 数据类型检测
* @access protected
* @param mixed $data 数据
* @param string $key 字段名
* @return void
*/
protected function _parseType(&$data,$key) {
if(!isset($this->options['bind'][':'.$key]) && isset($this->fields['_type'][$key])){
$fieldType = strtolower($this->fields['_type'][$key]);
if(false !== strpos($fieldType,'enum')){
// 支持ENUM类型优先检测
}elseif(false === strpos($fieldType,'bigint') && false !== strpos($fieldType,'int')) {
$data[$key] = intval($data[$key]);
}elseif(false !== strpos($fieldType,'float') || false !== strpos($fieldType,'double')){
$data[$key] = floatval($data[$key]);
}elseif(false !== strpos($fieldType,'bool')){
$data[$key] = (bool)$data[$key];
}
}
}// 数据类型的 数据转换
/**
* 对保存到数据库的数据进行处理
* @access protected
* @param mixed $data 要操作的数据
* @return boolean
*/
protected function _facade($data) {
// 检查数据字段合法性
if(!empty($this->fields)) {
if(!empty($this->options['field'])) {
$fields = $this->options['field'];
unset($this->options['field']);
if(is_string($fields)) {
$fields = explode(',',$fields);
}
}else{
$fields = $this->fields;
}
foreach ($data as $key=>$val){
if(!in_array($key,$fields,true)){
if(!empty($this->options['strict'])){
E(L('_DATA_TYPE_INVALID_').':['.$key.'=>'.$val.']');
}
unset($data[$key]);
}elseif(is_scalar($val)) {
// 字段类型检查 和 强制转换
$this->_parseType($data,$key); // 是转换数据类型
}
}
}
// 安全过滤
if(!empty($this->options['filter'])) {
$data = array_map($this->options['filter'],$data); // 数据跑起来了
unset($this->options['filter']);
}
$this->_before_write($data);
return $data;
}//总结,就是对数据的 处理
// 各种数据 处理
// 写入数据前的回调方法 包括新增和更新
protected function _before_write(&$data) {
}
// 插入数据前的回调方法
protected function _before_insert(&$data,$op
[李景山php]thinkphp核心源码注释|Model.class.php
最新推荐文章于 2023-08-09 16:10:22 发布
本文深入探讨了ThinkPHP框架中Model类的核心源码,详细注释了Model.class.php文件,帮助开发者理解框架底层运行机制,提升PHP开发技能。
摘要由CSDN通过智能技术生成