laravel查询底层原理

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

开发环境: Laravel5.3 + PHP7

Folder/FileDescription
CapsuleCapsule文件夹下只有一个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相关命令的类
QueryQuery 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文件夹下,以一行简单又经常使用的代码为例来学习下内部实现的原理吧:

 
  1. Route::get('/query_builder', function() {

  2. // Query Builder

  3. return DB::table('users')->where('id', '=', 1)->get();

  4. });

  5.  
  6. // Illuminate/Support/Facades/DB

  7. class DB extends Facade

  8. {

  9. /**

  10. * Get the registered name of the component.

  11. *

  12. * @return string

  13. */

  14. protected static function getFacadeAccessor()

  15. {

  16. return 'db';

  17. }

  18. }

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

 
  1. // $method = 'table', $parameters = 'users'

  2. public function __call($method, $parameters)

  3. {

  4. return $this->connection()->$method(...$parameters);

  5. }

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

 
  1. public function connection($name = null)

  2. {

  3. // $name = 'mysql', $type = null

  4. list($name, $type) = $this->parseConnectionName($name);

  5.  
  6. // 首次在$connections[]中没有'mysql' => $mysql_connection,所以需要根据配置创建对应DB连接

  7. if (! isset($this->connections[$name])) {

  8. // 重点是makeConnection()创建了mysql连接实例

  9. $connection = $this->makeConnection($name);

  10.  
  11. // 由于$type是null,不是'write'或'read',所以实际上啥也没做

  12. $this->setPdoForType($connection, $type);

  13.  
  14. // 得到连接实例$connection后,还需要对该实例做准备工作,如绑定事件,设置connector

  15. $this->connections[$name] = $this->prepare($connection);

  16. }

  17.  
  18. return $this->connections[$name];

  19. }

  20.  
  21. protected function parseConnectionName($name)

  22. {

  23. $name = $name ?: $this->getDefaultConnection();

  24. // 检查是否以::read, ::write结尾

  25. return Str::endsWith($name, ['::read', '::write'])

  26. ? explode('::', $name, 2) : [$name, null];

  27. }

  28.  
  29. public function getDefaultConnection()

  30. {

  31. // laravel默认是mysql,这里假定是常用的mysql连接

  32. return $this->app['config']['database.default'];

  33. }

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

 
  1. protected function makeConnection($name)

  2. {

  3. // 从config/database.php中获取'connections.mysql'的配置

  4. $config = $this->getConfig($name);

  5.  
  6. // 如果已经自定义了连接,如在AppServiceProvider的boot()中又使用DatabaseManager::extend()方法自定义了一个'mysql'连接实例,

  7. // 那就用该实例,这里假设没有自定义

  8. if (isset($this->extensions[$name])) {

  9. return call_user_func($this->extensions[$name], $config, $name);

  10. }

  11.  
  12. // $driver = 'mysql'

  13. $driver = $config['driver'];

  14.  
  15. if (isset($this->extensions[$driver])) {

  16. return call_user_func($this->extensions[$driver], $config, $name);

  17. }

  18.  
  19. // 通过ConnectionFactory类工厂模式获取Mysql的连接类

  20. return $this->factory->make($config, $name);

  21. }

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

 
  1. public function make(array $config, $name = null)

  2. {

  3. $config = $this->parseConfig($config, $name);

  4.  
  5. if (isset($config['read'])) {

  6. return $this->createReadWriteConnection($config);

  7. }

  8.  
  9. return $this->createSingleConnection($config);

  10. }

  11.  
  12. protected function createSingleConnection(array $config)

  13. {

  14. // $pdo是个闭包

  15. $pdo = $this->createPdoResolver($config);

  16.  
  17. return $this->createConnection(

  18. // $config['driver'] = 'mysql', $config['database'] = 'homestead'(数据库名称)

  19. $config['driver'], $pdo, $config['database'], $config['prefix'], $config

  20. );

  21. }

  22.  
  23. protected function createPdoResolver(array $config)

  24. {

  25. return function () use ($config) {

  26. return $this->createConnector($config)->connect($config);

  27. };

  28. }

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

 
  1. protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])

  2. {

  3. // 容器中已经绑定了'db.connection.mysql'服务就解析出该服务,这里是没有注册的

  4. if ($this->container->bound($key = "db.connection.{$driver}")) {

  5. return $this->container->make($key, [$connection, $database, $prefix, $config]);

  6. }

  7.  
  8. // $driver = 'mysql'

  9. switch ($driver) {

  10. case 'mysql':

  11. return new MySqlConnection($connection, $database, $prefix, $config);

  12. case 'pgsql':

  13. return new PostgresConnection($connection, $database, $prefix, $config);

  14. case 'sqlite':

  15. return new SQLiteConnection($connection, $database, $prefix, $config);

  16. case 'sqlsrv':

  17. return new SqlServerConnection($connection, $database, $prefix, $config);

  18. }

  19.  
  20. throw new InvalidArgumentException("Unsupported driver [$driver]");

  21. }

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

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

 
  1. protected function createPdoResolver(array $config)

  2. {

  3. return function () use ($config) {

  4. return $this->createConnector($config)->connect($config);

  5. };

  6. }

  7.  
  8. public function createConnector(array $config)

  9. {

  10. if (! isset($config['driver'])) {

  11. throw new InvalidArgumentException('A driver must be specified.');

  12. }

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

  15. return $this->container->make($key);

  16. }

  17.  
  18. switch ($config['driver']) {

  19. case 'mysql':

  20. return new MySqlConnector;

  21. case 'pgsql':

  22. return new PostgresConnector;

  23. case 'sqlite':

  24. return new SQLiteConnector;

  25. case 'sqlsrv':

  26. return new SqlServerConnector;

  27. }

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

  30. }

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

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

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值