03 08 2020

TP5的数据库相关操作类由 Connection(连接器)、Query(查询器)、Builder(sql生成器)组成。


例:

Db::query("select * from user where id=?", [1]);


因为Db类没有定义query(),所以触发了__callStatic(),__callStatic()又调用自身的connect(),connect()实例化Mysql连接器,然后保存到$instance。


thinkphp/library/think/Db.php

/**
 * 数据库连接参数解析
 * @access private
 * @param  mixed $config
 * @return array
 */
private static function parseConfig($config)
{
    if (is_string($config) && false === strpos($config, '/')) {
        // 支持读取配置参数
        $config = isset(self::$config[$config]) ? self::$config[$config] : self::$config;
    }

    $result = is_string($config) ? self::parseDsnConfig($config) : $config;

    if (empty($result['query'])) {
        $result['query'] = self::$config['query'];
    }

    return $result;
}
  
/**
 * 切换数据库连接
 * @access public
 * @param  mixed         $config 连接配置
 * @param  bool|string   $name 连接标识 true 强制重新连接
 * @param  string        $query 查询对象类名
 * @return mixed 返回查询对象实例
 * @throws Exception
 */
public static function connect($config = [], $name = false, $query = '')
{
    // 解析配置参数
    $options = self::parseConfig($config ?: self::$config);

    $query = $query ?: $options['query'];  // think\db\Query

    // 创建数据库连接对象实例
    self::$connection = Connection::instance($options, $name);  // 返回 think\db\connector\Mysql 对象

    return new $query(self::$connection);
}

public static function __callStatic($method, $args)
{
    return call_user_func_array([static::connect(), $method], $args);
}


thinkphp/library/think/db/Connection.php

/**
 * 取得数据库连接类实例
 * @access public
 * @param  mixed         $config 连接配置
 * @param  bool|string   $name 连接标识 true 强制重新连接
 * @return Connection
 * @throws Exception
 */
public static function instance($config = [], $name = false)
{
    if (false === $name) {
        $name = md5(serialize($config));
    }

    if (true === $name || !isset(self::$instance[$name])) {
        if (empty($config['type'])) {
            throw new InvalidArgumentException('Undefined db type');
        }

        // 记录初始化信息
        Container::get('app')->log('[ DB ] INIT ' . $config['type']);

        if (true === $name) {
            $name = md5(serialize($config));
        }

        // 工厂模式
        self::$instance[$name] = Loader::factory($config['type'], '\\think\\db\\connector\\', $config);
    }

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


thinkphp/library/think/db/Query.php

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


thinkphp/library/think/db/Connection.php

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

    if (!$this->linkID) {
        return false;
    }

    // 记录SQL语句
    $this->queryStr = $sql;

    $this->bind = $bind;

    Db::$queryTimes++;

    try {
        // 调试开始
        $this->debug(true);

        // 预处理
        $this->PDOStatement = $this->linkID->prepare($sql);

        // 是否为存储过程调用
        $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);

        // 参数绑定
        if ($procedure) {
            $this->bindParam($bind);
        } else {
            $this->bindValue($bind);
        }

        // 执行查询
        $this->PDOStatement->execute();

        // 调试结束
        $this->debug(false, '', $master);

        // 返回结果集
        return $this->getResult($pdo, $procedure);
    } catch (\PDOException $e) {
        if ($this->isBreak($e)) {
            return $this->close()->query($sql, $bind, $master, $pdo);
        }

        throw new PDOException($e, $this->config, $this->getLastsql());
    } catch (\Throwable $e) {
        if ($this->isBreak($e)) {
            return $this->close()->query($sql, $bind, $master, $pdo);
        }

        throw $e;
    } catch (\Exception $e) {
        if ($this->isBreak($e)) {
            return $this->close()->query($sql, $bind, $master, $pdo);
        }

        throw $e;
    }
}


执行顺序:

Db::query()触发Db::__callStatic(),实例化 Query 并传入通过工厂模式获取的Mysql实例,而Mysql连接器继承了Connection, 调用 query(),所以实际上是调用了Connection->query()