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):
 /**     * 架构函数     * 取得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类的关系没有注意到是继承,一直卡在那里,要不是作者的点拨,博客可能都没有进展)

原创粉丝点击