Laravel5.5源码详解--数据库的启动与连接过程
Laravel5.5源码详解 – 数据库的启动与连接过程
整个laravel的操作,⼀般情况下,数据库的处理会占掉很⼤⼀部分。所以对数据 库处理的理解,显得尤为重要。关于其源码解析,⽹上有⾮常多的⽂献,但流程⼀般都含糊其辞,读完来龙去脉甚为不解。所以,我⾃⼰做了⼀次流程分析,并记录下全过程。
Laravel对不同数据库连接的实例封装了对应连接的PDO类,为上层使⽤数据库连接实例提供了统⼀的接⼝。我这⾥源码分析,都以以mySql为实例进⾏讲解,过程⼤致如下,
DB::table('users') --> 拿到PDO --> 调⽤核⼼类Illuminate\Database\Connection
这⾥先局部后全局,分三部分讲解。
1. 数据库的连接与PHP-PDO的关系:解释laravel与数据库的接洽点;
2. 数据库查询构造流程:理解是如何最后实现调⽤核⼼类Connection::table()函数的;
3. 数据库启动⼤脉络分析:理解全流程,然后重点是如何拿到PDO的。
另外,例⼦中⽤到的laravel的门⾯(Facades)模式,原理⽐较简单,可以参考其官⽅⽂档。
说在前⾯的话
Laravel⽬前⽀持的有四类数据库,laravel中对应的名称分别为:mysql,pgsql,sqlite,sqlsrv,即MySQL、Postgres、SQLite和SQL Server;同时,laravel还⽀持⽤户算定的数据库和驱动程序。
当操作数据库的查询构造器时,可以使⽤类似
DB::table('users')->get();
DB::table('users')->select();
DB::table('users')->insert();
DB::update();
语法,其中
DB::table('users')
部分就是获取查询构造器,后⾯的“->get()”等调⽤查询构造的⽅法实现相应数据操作。后⾯我们会讲到(详见第三节),这些查询会通过DatabaseManager::connection()再调⽤各个$methods。
public function __call($method, $parameters)
{
return $this->connection()->$method(...$parameters);
}
查询构造器的建⽴过程分为两个阶段:⼀个是数据库连接封装阶段,另⼀个是查询构造器⽣成阶段。
数据库连接封装⼜可以分为四个步骤:
⼀、数据库管理器阶段,在DatabaseServiceProvider类中的registerConnectionServices()函数中创建ConnectionFactory实例;
Laravel⾸先通过服务提供者“Illuminate\Database\DatabaseServiceProvider”注册了数据库管理服务(“DB”服务)和数据库连接⼯⼚服务(“db.factory”服务),通过上述服务获取数据库管理DatabaseM
anager类和数据库连接⼯⼚实例ConnectionFactory类的实例,其中数据库连接⼯⼚实例作为数据库管理器实例的⼀个属性,在DatabaseServiceProvider类中的registerConnectionServices()函数中创建ConnectionFactory实例。
⼆、数据库连接⼯⼚阶段,这⼀阶段主要是为连接数据库作配置准备,并⽣成连接器MySqlConnector;为了对上层提供统⼀的接
⼝,Laravel在底层根据不同的配置调⽤了不同的数据库驱动扩展,框架上使⽤了简单⼯⼚设计模式,⽤来根据配置⽂件获取不同的数据库连接实例。
三、数据库连接器阶段,连接器MySqlConnector会创建连接,并调⽤其⼦函数::createConnection() 和 ::createPdoConnection();Laravel针对不同的数据库有不同的实现,主要包括连接DSN名称及配置等。Laravel框架⽤四个类分别封装了默认⽀持的四个数据库连接的过程,通过connect()⽅法提供统⼀的接⼝。
四、数据库连接创建阶段,在这个阶段MySqlConnector的⽗类Connector会⽣成PDO实例,并完成连接。本质上,不同数据库连接的实例就是封装了对应连接的PDO类实例、请求语法类实例、和结果处理类实例,从⽽为上层使⽤数据库连接实例提供统⼀的接⼝。
第⼀节,数据库的连接与PHP-PDO的关系
⾸先,我们要知道,数据最终是在类Illuminate\Database\Connectors\Connector.php中完成链接的。我们先分析⼀下其源码:
class Connector
{
use DetectsLostConnections;
//下⾯是连接时默认⽤到的参数,当然你可以在创建联接时更改
protected $options = [
PDO::ATTR_CASE => PDO::CASE_NATURAL, // 保留数据库驱动返回的列名,不强制列名为指定的⼤⼩写
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 抛出 exceptions 异常
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, // 不转换 NULL 和空字符串
PDO::ATTR_STRINGIFY_FETCHES => false, // 提取的时候将数值转换为字符串? => 不转换
PDO::ATTR_EMULATE_PREPARES => false, // 禁⽤预处理语句的模拟
];
// 尝试建⽴⼀个连接
public function createConnection($dsn, array $config, array $options)
{
// 先拿到连接数据库所需要的⽤户名和密码,这个⼀般在.env中设置,你可以看到有以下3项,
// DB_DATABASE=laraveldb DB_USERNAME=username DB_PASSWORD=password
list($username, $password) = [
$config['username'] ?? null, $config['password'] ?? null,
];
// 尝试调⽤实际建⽴连接的createPdoConnection函数,注意上⾯的$options已经作为设置参数传⼊
try {
return $this->createPdoConnection(
$dsn, $username, $password, $options
);
} catch (Exception $e) {
return $this->tryAgainIfCausedByLostConnection(
$e, $dsn, $username, $password, $options
);
}
}
// 实际建⽴数据库连接的函数
protected function createPdoConnection($dsn, $username, $password, $options)
{
if (class_exists(PDOConnection::class) && ! $this->isPersistentConnection($options)) {
return new PDOConnection($dsn, $username, $password, $options);
}
return new PDO($dsn, $username, $password, $options);
}
⼤致上,createPdoConnection会检查有没有PDOConnection这个类,实际上在laravel提供的默认源码中这个类是不存在的,你可以检查⼀下你的composer.json⽂件。如果想安装使⽤doctrine,可以参考以下官⽹
⾔归正传,createPdoConnection不到PDOConnection这个类,就会调⽤后⾯那句
return new PDO($dsn, $username, $password, $options);
其中$dsn就是我们在.env中设置的数据库地址
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
如果打印出来,就是⼀段字符串,如下,
'mysql:host=127.0.0.1;:port=3306;dbname=laraveldb'
这⾥值得⼀提的是PDO,PDO是个什么东西?PDO是PHP提供的数据对象扩展(PHP Data Object),它为PHP访问数据库提供了⼀套轻量级的接⼝,从PHP5.1版以后开始提供。你可以参考官⽅⽹站,
因此不难明⽩,所谓的laravel连接数据库,只不过是调⽤了PHP中的PDO(或者说该类)的API函数,并进⾏⼀系列的操作的过程。同样,Qureybuilder的相关API,也只不过是PDO的⼀层封装外⾐!
这⾥需要进⼀步说明的是,这个PDO创建之后,是直接返回给变量$connection的。以mySql为例,
namespace Illuminate\Database\Connectors;
use PDO;
class MySqlConnector extends Connector implements ConnectorInterface
{
public function connect(array $config)
{
$dsn = $this->getDsn($config);
$options = $this->getOptions($config);
$connection = $this->createConnection($dsn, $config, $options); // 在这⾥创建connection
if (! empty($config['database'])) {
$connection->exec("use `{$config['database']}`;");
}
$this->configureEncoding($connection, $config);
$this->configureTimezone($connection, $config);
$this->setModes($connection, $config);
return $connection;
}
...
}
这个类MySqlConnector是Illuminate\Database\Connectors\Connector的⼦类。
第⼆节 laravel中数据库查询构造流程
当 connection 对象构建初始化完成后,我们可以⽤ DB 来进⾏数据库的 增删改查(CRUD,即( Creat
e、Retrieve、Update、Delete)等操作。laravel的查询构造器让我们避免使⽤原⽣的sql语句,⽽是⽤⼀种语法上更容易理解的⽅式操作(Laravel官⽅称这样可以避免漏洞),例如
DB::table('table')->select('*')->where('user_id', 1);
第⼀,查询构造的这个过程是如何实现的呢?
当然,这种看似静态调⽤的⽅法,其实是laravel⾥的门⾯模式,实际上调⽤的并不是静态⽅法。这个简单的模式只有⼏⾏代码,
<?php
namespace Illuminate\Support\Facades;
class DB extends Facade
{
protected static function getFacadeAccessor()
{
return'db';
}
}
其原理只不过是通过调⽤其⽗类 Illuminate\Support\Facades\Facade中的PHP的魔术⽅法 __callStatic(),将请求转到了相应的⽅法上。这⾥的’db’,定义在
Illuminate\Foundation\Application.php
的 registerCoreContainerAliases()⾥⾯,如下
'db' => [\Illuminate\Database\DatabaseManager::class],
所以,在发出指令的时候,
DB::table('table')->select('*')->where('user_id', 1);
laravel会通过Facade,到DatabaseManager⾥⾯的table()函数,本质上是这个魔术函数
public function __call($method, $parameters)
{
return $this->connection()->$method(...$parameters);
}
php的工作流程这⾥的$this->connection()实质上是mySqlConnection对象,这个对象的⽗类正是Illuminate\Database\Connection,于
是,DatabaseManager顺藤摸⽠,到了mySqlConnection,并调⽤了其⽗类Connection中的table⽅法。
public function table($table)
{
// ⽤其QueryBuilder进⾏查询
return $this->query()->from($table);
}
public function query()
{
return new QueryBuilder(
$this, $this->getQueryGrammar(), $this->getPostProcessor()
);
}
第⼆,Connection核⼼类业务
要知道,查询构造⼯作是在Illuminate\Database\Connection中完成的,这个类是我们要了解的核⼼,其构造函数如下,
public function__construct($pdo, $database = '', $tablePrefix = '', array $config = [])
{
$this->pdo = $pdo; // $pdo是通过MySqlConnection--MySqlConnector拿到的,参考第三节
$this->database = $database;
$this->tablePrefix = $tablePrefix;
$this->config = $config;
$this->useDefaultQueryGrammar(); // Grammar SQL语法编译器实例
$this->useDefaultPostProcessor(); // Processor SQL结果处理器实例
}
可见,除了$pdo,这⾥还在MySqlConnection构造函数中通过setter注⼊了
\Illuminate\Database\Query\Grammars\Grammar
\Illuminate\Database\Query\Processors\Processor
这⾥有三样东西要关注,PDO,Grammar和Processor。
不过,这些具体内容在⽹上已经写得⽐较详细,我这⾥不再重复,可以参考
第三节数据库启动⼤脉络分析
我这⾥类(对象)的调⽤关系⽤===>表⽰,==>表⽰对象内部的函数调⽤,::表⽰属于该类的⼦函数,整个⼤脉络如下:
DB::table('users')->get();
DB::table('users')->select();
DB::table('users')->insert();
DB::update();
===>
DatabaseManager::connection() ==> ::makeConnection()
===>
ConnectionFactory::make() ==> ::createSingleConnection() ==> ::pdoResolver() ==> ::createPdoResolverWithHosts() ==> ::createConnector() ===>
MySqlConnector::Connect()
===>
Connector::createConnection() ==> ::createPdoConnection()
===> 拿到MySqlConnection,并回到DatabaseManager,
DatabaseManager::__call() ==> $method = '$table'
===>
MySqlConnection::table()
===> 实际调⽤MySqlConnection⽗类的函数
Connection::table()
下⾯对源码进⾏详细剖析。讲过的部分不再重复,重点是理解如何拿到$pdo。
在Illuminate\Database\DatabaseManager中,connection是这样定义的,
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论