laravel中的数据库也是以服务提供者进行初始化的名为DatabaseServiceProvider,在config文件的providers数组中有写。路径为vendor\laravel\framework\src\Illuminate\Database\DatabaseServiceProvider.php

跟以往的serviceProvider一样在register方法中注册,在boot方法中引导加载。

来看一下register方法。为了保险起见它先通过Model将之前的加载数据给清除掉了。随后开始注册各种数据库连接所用到的对象,通过singleton方法注册一个单例的延迟加载对象到容器中。将DB门面类绑定到了DatabaseManager类中。

    public function register()
{
Model::clearBootedModels(); $this->registerConnectionServices(); $this->registerEloquentFactory(); $this->registerQueueableEntityResolver();
} protected function registerConnectionServices()
{
// The connection factory is used to create the actual connection instances on
// the database. We will inject the factory into the manager so that it may
// make the connections while they are actually needed and not of before.
$this->app->singleton('db.factory', function ($app) {
return new ConnectionFactory($app);
}); // The database manager is used to resolve various connections, since multiple
// connections might be managed. It also implements the connection resolver
// interface which may be used by other components requiring connections.
$this->app->singleton('db', function ($app) {
return new DatabaseManager($app, $app['db.factory']);
}); $this->app->bind('db.connection', function ($app) {
return $app['db']->connection();
});
} /**
* Register the Eloquent factory instance in the container.
*
* @return void
*/
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')
);
});
} /**
* Register the queueable entity resolver implementation.
*
* @return void
*/
protected function registerQueueableEntityResolver()
{
$this->app->singleton(EntityResolver::class, function () {
return new QueueEntityResolver;
});
}

数据库连接对象ConnectionFactory。这个连接工厂类之中的各种方法都是在创建配置,以及通过配置数组,返回对应的数据库连接实例。这个类中的方法大多是对数据库的连接做一些配置,然后根据这些配置来返回相应的数据库连接实例。

    /**
* Create a new connection instance.
*
* @param string $driver
* @param \PDO|\Closure $connection
* @param string $database
* @param string $prefix
* @param array $config
* @return \Illuminate\Database\Connection
*
* @throws \InvalidArgumentException
*/
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{
if ($resolver = Connection::getResolver($driver)) {
return $resolver($connection, $database, $prefix, $config);
} 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]");
}

数据库管理对象DatabaseManager。这个数据库管理类之中的各种方法也是通过各种数据库配置来调用ConnectionFactory工厂来返回数据库连接实例,它会通过配置read,write来返回相应的读写pdo实例。以及包含了数据库实例的创建与断开销毁等。set、get各种配置。如setPdoForType方法来设置数据库连接的读写分离(设置只读、只写)。那么这个类的上游方法在哪里呢。它是从哪里被调用的呢?我们开头提了一句,DB门面类所绑定的类,就是这个类,但是如果我们去这个类中寻找常用的talbe()、query()等方法,肯定是一无所获的,不过我们会发现__call()方法,这个魔术方法会在调用不存在的方法时执行,看一下它的内容。它只有一句代码,从$this->connection()这个对象中,执行相应的方法并返回结果。是的,laravel的源码封装度太高了,这里我们暂时只需要知道$this->connection()代表了数据库连接实例就好。

public function __call($method, $parameters)
{
return $this->connection()->$method(...$parameters);
}

刚刚说到数据库连接实例,现在我们就来探寻这个实例是如何被创建出来的。如下makeConnection方法所示,通过刚刚说到的ConnectionFactory来返回数据库连接实例。

    /**
* Prepare the read / write mode for database connection instance.
*
* @param \Illuminate\Database\Connection $connection
* @param string $type
* @return \Illuminate\Database\Connection
*/
protected function setPdoForType(Connection $connection, $type = null)
{
if ($type == 'read') {
$connection->setPdo($connection->getReadPdo());
} elseif ($type == 'write') {
$connection->setReadPdo($connection->getPdo());
} return $connection;
} /**
* Make the database connection instance.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
protected function makeConnection($name)
{
$config = $this->configuration($name); // First we will check by the connection name to see if an extension has been
// registered specifically for that connection. If it has we will call the
// Closure and pass it the config allowing it to resolve the connection.
if (isset($this->extensions[$name])) {
return call_user_func($this->extensions[$name], $config, $name);
} // Next we will check to see if an extension has been registered for a driver
// and will call the Closure if so, which allows us to have a more generic
// resolver for the drivers themselves which applies to all connections.
if (isset($this->extensions[$driver = $config['driver']])) {
return call_user_func($this->extensions[$driver], $config, $name);
} return $this->factory->make($config, $name);
}

好的,看到DatabaseManager如何创建出数据库连接实例,又要把视线跳到之前说的ConnectionFactory类中了。$this->factory->make($config, $name);最后返回了make方法,我们就从这个方法入手,请看下列代码。

    /**
* Establish a PDO connection based on the configuration.
*
* @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);
} /**
* Create a single database connection instance.
*
* @param array $config
* @return \Illuminate\Database\Connection
*/
protected function createSingleConnection(array $config)
{
$pdo = $this->createPdoResolver($config); return $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);
} /**
* Create a new connection instance.
*
* @param string $driver
* @param \PDO|\Closure $connection
* @param string $database
* @param string $prefix
* @param array $config
* @return \Illuminate\Database\Connection
*
* @throws \InvalidArgumentException
*/
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{
if ($resolver = Connection::getResolver($driver)) {
return $resolver($connection, $database, $prefix, $config);
} 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]");
}

可以看到,经过一系列的方法跳转,我们终于通过数据库配置,得到了mysql等数据库的连接对象。

数据库工厂类ConnectionFactory返回的实例连接,我们拿mysql举例。返回的便是MySqlConnection这个类\vendor\laravel\framework\src\Illuminate\Database\MySqlConnection.php

进入这个类文件,可以看见都是获取grammar相关的方法,这些方法暂时先不去看它。秘密在于它的父类Connection类。(\vendor\laravel\framework\src\Illuminate\Database\Connection.php)先看它的构造方法,从这个方法我们可以知道,所有模型对象也好,DB对象也好,底层都是通过pdo去连接执行的,另外呢,tablePrefix数据表前缀,以及数据库连接配置也是在这里进行加载的。useDefaultQueryGrammar这俩个方法就不去深究了。我们来看点有趣的。

    /**
* Create a new database connection instance.
*
* @param \PDO|\Closure $pdo
* @param string $database
* @param string $tablePrefix
* @param array $config
* @return void
*/
public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
{
$this->pdo = $pdo; // First we will setup the default properties. We keep track of the DB
// name we are connected to since it is needed when some reflective
// type commands are run such as checking whether a table exists.
$this->database = $database; $this->tablePrefix = $tablePrefix; $this->config = $config; // We need to initialize a query grammar and the query post processors
// which are both very important parts of the database abstractions
// so we initialize these to their default values while starting.
$this->useDefaultQueryGrammar(); $this->useDefaultPostProcessor();
}

有趣的是什么呢?就是这个table()方法了。我们都知道,在laravel中既可以使用模型的方式去进行数据库操作,也可以使用DB::table()的方式,而我们平时使用较多的table()方法的真身,就在这个类里了。这个table方法只做了一件事,就是从$this->query()这个对象中from一个表,然后返回的对象就可以通过链式调用的方式去做其他操作了。接下来看这个$this->query(),这个方法也只做了一件事,new一个QueryBuilder对象。

    /**
* Begin a fluent query against a database table.
*
* @param string $table
* @return \Illuminate\Database\Query\Builder
*/
public function table($table)
{
return $this->query()->from($table);
} /**
* Get a new query builder instance.
*
* @return \Illuminate\Database\Query\Builder
*/
public function query()
{
return new QueryBuilder(
$this, $this->getQueryGrammar(), $this->getPostProcessor()
);
}

而QueryBuilder这个对象就是我们平时使用的eloquent orm 的入口,我们平时使用的那些方便的数据库操作方法都是从这里进入。

通过一系列跳转,我们会发现,这个QueryBuilder的真身在

\vendor\laravel\framework\src\Illuminate\Database\Query\Builder.php这个类中,名为Builder类。

来到这个类文件中,稍微浏览一下,感觉发现了宝藏,里面那些where()、join()、get()、find()方法,不正是我们常用的各种方便的orm方法么?

来来回回绕了这么大一个圈终于找到,我们常用的函数是从何而来,但是现在还有一个疑问了。现在我们一路跟踪到的线索,都是从DB::table()这种方式跟踪而来,那么model的方式是怎么调用的呢?

让我们随便新建一个模型类,然后找到它的父类Model

vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php

浏览一番过后,我们发现了比较眼熟的fill()、all()、save()等方法,然后我们会发现这些方法中,大部分都有$this->newModelQuery();这么一句,我们根据这一线索一路跟踪,一路尾行,最终我们会发现new QueryBuilder这句代码又出现了。

    /**
* Get a new query builder for the model's table.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function newQuery()
{
return $this->registerGlobalScopes($this->newQueryWithoutScopes());
} /**
* Register the global scopes for this builder instance.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return \Illuminate\Database\Eloquent\Builder
*/
public function registerGlobalScopes($builder)
{
foreach ($this->getGlobalScopes() as $identifier => $scope) {
$builder->withGlobalScope($identifier, $scope);
} return $builder;
} /**
* Get a new query builder instance for the connection.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection(); return new QueryBuilder(
$connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
);
}

是的,再次跳转后,我们便又回到了\vendor\laravel\framework\src\Illuminate\Database\Query\Builder.php这个类中。

至于那些model里没写的方法为什么可以直接调用?你去model类里找一找看有没有__call这个魔术方法,看它里面写了些什么

    /**
* Handle dynamic method calls into the model.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
} return $this->newQuery()->$method(...$parameters);
}

到这里,数据库服务是怎么启动的,DB门面、model类为什么能直接执行orm方法,相信我们已经有清晰的认识了。至于orm是怎么转化成sql语句执行的,且听下回分解~

laravel5.5源码笔记(七、数据库初始化)的更多相关文章

  1. Tomcat8源码笔记(七)组件启动Server Service Engine Host启动

    一.Tomcat启动的入口 Tomcat初始化简单流程前面博客介绍了一遍,组件除了StandardHost都有博客,欢迎大家指文中错误.Tomcat启动类是Bootstrap,而启动容器启动入口位于 ...

  2. laravel5.5源码笔记(五、Pipeline管道模式)

    Pipeline管道模式,也有人叫它装饰模式.应该说管道是装饰模式的一个变种,虽然思想都是一样的,但这个是闭包的版本,实现方式与传统装饰模式也不太一样.在laravel的源码中算是一个比较核心的设计模 ...

  3. laravel5.5源码笔记(一、入口应用的初始化)

    laravel的项目入口文件index.php如下 define('LARAVEL_START', microtime(true)); require __DIR__.'/../vendor/auto ...

  4. laravel5.5源码笔记(八、Eloquent ORM)

    上一篇写到Eloquent ORM的基类Builder类,这次就来看一下这些方便的ORM方法是如何转换成sql语句运行的. 首先还是进入\vendor\laravel\framework\src\Il ...

  5. laravel5.5源码笔记(六、中间件)

    laravel中的中间件作为一个请求与响应的过滤器,主要分为两个功能. 1.在请求到达控制器层之前进行拦截与过滤,只有通过验证的请求才能到达controller层 2.或者是在controller中运 ...

  6. laravel5.5源码笔记(四、路由)

    今天这篇博文来探索一下laravel的路由.在第一篇讲laravel入口文件的博文里,我们就提到过laravel的路由是在application对象的初始化阶段,通过provider来加载的.这个路由 ...

  7. laravel5.5源码笔记(二、服务提供者provider)

    laravel里所谓的provider服务提供者,其实是对某一类功能进行整合,与做一些使用前的初始化引导工作.laravel里的服务提供者也分为,系统核心服务提供者.与一般系统服务提供者.例如上一篇博 ...

  8. laravel5.5源码笔记(三、门面类facade)

    上次说了provider,那么这次来说说facade 首先是启动的源头,从laravel的kernel类中的$bootstrappers 数组,我们可以看到它的一些系统引导方法,其中的Register ...

  9. Tomcat8源码笔记(四)Server和Service初始化

    上一章 简单说明下Tomcat各个组件: Server:服务器,Tomcat服务器,一个Tomcat只有一个Server组件; Service:业务层,是Server下最大的子容器,一个Server可 ...

随机推荐

  1. 【Python】Java程序员学习Python(四)— 内置方法和内置变量

    <假如爱有天意> 当天边那颗星出现,你可知我又开始想念,有多少爱恋只能遥遥相望,就像月光洒向海面,年少的我们曾以为,相爱的人就能到永远,当我们相信情到深处在一起,听不见风中的叹息,谁知道爱 ...

  2. vmware虚拟机挂载Windows磁盘的两种方法

    第一种 vmware虚拟机通过ntfs-3g挂接windows盘 1.共享windows盘虚拟机设置——>添加硬盘——>选择IDE——>使用物理磁盘——>选择本地盘(单分区)— ...

  3. zabbix系列之七——安装后配置二Userparameters

    1User parameters(用户自定义参数) 1.1配置 描述 详细 备注 简介 1执行zabbix中未预定义的agent check时使用 配置 1)    zabbix agent的配置文件 ...

  4. Oracle EBS AR 收款API收款方法标识无效

    1.确认是不是没有收款方法 methods那个表的问题2.查看收款方法那个LOV的问题3.界面录入 是否会有问题  碰到的问题是 收款日期比较早时 找不到对应的收款方法 银行账户需要重新设置

  5. vim和xshell配色

    xshell配色: http://www.hookr.cn/xshell-pei-se.html vim配色: 参考该文中的配置方法,包括设置256色等.http://www.cnblogs.com/ ...

  6. ActiveMq--消息队列

    ActiveMQ官网下载地址:http://activemq.apache.org/download.html ActiveMQ的目录: bin存放的是脚本文件 conf存放的是基本配置文件 data ...

  7. iOS设计模式 - 生成器

    iOS设计模式 - 生成器 原理图 说明 1. 将构建复杂对象的过程拆分成一个一个的模块,通过统一的指导者来指导对象的构建过程称之为生成器模式 2. 生成器模式适合用于构建组合的对象 源码 https ...

  8. SpringBoot整合Redis初实践

    Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理. 有时,为了提升整个网站的性能,在开发时会将经常访问的数据进行缓存,这样在调用这个数据接口时,可以提 ...

  9. ZT 类模板Stack的实现 by vector

    *//*第3章 类模板 与函数相似,类也可以被一种或多种类型参数化.容器类就是一个具有这种特性的典型例子,它通常被用于管理某种特定类型的元素.只要使用类模板,你就可以实现容器类,而不需要确定容器中元素 ...

  10. docker 部署django项目(nginx + uwsgi +mysql)

    最近在学习用docker部署Django项目,经过百折不挠的鼓捣,终于将项目部署成功,爬过好多坑,也发现很多技能需要提高.特此写下随笔与小伙伴们分享,希望能对大家有所启发. docker的理论我就不赘 ...