在我们学习和使用一个开发框架时,无论使用什么框架,如何连接数据库、对数据库进行增删改查都是学习的重点,在Laravel中我们可以通过两种方式与数据库进行交互:

  • DB, DB是与PHP底层的PDO直接进行交互的,通过查询构建器提供了一个方便的接口来创建及运行数据库查询语句。
  • Eloquent Model, Eloquent是建立在DB的查询构建器基础之上,对数据库进行了抽象的ORM,功能十分丰富让我们可以避免写复杂的SQL语句,并用优雅的方式解决了数据表之间的关联关系。

上面说的这两个部分都包括在了Illuminate/Database包里面,除了作为Laravel的数据库层Illuminate/Database还是一个PHP数据库工具集, 在任何项目里你都可以通过composer install illuminate/databse安装并使用它。

Database服务注册和初始化

Database也是作为一种服务注册到服务容器里提供给Laravel应用使用的,它的服务提供器是Illuminate\Database\DatabaseServiceProvider

public function register()
{Model::clearBootedModels();$this->registerConnectionServices();$this->registerEloquentFactory();$this->registerQueueableEntityResolver();
}

第一步:Model::clearBootedModels()。在 Eloquent 服务启动之前为了保险起见需要清理掉已经booted的Model和全局查询作用域

/*** Clear the list of booted models so they will be re-booted.** @return void*/
public static function clearBootedModels()
{static::$booted = [];static::$globalScopes = [];
}

第二步:注册ConnectionServices

protected function registerConnectionServices()
{$this->app->singleton('db.factory', function ($app) {return new ConnectionFactory($app);});$this->app->singleton('db', function ($app) {return new DatabaseManager($app, $app['db.factory']);});$this->app->bind('db.connection', function ($app) {return $app['db']->connection();});
}
  • db.factory用来创建数据库连接实例,它将被注入到DatabaseManager中,在讲服务容器绑定时就说过了依赖注入的其中一个作用是延迟初始化对象,所以只要在用到数据库连接实例时它们才会被创建。
  • db DatabaseManger 作为Database面向外部的接口,DB这个Facade就是DatabaseManager的静态代理。应用中所有与Database有关的操作都是通过与这个接口交互来完成的。
  • db.connection 数据库连接实例,是与底层PDO接口进行交互的底层类,可用于数据库的查询、更新、创建等操作。

所以DatabaseManager作为接口与外部交互,在应用需要时通过ConnectionFactory创建了数据库连接实例,最后执行数据库的增删改查是由数据库连接实例来完成的。

第三步:注册Eloquent工厂

protected function registerEloquentFactory()
{$this->app->singleton(FakerGenerator::class, function ($app) {return FakerFactory::create($app['config']->get('app.faker_locale', 'en_US'));});$this->app->singleton(EloquentFactory::class, function ($app) {return EloquentFactory::construct($app->make(FakerGenerator::class), $this->app->databasePath('factories'));});
}

启动数据库服务

public function boot()
{Model::setConnectionResolver($this->app['db']);Model::setEventDispatcher($this->app['events']);
}

数据库服务的启动主要设置 Eloquent Model 的连接分析器(connection resolver),让model能够用db服务连接数据库。还有就是设置数据库事件的分发器 dispatcher,用于监听数据库的事件。

DatabaseManager

上面说了DatabaseManager是整个数据库服务的接口,我们通过DB门面进行操作的时候实际上调用的就是DatabaseManager,它会通过数据库连接对象工厂(ConnectionFacotry)获得数据库连接对象(Connection),然后数据库连接对象会进行具体的CRUD操作。我们先看一下DatabaseManager的构造函数:

public function __construct($app, ConnectionFactory $factory)
{$this->app = $app;$this->factory = $factory;
}

ConnectionFactory是在上面介绍的绑定db服务的时候传递给DatabaseManager的。比如我们现在程序里执行了DB::table('users')->get(), 在DatabaseManager里并没有table方法然后就会触发魔术方法__call

class DatabaseManager implements ConnectionResolverInterface
{protected $app;protected $factory;protected $connections = [];public function __call($method, $parameters){return $this->connection()->$method(...$parameters);}public function connection($name = null){list($database, $type) = $this->parseConnectionName($name);$name = $name ?: $database;if (! isset($this->connections[$name])) {$this->connections[$name] = $this->configure($this->makeConnection($database), $type);}return $this->connections[$name];}}

connection方法会返回数据库连接对象,这个过程首先是解析连接名称parseConnectionName

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

如果没有指定连接名称,Laravel会使用database配置里指定的默认连接名称, 接下来makeConnection方法会根据连接名称来创建连接实例:

protected function makeConnection($name)
{//假定$name是'mysql', 从config/database.php中获取'connections.mysql'的配置$config = $this->configuration($name);//首先去检查在应用启动时是否通过连接名注册了extension(闭包), 如果有则通过extension获得连接实例//比如在AppServiceProvider里通过DatabaseManager::extend('mysql', function () {...})if (isset($this->extensions[$name])) {return call_user_func($this->extensions[$name], $config, $name);}//检查是否为连接配置指定的driver注册了extension, 如果有则通过extension获得连接实例if (isset($this->extensions[$driver])) {return call_user_func($this->extensions[$driver], $config, $name);}// 通过ConnectionFactory数据库连接对象工厂获取Mysql的连接类    return $this->factory->make($config, $name);
}

ConnectionFactory

上面makeConnection方法使用了数据库连接对象工程来获取数据库连接对象,我们来看一下工厂的make方法:

/*** 根据配置创建一个PDO连接** @param  array   $config* @param  string  $name* @return \Illuminate\Database\Connection*/
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 parseConfig(array $config, $name)
{return Arr::add(Arr::add($config, 'prefix', ''), 'name', $name);
}

在建立连接之前, 先通过parseConfig向配置参数中添加默认的 prefix 属性与 name 属性。

接下来根据配置文件中是否设置了读写分离。如果设置了读写分离,那么就会调用 createReadWriteConnection 函数,生成具有读、写两个功能的 connection;否则的话,就会调用 createSingleConnection 函数,生成普通的连接对象。

protected function createSingleConnection(array $config)
{$pdo = $this->createPdoResolver($config);return $this->createConnection($config['driver'], $pdo, $config['database'], $config['prefix'], $config);
}protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{......switch ($driver) {case 'mysql':return new MySqlConnection($connection, $database, $prefix, $config);case 'pgsql':return new PostgresConnection($connection, $database, $prefix, $config);......                }throw new InvalidArgumentException("Unsupported driver [$driver]");
}

创建数据库连接的方法createConnection里参数$pdo是一个闭包:

function () use ($config) {return $this->createConnector($config)->connect($config);
};

这就引出了Database服务中另一部份连接器Connector, Connection对象是依赖连接器连接上数据库的,所以在探究Connection之前我们先来看看连接器Connector。

Connector

illuminate/database中连接器Connector是专门负责与PDO交互连接数据库的,我们接着上面讲到的闭包参数$pdo往下看

createConnector方法会创建连接器:

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']}]");
}

这里我们还是以mysql举例看一下Mysql的连接器。

class MySqlConnector extends Connector implements ConnectorInterface
{public function connect(array $config){//生成PDO连接数据库时用的DSN连接字符串$dsn = $this->getDsn($config);//获取要传给PDO的选项参数$options = $this->getOptions($config);//创建一个PDO连接对象$connection = $this->createConnection($dsn, $config, $options);if (! empty($config['database'])) {$connection->exec("use `{$config['database']}`;");}//为连接设置字符集和collation$this->configureEncoding($connection, $config);//设置time zone$this->configureTimezone($connection, $config);//为数据库会话设置sql mode$this->setModes($connection, $config);return $connection;}
}

这样就通过连接器与PHP底层的PDO交互连接上数据库了。

Connection

所有类型数据库的Connection类都是继承了Connection父类:

class MySqlConnection extends Connection
{......
}class Connection implements ConnectionInterface
{public function __construct($pdo, $database = '', $tablePrefix = '', array $config = []){$this->pdo = $pdo;$this->database = $database;$this->tablePrefix = $tablePrefix;$this->config = $config;$this->useDefaultQueryGrammar();$this->useDefaultPostProcessor();}......   public function table($table){return $this->query()->from($table);}......public function query(){return new QueryBuilder($this, $this->getQueryGrammar(), $this->getPostProcessor());}......
}

Connection就是DatabaseManager代理的数据库连接对象了, 所以最开始执行的代码DB::table('users')->get()经过我们上面讲的历程,最终是由Connection来完成执行的,table方法返回了一个QueryBuilder对象,这个对象里定义里那些我们经常用到的where, get, first等方法, 它会根据调用的方法生成对应的SQL语句,最后通过Connection对象执行来获得最终的结果。 详细内容我们等到以后讲查询构建器的时候再看。

总结

说的东西有点多,我们来总结下文章里讲到的Database的这几个组件的角色

名称 作用
DB DatabaseManager的静态代理
DatabaseManager Database面向外部的接口,应用中所有与Database有关的操作都是通过与这个接口交互来完成的。
ConnectionFactory 创建数据库连接对象的类工厂
Connection 数据库连接对象,执行数据库操作最后都是通过它与PHP底层的PDO交互来完成的
Connector 作为Connection的成员专门负责通过PDO连接数据库

我们需要先理解了这几个组件的作用,在这些基础之上再去顺着看查询构建器的代码。

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

Laravel核心解读--Database(一)基础介绍相关推荐

  1. Laravel核心解读--Database(四) 模型关联

    Database 模型关联 上篇文章我们主要讲了Eloquent Model关于基础的CRUD方法的实现,Eloquent Model中除了基础的CRUD外还有一个很重要的部分叫模型关联,它通过面向对 ...

  2. Laravel核心解读--Database(三) 模型CRUD

    上一篇文章我们讲了Database的查询构建器QueryBuilder, 学习了QueryBuilder为构建生成SQL语句而提供的Fluent Api的代码实现.这一篇文章我们来学习Laravel ...

  3. Laravel核心解读--Database(二) 查询构建器

    上文我们说到执行DB::table('users')->get()是由Connection对象执行table方法返回了一个QueryBuilder对象,QueryBuilder提供了一个方便的接 ...

  4. Laravel核心解读--服务容器(IocContainer)

    Laravel的核心是IocContainer, 文档中称其为"服务容器",服务容器是一个用于管理类依赖和执行依赖注入的强大工具,Laravel中的功能模块比如 Route.Elo ...

  5. Laravel核心解读 -- 用户认证系统(基础介绍)

    用户认证系统(基础介绍) 使用过Laravel的开发者都知道,Laravel自带了一个认证系统来提供基本的用户注册.登录.认证.找回密码,如果Auth系统里提供的基础功能不满足需求还可以很方便的在这些 ...

  6. Laravel核心解读--完结篇

    过去一年时间写了20多篇文章来探讨了我认为的Larave框架最核心部分的设计思路.代码实现.通过更新文章自己在软件设计.文字表达方面都有所提高,在刚开始决定写Laravel源码分析地文章的时候我地期望 ...

  7. Laravel核心解读--完结篇 1

    过去一年时间写了20多篇文章来探讨了我认为的Larave框架最核心部分的设计思路.代码实现.通过更新文章自己在软件设计.文字表达方面都有所提高,在刚开始决定写Laravel源码分析地文章的时候我地期望 ...

  8. Laravel核心解读--用户认证系统的实现细节

    用户认证系统的实现细节 上一节我们介绍来Laravel Auth系统的基础知识,说了他的核心组件都有哪些构成,这一节我们会专注Laravel Auth系统的实现细节,主要关注Auth也就是AuthMa ...

  9. Laravel核心解读--Console内核

    Console内核 上一篇文章我们介绍了Laravel的HTTP内核,详细概述了网络请求从进入应用到应用处理完请求返回HTTP响应整个生命周期中HTTP内核是如何调动Laravel各个核心组件来完成任 ...

最新文章

  1. 统一客服消息返回错误:{errcode:43004,errmsg:require subscribe hint: [9Vv08633952]}
  2. 一边动,一边画,自己就变二次元!华人小哥参与的黑科技:实时交互式视频风格化...
  3. 面向技术编程,面向工资编程,面向用户编程?
  4. 多行列表右边距为零的实现方法
  5. Launcher3自定义壁纸旋转后拉伸无法恢复
  6. 安装 | R2021a链接及Matlab运行图
  7. 【echarts】echarts开发详解
  8. WinUI 3 Preview 3 发布了,再一次试试它的性能
  9. 常用的好用的window工具
  10. usb hub区分端口_树莓派上 USB 子系统拓扑浅析
  11. 移动应用开发——实验二
  12. R语言之数据处理常用包
  13. C#之double内存
  14. Flutter学习 — 实现滑动关闭、删除item
  15. ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any()
  16. SDN核心技术与内容
  17. vs项目筛选器显示错乱、只显示部分文件
  18. 学python看小甲鱼还是黑马_为什么我看完小甲鱼的python视频还是不会写呢?
  19. 2048小游戏(网页版)
  20. 《Learning to Reconstruct Botanical Trees from Single Images》学习从单幅图像重建植物树

热门文章

  1. Linux系统中硬盘的管理
  2. SQL Server 2012附加数据库报错
  3. C#输出带有换行符的字符串
  4. 你们觉得这个时代好还是父母那个时代好?
  5. Exchange 2013 邮箱管理
  6. [Android学习笔记四] 自定义Android组件之组合方式创建密码框组件
  7. 一维数组和二维数组创建,输出,Arrays.fill()替换
  8. IBM收购National Interest Security
  9. EXCHANGE 系列
  10. 进程间通信线程间通信