学习笔记——阅读TP5的数据库访问类

一直觉得TP框架的数据库查询链式操作很有意思,闲来无事想去一探究竟,他是怎么实现的数据库查询呢?


TP5的目录结构跟TP3比大相径庭。简单介绍一下TP5的主目录结构:

├─application 应用目录
├─extend 扩展类库目录(可定义)
├─public 网站对外访问目录
├─runtime 运行时目录(可定义)
├─vendor 第三方类库目录(Composer)
├─thinkphp 框架核心目录
├─build.php 自动生成定义文件(参考)
├─composer.json Composer定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行工具入口


而数据库操作类是在thinkphp目录下面,我们用到的文件(已加粗)包括:
├─thinkphp
├──library
├───think
├────db
├─────bulider
├─────connector
├─────exception
├─────Builder.php
├─────Connection.php
├─────Query.php
├────Db.php


打开各个文件看,在Connection.php 和 Builder.php中定义了抽象类,而同名文件夹下的各个数据库对应类均引用了这个抽象类,从文件名不难理解Connection应该是做数据库连接的,而builder应该是做数据库sql语句处理的。
在use Db类之后,随便写一个简单的查询语句:
Db::table(‘persons’)->select();
我们看一下程序的执行是怎样的。
查看代码Query.php这个文件use引用了db类的所有文件,数据库查询即从这个文件开始。
当创建Db对象的时候,程序先执行了Query.php的构造方法:

    public function __construct(Connection $connection = null, $model = null)
    {
        $this->connection = $connection ?: Db::connect([], true);
        $this->prefix     = $this->connection->getConfig('prefix');
        $this->model      = $model;
        // 设置当前连接的Builder对象
        $this->setBuilder();
    }

一、构造方法中调用了Db类中的connect方法,并把结果赋给connection属性。

从connection中调用getConfig方法得到前缀信息赋值给prefix属性;
先看一下Db类中的connect方法做了哪些操作:

    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);

            if (empty($options['type'])) {
                throw new \InvalidArgumentException('Undefined 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');
            }

            if (true === $name) {
                $name = md5(serialize($config));
            }
            self::$instance[$name] = new $class($options);
        }

        return self::$instance[$name];
    }

    private static function parseConfig($config)
    {
        if (empty($config)) {
            $config = Config::get('database');
        } elseif (is_string($config) && false === strpos($config, '/')) {
            $config = Config::get($config); // 支持读取配置参数
        }

        return is_string($config) ? self::parseDsn($config) : $config;
    }

此方法又调用本类的parseConfig方法,parseConfig方法则从Config类中获取database数据,从config数组中的type字段获得类名,Config类是手动填写在config文件中的一些配置信息数组,type这里填的是mysql,所以如果填写的是其他数据库名称,则会创建其对应数据库的类;
并将config数组作为初始化参数传入对应类的构造函数中,最后将这个对象返回。
那么这里的类名是什么呢?打印出来看一下:
\think\db\connector\Mysql
connector文件夹下的mysql.php继承了Connection.php,Connection.php的构造函数中将之前传入的配置参数赋值给了config属性。

二、调用Builder类

上面的一系列操作并没有真正的连接数据库,我们从Db的connect方法中得到了connection类的实体对象,并且赋值给了Query类下面的connection属性;
而Query的构造函数第一个递归函数执行完毕,马上调用了本类中的setBuilder方法;
那么setBuilder方法做了些什么呢?先看代码:

    protected function setBuilder()
    {
        $class         = $this->connection->getBuilder();
        $this->builder = new $class($this->connection, $this);
    }

他调用了connection实体中的getBuilder方法,得到class并将其实例化,而参数是 t h i s > c o n n e c t i o n , this。将对象赋值给了builder属性

    public function getBuilder()
    {
        if (!empty($this->builder)) {
            return $this->builder;
        } else {
            return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type'));
        }
    }

到现在就会发现 之前有个bulider类一直没有用到,会不会是调用他了呢,那么数据库是从什么时候开始连接的?
再次打印class,猜的没错,这次创建了bulider对象:
\think\db\builder\Mysql
打开bulider文件,可以看到这里面定义了 增删改查 等操作的SQL表达式,还有一些对SQL字符串的处理方法。

三、初始化操作完成,得到包含connection和builder实体的Query对象

经过以上的这些操作,Query类中connection属性和bulider属性均保存了对应的实体,这样程序可以在query执行时调用到他们的各种方法。而我们的查询语句Db::table(‘persons’)->select();算是完成了Db::这部分的初始化处理。
现在调用Query类中的table方法:

    public function table($table)
    {
        if (is_string($table)) {
            if (strpos($table, ')')) {
                // 子查询
            } elseif (strpos($table, ',')) {
                $tables = explode(',', $table);
                $table  = [];
                foreach ($tables as $item) {
                    list($item, $alias) = explode(' ', trim($item));
                    if ($alias) {
                        $this->alias([$item => $alias]);
                        $table[$item] = $alias;
                    } else {
                        $table[] = $item;
                    }
                }
            } elseif (strpos($table, ' ')) {
                list($table, $alias) = explode(' ', $table);

                $table = [$table => $alias];
                $this->alias($table);
            }
        } else {
            $tables = $table;
            $table  = [];
            foreach ($tables as $key => $val) {
                if (is_numeric($key)) {
                    $table[] = $val;
                } else {
                    $this->alias([$key => $val]);
                    $table[$key] = $val;
                }
            }
        }
        $this->options['table'] = $table;
        return $this;
    }

将实体this返回实现链式操作,然后继续执行Query下面的select方法。
但是一直有个问题,其实到现在为止都还没有做数据库连接,那么到底是什么时候连接数据库的呢,继续往下看!
select方法也是很长很长,但是在里面我找到了这样一句话:

$resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']);

前面对我们传过来的各项数组或者字符串进行处理并拼接成了sql字符串,在这里调用了query方法,看一下query方法是怎么写的:

    public function query($sql, $bind = [], $master = false, $class = false)
    {
        return $this->connection->query($sql, $bind, $master, $class);
    }

当看到这里的connection的时候我觉得已经有点眉目了(connection类是做数据库连接的)
那么connection下面一定有个query方法,再去看connection类(讲道理类虽然不多,然而还是会有点晕!)

    public function query($sql, $bind = [], $master = false, $pdo = false)
    {
        $this->initConnect($master);
        if (!$this->linkID) {
            return false;
        }
        /*下面代码太多,省略不看了*/
    }

其实这里调用的initConnect从字面上已经能看出来,这个方法就是做数据库连接的了,其实下面的所有数据库操作方法(比如删、改、开启事务)前面都有这样一个init方法:

    protected function initConnect($master = true)
    {
        if (!empty($this->config['deploy'])) {
            // 采用分布式数据库
            if ($master || $this->transTimes) {
                if (!$this->linkWrite) {
                    $this->linkWrite = $this->multiConnect(true);
                }
                $this->linkID = $this->linkWrite;
            } else {
                if (!$this->linkRead) {
                    $this->linkRead = $this->multiConnect(false);
                }
                $this->linkID = $this->linkRead;
            }
        } elseif (!$this->linkID) {
            // 默认单数据库
            $this->linkID = $this->connect();
        }
    }

    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['result_type'])) {
                $this->fetchType = $config['result_type'];
            }
            try {
                if (empty($config['dsn'])) {
                    $config['dsn'] = $this->parseDsn($config);
                }
                if ($config['debug']) {
                    $startTime = microtime(true);
                }
                $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];
    }

至此数据库连接和查询都执行完毕了,return结果,最后就是我们想看到的了。

猜你喜欢

转载自blog.csdn.net/qq409451388/article/details/79804823