thinkphp3.2源码----获取并保存模型对象与数据库连接实例
来源:互联网 发布:网络稳定性测试软件app 编辑:程序博客网 时间:2024/05/20 04:29
摘自文档:
在ThinkPHP中基础的模型类就是 Think\Model 类,该类完成了基本的CURD、ActiveRecord模式、连贯
操作和统计查询,一些高级特性被封装到另外的模型扩展中。
基础模型类的设计非常灵活,甚至可以无需进行任何模型定义,就可以进行相关数据表的ORM和CURD操
作,只有在需要封装单独的业务逻辑的时候,模型类才是必须被定义的。
使用模型类我们可以对相关的数据表进行CURD操作,实例化模型类的方法是我们熟悉的M或者D方法,使用模型类之前,我们来看看thinkphp是然后让模型类去对数据表进行CURD操作的。
使用M方法实例化模型
因为D方法要先去查找某个具体的模型类,所有我们从M方法中去研究tp是如何获取数据库连接实例的。
我们写下M(‘user’)运行,因为我的数据库中没有这张表,最后tp会抛出异常,在以前tp错误异常处理中也介绍过tp处理异常的方法,通过tp返回的回溯追踪我们来看看M方法的执行过程:
通过上图我们可以看出,省去中间的一些细节操作,M的操作可以看成一下四步:- Think\Model->__construct
源码(Thinkphp/Library/Think/Model.class.php):
- Think\Model->__construct
/** * 架构函数 * 取得DB类的实例对象 字段检查 * @access public * @param string $name 模型名称 * @param string $tablePrefix 表前缀 * @param mixed $connection 数据库连接信息 */ public function __construct($name = '', $tablePrefix = '', $connection = '') { // 模型初始化 $this->_initialize(); // 获取模型名称 if (!empty($name)) { if (strpos($name, '.')) { // 支持 数据库名.模型名的 定义 list($this->dbNamedbName, $this->name) = explode('.', $name); } else { $this->name = $name; } } elseif (empty($this->name)) { $this->name = $this->getModelName(); } // 设置表前缀 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); }
(这个构造方法里的 _initialize方法先放一下,在以后的博客中再和大家一起学习,这里就先按照注释来,它是起模型初始化的作用;)
这个构造方法主要的作用就是完善当前Model的一些属性,包括模型名称,表前缀,数据库连接信息等,准备完毕后最后执行了一个db()方法,通过这个db方法来获取数据库连接
db方法:
/** * 切换当前的数据库连接 * @access public * @param integer $linkNum 连接序号 * @param mixed $config 数据库连接信息 * @param boolean $force 强制重新连接 * @return Model */ 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); } $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; }
Think\Model的构造方法中是这样调用db方法的: $this->db(0, empty($this->connection) ? $connection : $this->connection, true);
我们直接通过最后一个参数’true’就可以知道,Think\Model的构造方法中调用db方法是想创建一个新的数据库连接实例,所有我们直接看这一段:
if (!isset($this->_db[$linkNum]) || $force) { // 创建一个新的实例 if (!empty($config) && is_string($config) && false === strpos($config, '/')) { // 支持读取配置参数 $config = C($config); } $this->_db[$linkNum] = Db::getInstance($config); }
这个流程中首先判断你是否传递了连接数据库的字符串参数,如果有就通过C函数去分解,并组成一个$config数组(具体分解组装操作看C函数的源码),然后调用Db::getInstance($config)
获取连接实例,并赋值给Model的数据连接对象池。
getInstance($config):
/** * 取得数据库类实例 * @static * @access public * @param mixed $config 连接配置 * @return Object 返回数据库驱动类 */ public static function getInstance($config = array()) { $md5 = md5(serialize($config)); if (!isset(self::$instance[$md5])) { // 解析连接参数 支持数组和字符串 $options = self::parseConfig($config); // 兼容mysqli if ('mysqli' == $options['type']) { $options['type'] = 'mysql'; } // 如果采用lite方式 仅支持原生SQL 包括query和execute方法 $class = !empty($options['lite']) ? 'Think\Db\Lite' : 'Think\\Db\\Driver\\' . ucwords(strtolower($options['type'])); if (class_exists($class)) { self::$instance[$md5] = new $class($options); } else { // 类没有定义 E(L('_NO_DB_DRIVER_') . ': ' . $class); } } self::$_instance = self::$instance[$md5]; return self::$_instance; }
getInstance方法中,首先处理传递过来的连接数据库参数,在$options = self::parseConfig($config);
一句中的parseConfig方法里可以看到,如果我们什么也没有传递过来,就读取我们的数据库配置,parseConfig:
/** * 数据库连接参数解析 * @static * @access private * @param mixed $config * @return array */ private static function parseConfig($config) { if (!empty($config)) { if (is_string($config)) { return self::parseDsn($config); } $config = array_change_key_case($config); $config = array( 'type' => $config['db_type'], 'username' => $config['db_user'], 'password' => $config['db_pwd'], 'hostname' => $config['db_host'], 'hostport' => $config['db_port'], 'database' => $config['db_name'], 'dsn' => isset($config['db_dsn']) ? $config['db_dsn'] : null, 'params' => isset($config['db_params']) ? $config['db_params'] : null, 'charset' => isset($config['db_charset']) ? $config['db_charset'] : 'utf8', 'deploy' => isset($config['db_deploy_type']) ? $config['db_deploy_type'] : 0, 'rw_separate' => isset($config['db_rw_separate']) ? $config['db_rw_separate'] : false, 'master_num' => isset($config['db_master_num']) ? $config['db_master_num'] : 1, 'slave_no' => isset($config['db_slave_no']) ? $config['db_slave_no'] : '', 'debug' => isset($config['db_debug']) ? $config['db_debug'] : APP_DEBUG, 'lite' => isset($config['db_lite']) ? $config['db_lite'] : false, ); } else { $config = array( 'type' => C('DB_TYPE'), 'username' => C('DB_USER'), 'password' => C('DB_PWD'), 'hostname' => C('DB_HOST'), 'hostport' => C('DB_PORT'), 'database' => C('DB_NAME'), 'dsn' => C('DB_DSN'), 'params' => C('DB_PARAMS'), 'charset' => C('DB_CHARSET'), 'deploy' => C('DB_DEPLOY_TYPE'), 'rw_separate' => C('DB_RW_SEPARATE'), 'master_num' => C('DB_MASTER_NUM'), 'slave_no' => C('DB_SLAVE_NO'), 'debug' => C('DB_DEBUG', null, APP_DEBUG), 'lite' => C('DB_LITE'), ); } return $config; }
连接信息处理完后,就终于到了实例化数据库驱动的操作上了:
// 如果采用lite方式 仅支持原生SQL 包括query和execute方法 $class = !empty($options['lite']) ? 'Think\Db\Lite' : 'Think\\Db\\Driver\\' . ucwords(strtolower($options['type'])); if (class_exists($class)) { self::$instance[$md5] = new $class($options); } else { // 类没有定义 E(L('_NO_DB_DRIVER_') . ': ' . $class); }
在这里通过读取不同的配置信息,来决定调用哪一个数据库驱动,比如我的是Mysql数据库驱动,打印出这个$class 就是 “Think\Db\Driver\Mysql”,tp就调用Think\Db\Driver\Mysql.class.php这个Mysql驱动,这里就以Mysql驱动类为例吧:
<?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\Db\Driver;use Think\Db\Driver;/** * mysql数据库驱动 */class Mysql extends Driver{.......................
Mysql这个驱动类中并没有构造方法,但是他继承自Driver基类,先执行基类的构造方法:
Driver基类的构造方法( Think\Db\Driver.class.php):
/** * 架构函数 读取数据库配置信息 * @access public * @param array $config 数据库配置数组 */ public function __construct($config = '') { if (!empty($config)) { $this->config = array_merge($this->config, $config); if (is_array($this->config['params'])) { $this->options = $this->config['params'] + $this->options; } } }
配置完毕,现在Mysql这个类以及他的基类Driver都配置好了,最后一层一层的回到最开始的M函数:
/** * 实例化一个没有模型文件的Model * @param string $name Model名称 支持指定基础模型 例如 MongoModel:User * @param string $tablePrefix 表前缀 * @param mixed $connection 数据库连接信息 * @return Think\Model */function M($name = '', $tablePrefix = '', $connection = ''){ static $_model = array(); if (strpos($name, ':')) { list($class, $name) = explode(':', $name); } else { $class = 'Think\\Model'; } $guid = (is_array($connection) ? implode('', $connection) : $connection) . $tablePrefix . $name . '_' . $class; if (!isset($_model[$guid])) { $_model[$guid] = new $class($name, $tablePrefix, $connection); } return $_model[$guid];}
可以看到M函数返回的$_model[$guid]
就是我们前面解读的tp那一系列操作返回的数据库连接实例,以我现在配置的是Mysql驱动为例子,这个$_model[$guid]
是包含了Mysql.class.php以及Driver.class.php两个类的对象,而实例化这些数据库驱动又是通过实例化Model.class.php这个Model实现的,因此我们通过M函数就得到了三个实例对象:Model,Driver,Mysql,通过返回的这些对象就可以对数据库进行CURD操作了,我们平时用的所有关于数据库的操作都能在这个三个类中找到。
上述只是第一次使用M方法,当我们第二次或者第N次使用M方法时,tp又是如何保存连接对象和已经实例化的某一个模型对象呢?
首先我们来说保存已经实例化的模型对象:
在M中声明了一个静态属性$_model,当有相同的连接参数传入时,比如都是M(‘user’),
$guid = (is_array($connection) ? implode('', $connection) : $connection) . $tablePrefix . $name . '_' . $class;
在这里$guid这个变量就相当于记录的‘user’这个名字,后面再判断:
if (!isset($_model[$guid])) {
如果不存在‘$guid’所记录的名字才去实例化新的模型,所有说不管你用多少次M(‘user’) tp都只实例化了一次,极大的减少开销。
最后是怎么将保存连接对象:
如果是不同的模型,无论前面的代码怎么执行,最后要到Model类中的db方法(只有实例化模型类都要执行类的构造方法),执行db方法又回到了开始的Db类中去,Think\Db 下面的 Db.class.php这个类用一个比较时髦的词语来说就是门面,通过他决定你要走什么“门”,而在这个类中也有一个静态属性记录了你已经走过的“门牌号”,
Db.class.php:
<?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 数据库中间层实现类 */class Db{ private static $instance = array(); // 数据库连接实例 private static $_instance = null; // 当前数据库连接实例 .........
在取得数据库类实例的静态方法getInstance是这样控制的:
if (!isset(self::$instance[$md5])) { // 解析连接参数 支持数组和字符串 $options = self::parseConfig($config); // 兼容mysqli if ('mysqli' == $options['type']) { $options['type'] = 'mysql'; } // 如果采用lite方式 仅支持原生SQL 包括query和execute方法 $class = !empty($options['lite']) ? 'Think\Db\Lite' : 'Think\\Db\\Driver\\' . ucwords(strtolower($options['type'])); if (class_exists($class)) { self::$instance[$md5] = new $class($options); dump('我执行的是连接数据库'); } else { // 类没有定义 E(L('_NO_DB_DRIVER_') . ': ' . $class); } } ......
if (!isset(self::$instance[$md5])) {
如果不存在我们传入的连接参数才去执行后面的 self::$instance[$md5] = new $class($options);
实例操作,并且执行完一次后就赋值给刚刚说的“门牌号”,所以你只需要连接一次数据库就一直保存了这个数据库连接实例,极大减少了开销。
(写在后面:感谢thinkp3.2作者“麦当苗儿”的帮助,一开始Mysql类和Driver类的关系没有注意到是继承,一直卡在那里,要不是作者的点拨,博客可能都没有进展)
- thinkphp3.2源码----获取并保存模型对象与数据库连接实例
- 【thinkphp3.x】thinkphp3.x中D方法实例化模型对象
- 【thinkphp3.x】thinkphp3.x中M方法实例化模型对象
- 【thinkphp3.x】thinkphp3.x中D方法实例化模型对象
- 【thinkphp3.x】thinkphp3.x中M方法实例化模型对象
- thinkphp3.2【model模型】
- ThinkPHP3.2 模型
- JDBC与JNDI获取数据库连接对象注意事项
- 【面向对象课程项目:纸牌】Java实例学习(二):优秀源码与自己模型的对比
- Python获取网页源码并保存为文件Demo
- ThinkPHP3.2的关联模型
- java获取数据库连接对象
- 获取数据库连接对象
- spring获取数据库连接对象
- vb调用excel对象的使用实例:使用excel.application与workbook等并保存工作薄
- js获取地址栏参数,分离并保存为对象
- thinkphp3.2源码 ----- 自动加载
- QT与数据库连接实例
- 基于聚类的“图像分割”
- zabbix 配置监控项和web监控
- Linq的基本用法,代码
- Java 多文件夹合并
- Spring介绍
- thinkphp3.2源码----获取并保存模型对象与数据库连接实例
- 两个字符串连接
- jQuery自制手风琴效果增强版(附实现原理)
- 百度地图使用模拟器报错java.lang.IllegalArgumentException: No config chosen
- linux内核配置
- 重装电脑后,sqlserver各种不服
- 提示git error: failed to push some refs to 'git@github.com:
- linux必备软件合集
- Java heap space