ThinkPHP 中数据库是如何连接的

2016/12/30 PHP

ThinkPHP 中数据库是如何连接的

之前分析了ThinkPHP在执行指定的方法之前的步骤. 链接: ThinkPHP学习笔记 下面来看看, 通过路由规则后, 实例化了指定的类, 然后执行指定的方法. 是怎么实现的.

先看看代码段:


code 1 /Users/qiuyu/www/thinkphp_3.2.3_full/Application/Home/Controller/IndexController.class.php

<?php
namespace Home\Controller;
use Think\Controller;

class IndexController extends Controller
{
    public function index()
	{
		$lesson_api = D('lesson');
		$res = $lesson_api->getLast();
dump($res);
	}
}

首先是继承了Controller类. Controller.class.php 这个类中, 有个构造方法.

code 2 /Users/qiuyu/www/thinkphp_3.2.3_full/ThinkPHP/Library/Think/Controller.class.php

   /**
     * 架构函数 取得模板对象实例
     * @access public
     */
    public function __construct() {
        Hook::listen('action_begin',$this->config);
        //实例化视图类
        $this->view     = Think::instance('Think\View');
        //控制器初始化
        if(method_exists($this,'_initialize'))
            $this->_initialize();
    }

作用有3点:

  1. 执行方法开始行为
  2. 实例化视图类
  3. 如果Controller.class.php的子类中有_initialize()方法, 就执行这个方法. 相当于子类中的构造方法.

再来看一下code 1中的D()方法.

code 3 /Users/qiuyu/www/thinkphp_3.2.3_full/ThinkPHP/Common/functions.php

/**
 * 实例化模型类 格式 [资源://][模块/]模型
 * @param string $name 资源地址
 * @param string $layer 模型层名称
 * @return Think\Model
 */
function D($name='',$layer='') {
    if(empty($name)) return new Think\Model;
    static $_model  =   array();
    $layer          =   $layer? : C('DEFAULT_M_LAYER');
    if(isset($_model[$name.$layer]))
        return $_model[$name.$layer];
    $class          =   parse_res_name($name,$layer);
    if(class_exists($class)) {
        $model      =   new $class(basename($name));
    }elseif(false === strpos($name,'/')){
        // 自动加载公共模块下面的模型
        if(!C('APP_USE_NAMESPACE')){
            import('Common/'.$layer.'/'.$class);
        }else{
            $class      =   '\\Common\\'.$layer.'\\'.$name.$layer;
        }
        $model      =   class_exists($class)? new $class($name) : new Think\Model($name);
    }else {
        Think\Log::record('D方法实例化没找到模型类'.$class,Think\Log::NOTICE);
        $model      =   new Think\Model(basename($name));
    }
    $_model[$name.$layer]  =  $model;
    return $model;
}

可以看到. 如果不传参数, 就是实例化Think\Model类.

  • 第一个参数, 就是要调用的模型层中的类名.
  • 第二个参数, 是在模型层分为多层的情况下使用, 指定是哪个模型层. 如果为空, 则按照配置文件, 值就是默认的模型层名称.
  • 根据代码if(isset($_model[$name.$layer]))可以看出, 是单例模式. 如果存在这个实例, 就直接返回, 防止多次实例化.
  • 最后, 根据参数, 实例化对应的类. 如果类不存在, 就实例化默认的Think\Model, 同时也把参数1在实例化的时候传过去了.

简单来说,D('lesson')的作用, 与下面的代码是等效的.

code 4

<?php
namespace Home\Controller;
use Think\Controller;

class IndexController extends Controller
{
    public function index()
	{
		// $lesson_api = D('lesson');
		$lesson_api = new \Home\Model\LessonModel('lesson'); // 与上面的代码是等效的.
		$res = $lesson_api->getLast();
dump($res);
	}
}

既然是实例化的\Home\Model\LessonModel, 那么就看看这个类.

code 5 /Users/qiuyu/www/thinkphp_3.2.3_full/Application/Home/Model/LessonModel.class.php

<?php
namespace Home\Model;
use Think\Model;

class LessonModel extends Model
{
	public function getLast()
	{
		$res = $this->field('lesson_id, lesson_name')->where('status = 1')->order('lesson_id desc')->limit(10)->select();
		return $res;
	}
}

这个Mode类比较复杂. 代码就近2000行, 根据流程一步一步的看下去吧. 首先先看这个类中的构造方法:

code 6 /Users/qiuyu/www/thinkphp_3.2.3_full/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->dbName,$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);
    }

分析一下这个构造方法的作用:

  1. 模型初始化, 作用与Contrller中的一样, 如果Model类的子类中, 有_initialize()方法, 则会执行. 为什么要这么写, 可以看看我的另一篇文章, 链接: ThinkPHP中的__construct()与_initilize()的区别
  2. 获取模型名称
  3. 设置表前缀
  4. 数据库初始化操作(重点)

最有一句代码, 又执行了这个类中的db()方法. 传了3个参数, 等效于$this->db(0, '', true)

db()方法的代码如下.

code 7 /Users/qiuyu/www/thinkphp_3.2.3_full/ThinkPHP/Library/Think/Model.class.php

    /**
     * 切换当前的数据库连接
     * @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;
    }

分析: 根据参数, 会执行$this->_db[$linkNum] = Db::getInstance($config);. 其中$config的值是"", 空字符串. 是db类中的一个静态方法. 这个db类是ThinkPHP的数据库中间层实现类. 只有4个方法. 下面是db类中的getInstance()方法.

code 8 /Users/qiuyu/www/thinkphp_3.2.3_full/ThinkPHP/Library/Think/Db.class.php

    /**
     * 取得数据库类实例
     * @static
     * @access public
     * @param mixed $config 连接配置
     * @return Object 返回数据库驱动类
     */
    static public 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;
    }

分析: 这个方法也是一个单例模式, 如果没有实力, 就运行if里面的代码. 其中的代码$options = self::parseConfig($config), 又调用了parseconfig()方法 .参数是空字符串. 下面是这个方法的代码.

code 9

    /**
     * 数据库连接参数解析
     * @static
     * @access private
     * @param mixed $config
     * @return array
     */
    static private 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;
    }

参数为空, 可以看到. 会获取数据库的配置信息. 然后返回. 继续执行code 8中的代码. 重要的代码: ` $class = !empty($options[‘lite’])? ‘Think\Db\Lite’ : ‘Think\Db\Driver\‘.ucwords(strtolower($options[‘type’])); $class的值为Think\Db\Driver\Mysql`.

然后会实例化$class, 并且把获取到的数据库配置信息传过去. Think\Db\Driver\Mysql这个类没有构造方法, 但继承了Driver,

code 10 /Users/qiuyu/www/thinkphp_3.2.3_full/ThinkPHP/Library/Think/Db/Driver/Mysql.class.php

namespace Think\Db\Driver;
use Think\Db\Driver;

/**
 * mysql数据库驱动 
 */
class Mysql extends Driver{
	...
	...
	...
}

那么再看看它的父类.

code 11 /Users/qiuyu/www/thinkphp_3.2.3_full/ThinkPHP/Library/Think/Db/Driver.class.php

namespace Think\Db;
use Think\Config;
use Think\Debug;
use Think\Log;
use PDO;

abstract class Driver {
	...
	...
	...
}

这个类中定义了数据的操作方法. 如最常用的select(), find(), where()等等. 可以看到. 父类是个抽象类. 但是没有抽象方法. 也就是说. 这个类只能被继承. 不能实例化. 仔细想想, ThinkPHP的核心是CBD, 也就是:核心, 行为, 驱动. 这个应该就是驱动的实现方式. 说的挺高大上, 直接说是抽象类, 大家不就都明白了么. 靠

再来看看这个类中的方法, 有构造方法. 接受的参数是数据库的配置信息.如下:

code 12 /Users/qiuyu/www/thinkphp_3.2.3_full/ThinkPHP/Library/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;
            }
        }
    }

这个构造方法的作用, 就是把数据库的配置信息, 加载入类中的属性中 $this->options. 方便以后的调用.

还有一个构造方法

code 13 /Users/qiuyu/www/thinkphp_3.2.3_full/ThinkPHP/Library/Think/Db/Driver.class.php

   /**
     * 析构方法
     * @access public
     */
    public function __destruct() {
        // 释放查询
        if ($this->PDOStatement){
            $this->free();
        }
        // 关闭连接
        $this->close();
    }

ok. 按照流程, 代码现在没有能够执行的了. 需要返回. 也就是说, code 8中的代码self::$instance[$md5] = new $class($options);执行完毕了. 继续执行code 8中的下面的代码.

再返回到code 7中的代码, 这个时候, 代码$this->_db[$linkNum] = Db::getInstance($config);已经执行完毕了. 输出$this->_db[$linkNum], 结果是:

object(Think\Db\Driver\Mysql)#7 (17) {
  ["PDOStatement":protected] => NULL
  ["model":protected] => string(7) "_think_"
  ["queryStr":protected] => string(0) ""
  ["modelSql":protected] => array(0) {
  }
  ["lastInsID":protected] => NULL
  ["numRows":protected] => int(0)
  ["transTimes":protected] => int(0)
  ["error":protected] => string(0) ""
  ["linkID":protected] => array(0) {
  }
  ["_linkID":protected] => NULL
  ["config":protected] => array(17) {
    ["type"] => string(5) "mysql"
    ["hostname"] => string(13) "192.168.1.186"
    ["database"] => string(12) "zhulong_edu3"
    ["username"] => string(3) "sns"
    ["password"] => string(6) "123456"
    ["hostport"] => string(4) "3306"
    ["dsn"] => NULL
    ["params"] => array(0) {
    }
    ["charset"] => string(4) "utf8"
    ["prefix"] => string(0) ""
    ["debug"] => bool(true)
    ["deploy"] => int(0)
    ["rw_separate"] => bool(false)
    ["master_num"] => int(1)
    ["slave_no"] => string(0) ""
    ["db_like_fields"] => string(0) ""
    ["lite"] => NULL
  }
  ["exp":protected] => array(14) {
    ["eq"] => string(1) "="
    ["neq"] => string(2) "<>"
    ["gt"] => string(1) ">"
    ["egt"] => string(2) ">="
    ["lt"] => string(1) "<"
    ["elt"] => string(2) "<="
    ["notlike"] => string(8) "NOT LIKE"
    ["like"] => string(4) "LIKE"
    ["in"] => string(2) "IN"
    ["notin"] => string(6) "NOT IN"
    ["not in"] => string(6) "NOT IN"
    ["between"] => string(7) "BETWEEN"
    ["not between"] => string(11) "NOT BETWEEN"
    ["notbetween"] => string(11) "NOT BETWEEN"
  }
  ["selectSql":protected] => string(109) "SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%LOCK%%COMMENT%"
  ["queryTimes":protected] => int(0)
  ["executeTimes":protected] => int(0)
  ["options":protected] => array(4) {
    [8] => int(2)
    [3] => int(2)
    [11] => int(0)
    [17] => bool(false)
  }
  ["bind":protected] => array(0) {
  }
}

这些信息. 都是/Users/qiuyu/www/thinkphp_3.2.3_full/ThinkPHP/Library/Think/Db/Driver.class.php这个类中的属性. 还有就是数据库的配置信息.

然后, Model的构造方法, 也就是code 7中返回了这个实例化后的实例. 这样, 我们在Model层中, 就已经获得了数据库的实例, 只需要像code 5那样, 继承了Model之后, 就可以直接写类似于$this->query(), $this->find()的语句. 进行数据库操作.

Search

    Post Directory