文档章节

Laravel5.3之Query Builder源码解析(上)

botkenni
 botkenni
发布于 2018/06/08 17:00
字数 1489
阅读 4
收藏 0

说明:本文主要学习Laravel Database模块的Query Builder源码。实际上,Laravel通过Schema Builder来设计数据库,通过Query Builder来CURD数据库。Query Builder并不复杂或神秘,只是在PDO扩展的基础上又开放封闭的包装了一层,提供了fluent api,使得书写的代码也很简洁流畅。在看下Query Builder源码之前,先大概探索下illuminate/database package的目录结构。

开发环境: Laravel5.3 + PHP7

Folder/File Description
Capsule Capsule文件夹下只有一个Manager类,主要实现了容器实例化,DatabaseManager和ConnectionFactory的实例化
Connectors 里面包含了四种DB的链接器:MySQLConnector,PostgresConnector,SQLiteConnector,SqlServerConnector,是主要的组件之一,用来CRUD时链接对应的DB
Console 该文件内包含migration和seed的命令,如php artisan db:seed, php artisan migrate
Eloquent 该文件夹内包含的就是Eloquent的主要实现类,如重点的Model类,Builder类,Relations子文件夹内包含的表的关系类。是核心的组件,也是类最多的文件夹
Events 装载事件类的文件夹
Migrations 实际执行migrate相关命令的类
Query Query Builder的代码主要在这个文件夹,主要的类是Builder类,还包括Grammars和Processors两大类别,根据四个不同的DB分门别类
Schema 是设计database的主要参与类,主要的类是Builder类和Blueprint类,还有Grammars类别,根据四个不同的DB分门别类
Connection class 数据库链接类,封装了PDO,是重要的类
DatabaseManager class 在DatabaseServiceProvider注册为'db',通常会通过该manager来'向下走'到对应的数据库实现类,是重要的类
Seeder class 主要负责seed命令时的操作

数据库连接的实例化

Query Builder主要在Query文件夹下,以一行简单又经常使用的代码为例来学习下内部实现的原理吧:

Route::get('/query_builder', function() {
    // Query Builder
    return DB::table('users')->where('id', '=', 1)->get();
});

// Illuminate/Support/Facades/DB
class DB extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'db';
    }
}

在DatabaseServiceProvider已经注册了名为'db'的服务即DatabaseManager对象,则实际上魔术调用DatabaseManager中的table()方法,看下__call()魔术方法源码:

    // $method = 'table', $parameters = 'users'
    public function __call($method, $parameters)
    {
        return $this->connection()->$method(...$parameters);
    }

所以重点是connection()方法,该方法返回的是Connection对象,看下connection()方法源码:

public function connection($name = null)
    {
        // $name = 'mysql', $type = null
        list($name, $type) = $this->parseConnectionName($name);

        // 首次在$connections[]中没有'mysql' => $mysql_connection,所以需要根据配置创建对应DB连接
        if (! isset($this->connections[$name])) {
            // 重点是makeConnection()创建了mysql连接实例
            $connection = $this->makeConnection($name);
            
            // 由于$type是null,不是'write'或'read',所以实际上啥也没做
            $this->setPdoForType($connection, $type);

            // 得到连接实例$connection后,还需要对该实例做准备工作,如绑定事件,设置connector
            $this->connections[$name] = $this->prepare($connection);
        }

        return $this->connections[$name];
    }
    
    protected function parseConnectionName($name)
    {
        $name = $name ?: $this->getDefaultConnection();
        // 检查是否以::read, ::write结尾
        return Str::endsWith($name, ['::read', '::write'])
                            ? explode('::', $name, 2) : [$name, null];
    }
    
    public function getDefaultConnection()
    {
        // laravel默认是mysql,这里假定是常用的mysql连接
        return $this->app['config']['database.default'];
    }

通过上面源码知道重点是makeConnection($name)方法,该方法根据传入的mysql名称,来实例化出一个Connection对象,重点看下makeConnection()源码:

    protected function makeConnection($name)
    {
        // 从config/database.php中获取'connections.mysql'的配置
        $config = $this->getConfig($name);

        // 如果已经自定义了连接,如在AppServiceProvider的boot()中又使用DatabaseManager::extend()方法自定义了一个'mysql'连接实例,
        // 那就用该实例,这里假设没有自定义
        if (isset($this->extensions[$name])) {
            return call_user_func($this->extensions[$name], $config, $name);
        }

        // $driver = 'mysql'
        $driver = $config['driver'];

        if (isset($this->extensions[$driver])) {
            return call_user_func($this->extensions[$driver], $config, $name);
        }
        
        // 通过ConnectionFactory类工厂模式获取Mysql的连接类    
        return $this->factory->make($config, $name);
    }

实际上最后还是通过\Illuminate\Database\Connectors\ConnectionFactory来解析出对应的connection,这里使用了工厂模式,看下该工厂类的make()方法源码:

    public function make(array $config, $name = null)
    {
        $config = $this->parseConfig($config, $name);

        if (isset($config['read'])) {
            return $this->createReadWriteConnection($config);
        }

        return $this->createSingleConnection($config);
    }
    
    protected function createSingleConnection(array $config)
    {
        // $pdo是个闭包
        $pdo = $this->createPdoResolver($config);

        return $this->createConnection(
            // $config['driver'] = 'mysql', $config['database'] = 'homestead'(数据库名称)
            $config['driver'], $pdo, $config['database'], $config['prefix'], $config
        );
    }
    
    protected function createPdoResolver(array $config)
    {
        return function () use ($config) {
            return $this->createConnector($config)->connect($config);
        };
    }

深入代码发现,最后是通过该工厂类的createConnection()方法来造出的一个Connection对象,createConnection()源码就是常见的傻瓜式的工厂构造函数:

    protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
    {
        // 容器中已经绑定了'db.connection.mysql'服务就解析出该服务,这里是没有注册的
        if ($this->container->bound($key = "db.connection.{$driver}")) {
            return $this->container->make($key, [$connection, $database, $prefix, $config]);
        }

        // $driver = 'mysql'
        switch ($driver) {
            case 'mysql':
                return new MySqlConnection($connection, $database, $prefix, $config);
            case 'pgsql':
                return new PostgresConnection($connection, $database, $prefix, $config);
            case 'sqlite':
                return new SQLiteConnection($connection, $database, $prefix, $config);
            case 'sqlsrv':
                return new SqlServerConnection($connection, $database, $prefix, $config);
        }

        throw new InvalidArgumentException("Unsupported driver [$driver]");
    }

总之,通过以上一步步分析就拿到了Connection这个对象了,DatabaseManager中的__call()方法中最后执行的是(new MysqlConnection(*))->table('users')->where('id', 1)->get()

OK, 这里注意下MySqlConnection的构造参数$connection是个闭包,该闭包的值是ConnectionFactory::createPdoResolver()的返回值,看下闭包里的操作:

    protected function createPdoResolver(array $config)
    {
        return function () use ($config) {
            return $this->createConnector($config)->connect($config);
        };
    }
    
    public function createConnector(array $config)
    {
        if (! isset($config['driver'])) {
            throw new InvalidArgumentException('A driver must be specified.');
        }

        if ($this->container->bound($key = "db.connector.{$config['driver']}")) {
            return $this->container->make($key);
        }

        switch ($config['driver']) {
            case 'mysql':
                return new MySqlConnector;
            case 'pgsql':
                return new PostgresConnector;
            case 'sqlite':
                return new SQLiteConnector;
            case 'sqlsrv':
                return new SqlServerConnector;
        }

        throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]");
    }

很简单就能知道该闭包一旦执行时,实际上执行的行为类似于(new MySqlConnector)->connect($config)

这里,就已经得到了链接器实例MySqlConnection了,该connection中还装着一个(new MySqlConnector)->connect($config),下文在其使用时再聊下其具体连接逻辑。

总结:第一步数据库连接实例化已经走完了,已经拿到了连接实例MySqlConnection,下一步将学习下connect()连接器是如何连接数据库的,和如何编译执行SQL语句得到user_id为1的结果值。到时见。

© 著作权归作者所有

共有 人打赏支持
botkenni
粉丝 20
博文 409
码字总数 434882
作品 0
西城
程序员
私信 提问
Laravel5.3之Query Builder源码解析(上)

说明:本文主要学习Laravel Database模块的Query Builder源码。实际上,Laravel通过Schema Builder来设计数据库,通过Query Builder来CURD数据库。Query Builder并不复杂或神秘,只是在PDO扩...

botkenni
2016/12/05
18
0
Laravel 5.3之 Query Builder 源码解析(中)

说明:本篇主要学习数据库连接阶段和编译SQL语句部分相关源码。实际上,上篇已经聊到通过连接工厂类构造出了实例(假设驱动driver是mysql),在该MySqlConnection中主要有三件利器:;;,其中是...

botkenni
2016/12/05
11
0
Laravel5.3之Session源码解析(下)

说明:在中篇中学习了session的CRUD增删改查操作,本篇主要学习关闭session的相关源码。实际上,在Laravel5.3中关闭session主要包括两个过程:保存当前URL到session介质中;在Response Head...

botkenni
2016/11/19
56
0
Laravel5.3之Container源码解析

说明:本文主要学习Laravel中Container的源码,主要学习Container的绑定和解析过程,和解析过程中的依赖解决。分享自己的研究心得,希望对别人有所帮助。实际上Container的绑定主要有三种方式...

botkenni
2016/10/28
16
0
Laravel 学习笔记5.3之 Query Builder 源码解析(下)

说明:本文主要学习下Query Builder编译为的细节和执行SQL的过程。实际上,上一篇聊到了这个非常重要的类,这个类含有三个主要的武器:。主要就是在执行SQL时做MySql数据库操作,主要就是用来...

botkenni
2016/12/05
22
0

没有更多内容

加载失败,请刷新页面

加载更多

Prometheus简介

Prometheus是什么? Prometheus(普罗米修斯)是一套最初在SoundCloud上构建的开源监视和告警系统 。 特征 普罗米修斯的主要特点是: 具有由度量名称和键/值对标识的时间序列数据的多维数据模...

阿dai学长
26分钟前
1
0
“阿里巴巴小程序繁星计划”:20亿扶持200万小程序开发者和100万商家

3月21日,在2019阿里云峰会·北京站上,阿里巴巴旗下的阿里云、支付宝、淘宝、钉钉、高德等联合发布“阿里巴巴小程序繁星计划”:提供20亿元补贴,扶持200万+小程序开发者、100万+商家。凡入...

阿里云云栖社区
37分钟前
3
0
Android 动画Animation

动画分为视图动画(view animation)和属性动画(property animation),视图动画又分为帧动画和补间动画 视图动画控件(iv)点击事件(OnClickListener接口)触发位置在原位置 1.帧动画(Fra...

Coding缘
今天
1
0
Mysql-常用日期查询

今天: SELECT * FROM A WHERE create_time = create_time(now()); 昨天: SELECT * FROM A WHERE TO_DAYS( NOW( ) ) - TO_DAYS( create_time) <= 1; 最近7天: SELECT * FROM A where DATE......

米饭有毒
今天
3
0
如何离线分析Kafka海量业务消息?1分钟快速为您支招

场景介绍 说起Kafka,许多使用者对它是又爱又恨。Kafka是一种分布式的、基于发布/订阅的消息系统,其极致体验让人欲罢不能,但操心的运维、复杂的安全策略、可靠性易用性的缺失等,仍需要使用...

中间件小哥
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部