ThinkPHP5 数据库操作-----之流程分析(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011069013/article/details/53096102

目标

TP自从3.2.3开始就在使用PDO方式链接数据库,现在我就研究研究TP5的数据库链接操作 PDO。以及其对数据库操作 的流程。


TP5 默认使用的 是PDO 的方式链接数据库。下面对PDO进行了解释。

PDO解释

百度解释:
1、PDO并不能使用PDO扩展本身执行任何数据库操作,必须使用一个database-specific PDO driver(针对特定数据库的PDO驱动)访问数据库服务器。

2、PDO并不提供数据库抽象,它并不会重写SQL或提供数据库本身缺失的功能,如果你需要这种功能,你需要使用一个更加成熟的抽象层。

3、PDO需要PHP5核心OO特性的支持,所以它无法运行于之前的PHP版本。

作用:

(1)解决 数据库的注入问题


Notice:① ② …. 符号代表的是其他对应解释 .


分析TP5的数据库操作

  • Db 结构图

DB结构图

TP5和以前一样可以通过定义全局配置文件 database.php 来辅助Db类库的调用。

在普通的控制器类中调用Db,根据看到的辅助函数db()找到

操作数据有两种方式
Db::query("select * from think_user where status=1");
与
Db::contect()->query("select * from think_user where status=1");

//下面的 分析可以告诉你,这两个 调用的都是一个 Query类对象

一 获取数据库Db的实例

查看connect 这个方法:

 /**
 * 数据库初始化 并取得数据库类实例
 * @static
 * @access public
 * @param mixed         $config 连接配置
 * @param bool|string   $name 连接标识 true 强制重新连接
 * @return \think\db\Connection
 * @throws Exception
 */
public static function connect($config = [], $name = false)
{
    if (false === $name) {
        $name = md5(serialize($config));
    }
    if (true === $name || !isset(self::$instance[$name])) {
        // 解析连接参数 支持数组和字符串
        $options = self::parseConfig($config);
        //① 如果没有传入配置参数的话, 会通过$config = Config::get('database');这个来获取配置值。
        if (empty($options['type'])) {
            throw new \InvalidArgumentException('Underfined db type');
        }
        $class = false !== strpos($options['type'], '\\') ? $options['type'] : '\\think\\db\\connector\\' . ucwords($options['type']);
        // 记录初始化信息
        if (App::$debug) {
            Log::record('[ DB ] INIT ' . $options['type'], 'info');
            //哇咔咔,写入日志文件,可以看看方式,后面可能自己也用的上。
        }

        //② $class 打印出来为 \think\db\connector\Mysql ,在下面进行了实例化

        if (true === $name) {
            return new $class($options);
        } else {
            self::$instance[$name] = new $class($options);
        }
    }
    return self::$instance[$name];
}

结果:这里会去 实例化 \think\db\connector\Mysql('配置')

现在开始实例化 thinkphp\library\think\db\connector\Mysql.php 文件 的 Mysql 类,这个类实现了 连接器 connection 抽象类 ,即 thinkphp\library\think\db\Connection.php 文件。

Connection 这个抽象类,和 其子类 Mysql , Pgsql, … 等,构成了一个 类似 适配器 的设计模式。


1、通过 对象成员访问符

通过 Db::connect($config, $force)->name($name); 方式最终操作的是 \think\db\Query 这个类 。文件是:thinkphp\library\think\db\Query.php

分析

//在 连接器 connection 类中,有下面的魔术方法 call
/**
 * 调用Query类的查询方法
 * @access public
 * @param string    $method 方法名称
 * @param array     $args 调用参数
 * @return mixed
 */
public function __call($method, $args)
{
    if (!isset($this->query['database'])) {
        $class                   = $this->config['query'];
        $this->query['database'] = new $class($this);
    }
    //这里的 $calss 就是 \think\db\Query ; $model 就是 name

    return call_user_func_array([$this->query['database'], $method], $args);
}

查看 Query 类 的 name 方法

/**
 * 指定默认的数据表名(不含前缀)
 * @access public
 * @param string $name
 * @return $this
 */
public function name($name)
{
    $this->name = $name;
    return $this;
}

2、分析 作用域限定访问符

跟踪这个thinkphp/library/think/Db.php类,找到了下面这个

// 调用驱动类的方法
public static function __callStatic($method, $params)
{
    // 自动初始化数据库  -- 条用了回调
    return call_user_func_array([self::connect(), $method], $params);
}

//__callStatic 是PHP5.3后添加的新的魔术方法 
//__callStatic 当调用的静态方法不存在或权限不足时,会自动调用__callStatic方法。

这玩意 和最上面 对象成员访问符 操作的是一个 类。

所以才有这样的访问 方式:
Db::query(“select * from think_user where status=1”);

Db::contect()->query(”select * from think_user where status=1”);
功能完全相同


到这里 最开始的 那段 $model 所代表的代码就结束了。可惜好像 并有发现,和PDO有什么联系。都到这里了,任然没有链接数据库,没有任何错误报出。对于这种情况,TP官方给出的解释是:thinkphp的数据库链接是惰性的,只有在使用的时候才会去链接。


二 调用数据库 操作方法

根据 query 方法来探索。

/**
 * 执行查询 返回数据集
 * @access public
 * @param string      $sql    sql指令
 * @param array       $bind   参数绑定
 * @param boolean     $master 是否在主服务器读操作
 * @param bool|string $class  指定返回的数据集对象
 * @return mixed
 * @throws BindParamException
 * @throws PDOException
 */
public function query($sql, $bind = [], $master = false, $class = false)
{
    return $this->connection->query($sql, $bind, $master, $class);
}

//这里又 调用回 Connection 连接器 的 query 方法去了

Connection 链接器 的query 方法

/**
 * 执行查询 返回数据集
 * @access public
 * @param string        $sql sql指令
 * @param array         $bind 参数绑定
 * @param boolean       $master 是否在主服务器读操作
 * @param bool|string   $class 指定返回的数据集对象
 * @return mixed
 * @throws BindParamException
 * @throws PDOException
 */
public function query($sql, $bind = [], $master = false, $class = false)
{
    $this->initConnect($master); // ㈠ 初始化链接数据库
    if (!$this->linkID) {
        return false;
    }
    // 根据参数绑定组装最终的SQL语句
    $this->queryStr = $this->getRealSql($sql, $bind);

    //释放前次的查询结果
    if (!empty($this->PDOStatement)) {
        $this->free();
    }

    Db::$queryTimes++;
    try {
        // 调试开始
        $this->debug(true);
        // 预处理
        $this->PDOStatement = $this->linkID->prepare($sql);
        // 参数绑定
        $this->bindValue($bind);
        // 执行查询
        $result = $this->PDOStatement->execute();
        // 调试结束
        $this->debug(false);
        $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
        return $this->getResult($class, $procedure);
    } catch (\PDOException $e) {
        throw new PDOException($e, $this->config, $this->queryStr);
    }
}

㈠ 初始化数据库链接

这里调用了 了Connection 连接器的 connect() 链接数据库的方法。

/**
* 连接数据库方法
 * @access public
 * @param array         $config 连接参数
 * @param integer       $linkNum 连接序号
 * @param array|bool    $autoConnection 是否自动连接主数据库(用于分布式)
 * @return PDO
 * @throws Exception
 */
public function connect(array $config = [], $linkNum = 0, $autoConnection = false)
{
    if (!isset($this->links[$linkNum])) {
        if (!$config) {
            $config = $this->config;
        } else {
            $config = array_merge($this->config, $config);
        }
        // 连接参数
        if (isset($config['params']) && is_array($config['params'])) {
            $params = $config['params'] + $this->params;
        } else {
            $params = $this->params;
        }
        // 记录当前字段属性大小写设置
        $this->attrCase = $params[PDO::ATTR_CASE];
        // 记录数据集返回类型
        if (isset($config['resultset_type'])) {
            $this->resultSetType = $config['resultset_type'];
        }
        try {
            if (empty($config['dsn'])) {
                $config['dsn'] = $this->parseDsn($config);
            }
            if ($config['debug']) {
                $startTime = microtime(true);
            }
            //PDO 连接了
            $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); 

            if ($config['debug']) {
                // 记录数据库连接信息
                Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql');
            }
        } catch (\PDOException $e) {
            if ($autoConnection) {
                Log::record($e->getMessage(), 'error');
                return $this->connect($autoConnection, $linkNum);
            } else {
                throw $e;
            }
        }
    }
    return $this->links[$linkNum];
}

在上面找到 这句:

$this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); 

//这里就是 实例化 PDO 链接了。

到这里 PDO 的流程算是分析完毕了。


② 查看 thinkphp/library/think/Config.php 文件

//可以看到 在查看配置问价事,直接调用的是 静态属性 $config 里面的值,也就是说,在链接数据库之前,系统已经将配置信息加载到了静态属性 $config 。
//放在代码区的静态属性,不会因为对象完成而释放掉,值是不会丢失的。

/**
 * 获取配置参数 为空则获取所有配置
 * @param string    $name 配置参数名(支持二级配置 .号分割)
 * @param string    $range  作用域
 * @return mixed
 */
public static function get($name = null, $range = '')
{
    $range = $range ?: self::$range;
    // 无参数时获取所有
    if (empty($name) && isset(self::$config[$range])) {
        return self::$config[$range];
    }

    if (!strpos($name, '.')) {
        $name = strtolower($name);
        var_dump(self::$config[$range][$name]);
        return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null;
    } else {
        // 二维数组设置和获取支持
        $name    = explode('.', $name);
        $name[0] = strtolower($name[0]);
        return isset(self::$config[$range][$name[0]][$name[1]]) ? self::$config[$range][$name[0]][$name[1]] : null;
    }
}

结论

  • 在不调用Db类时,数据库是不会自动链接的。
  • 修改配置错误的密码后,采用Db::table(xxx) 没有报错,应该尚未链接数据库。 这里仅仅是 实例化了 链接类。
  • 调用Db下的 query 方法,报错误了:数据库链接错误。执行sql 语句时,TP5 链接了数据库。

  • 上面是我对TP5 链接数据库 流程的一个 简要分析。如果其他小伙伴有什么补充,请在下面留言。
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页