前言
laravel对操作数据库提供了原生sql、查询构建器和Eloquent ORM三种方式。今天我们来看看这三种方式的运行原理。
原生sql
原生sql和查询构建器都依赖静态依赖代理DB(也就是DatabaseManager)关于这个可以参考Facade,现在我们就以DB::select('select * from user where name = ?', ['test']),这个为例看看DB是如何运行的。在调用select方法时,DatabaseManager中没有就会调用魔术方法__call(),在__call中会先调用connection方法生成一个数据库连接对象然后调用数据库连接对象里的select。
生成数据库连接对象
生成数据库连接对象,先获取数据库的连接名和连接类型(read、write、null),并且从app/config/database中获取要连接的数据库配置预先生成一个连接数据库连接器闭包,利用数据库连接工厂生成一个数据库连接对象。
class DatabaseManager
{
public function __call($method, $parameters)
{
return $this->connection()->$method(...$parameters);
}
/**
* 返回一个数据库连接对象
**/
public function connection($name = null)
{
list($database, $type) = $this->parseConnectionName($name);
//$name = 'mysql';
$name = $name ?: $database;
//这里会把已经生成的连接保存在数组中,供下一次使用
if (! isset($this->connections[$name])) {
$this->connections[$name] = $this->configure(
$this->makeConnection($database), $type
);
}
return $this->connections[$name];
}
/**
* 获取数据库连接名和类型
**/
protected function parseConnectionName($name)
{
$name = $name ?: $this->getDefaultConnection();
//['mysql',null]
return Str::endsWith($name, ['::read', '::write'])
? explode('::', $name, 2) : [$name, null];
}
public function getDefaultConnection()
{
//获取config目录下database数组中的default的值,默认为mysql
return $this->app['config']['database.default'];
}
/**
* 利用数据库连接工厂创建数据库连接对象
**/
protected function makeConnection($name)
{
//获取了mysql连接的基本配置数组
$config = $this->configuration($name);
//这里的factory就是ConnectionFactory具体参看DatabaseServiceProvider中的register
return $this->factory->make($config, $name);
}
/**
* 获取数据库的配置信息
***/
protected function configuration($name)
{
//获取对应config目录中database数组中connections中键名为$name的数据库连接配置这里的$name默认为mysql
$name = $name ?: $this->getDefaultConnection();
$connections = $this->app['config']['database.connections'];
if (is_null($config = Arr::get($connections, $name))) {
throw new InvalidArgumentException("Database [{$name}] not configured.");
}
return $config;
}
}
class ConnectionFactory
{
public function make(array $config, $name = null)
{
//这里给config配置数组添加了name和prefix两个键
$config = $this->parseConfig($config, $name);
if (isset($config['read'])) {
return $this->createReadWriteConnection($config);
}
return $this->createSingleConnection($config);
}
protected function createSingleConnection(array $config)
{
//返回一个连接数据库的闭包
$pdo = $this->createPdoResolver($config);
创建一个数据库连接对象MySqlConnection
return $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);
}
protected function createPdoResolver(array $config)
{
//这里对于有多个host配置的会随机获取其中一个host去连接
return function () use ($config) {
return $this->createConnector($config)->connect($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);
case 'sqlite':
return new SQLiteConnection($connection, $database, $prefix, $config);
case 'sqlsrv':
return new SqlServerConnection($connection, $database, $prefix, $config);
}
throw new InvalidArgumentException("Unsupported driver [{$driver}]");
}
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']}]");
}
}
class MysqlConnector
{
public function connect(array $config)
{
//生成连接PDO时需要的dsn字符串 比如:'mysql:host=127.0.0.1;port=3306;dbname=test'
$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']}`;");
}
//设置字符集和排序
$this->configureEncoding($connection, $config);
//设置时区
$this->configureTimezone($connection, $config);
//设置数据库的model这个有机会的话想说说这个设置,平时不注意可能会在这里踩坑
$this->setModes($connection, $config);
return $connection;
}
/**
* 生成PDO连接对象
**/
public function createConnection($dsn, array $config, array $options)
{
list($username, $password) = [
$config['username'] ?? null, $config['password'] ?? null,
];
try {
return new PDO($dsn, $username, $password, $options);
} catch (Exception $e) {
return $this->tryAgainIfCausedByLostConnection(
$e, $dsn, $username, $password, $options
);
}
}
}
select方法
在connection中获取到数据库连接对象Connection之后,调用他的select方法,select方法中调用了run,run方法中先确保了pdo闭包存在(这个pdo闭包是在构造函数中传入的),然后调用闭包,这个闭包中完成了sql的执行,先通过getPdoForSelect方法调用pdo闭包,闭包中进行了PDO对象连接及一些基础配置的设置(具体参考数据库工厂中的createPdoResolver)并返回一个PDO对象,这里利用闭包做到了执行sql时才连接数据库。然后调用PDO的prepare方法,并在prepared中设置了PDOstatement的获取模式为FETCH_OBJ,绑定参数执行sql返回结果。
class Connection
{
public function select($query, $bindings = [], $useReadPdo = true)
{
//$query = 'select * from user where name = ?';
//$bindings = [1];
return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
if ($this->pretending()) {
return [];
}
//在prepared中设置了返回结果的模式为FETCH_OBJ
$statement = $this->prepared($this->getPdoForSelect($useReadPdo)
->prepare($query)); //生成PODStatement对象
$this->bindValues($statement, $this->prepareBindings($bindings)); //参数绑定
$statement->execute(); //利用PDOStatement执行sql
return $statement->fetchAll(); //获取sql的执行结果
});
}
/**
* 确保pdo的存在并且执行闭包
**/
protected function run($query, $bindings, Closure $callback)
{
//检查pdo连接是否存在
$this->reconnectIfMissingConnection();
$start = microtime(true);
try {
$result = $callback($query, $bindings);
} catch (QueryException $e) {
$result = $this->handleQueryException(
$e, $query, $bindings, $callback
);
}
$this->logQuery(
$query, $bindings, $this->getElapsedTime($start)
);
return $result;
}
}
查询构建器
上边我们了解了原生sql在laravel中的运行原理,我们在看看查询构建器是如何工作的,其实从字面意思猜他的作用大概就是构造sql语句。现在以DB::table('user')->where(['name' => 'test'])->get()这个为例。同原生sql运行一样,调用DatabasesManager中的__call生成一个数据库连接对象,调用数据库连接对象中的table方法。
DatabasesManager的table方法
这里的table比较简单,就是返回一个查询构建器并且设置他的属性from为'user'。
class Connection
{
public function table($table)
{
return $this->query()->from($table);
}
public function query()
{
//生成一个查询构建器
return new QueryBuilder(
$this, $this->getQueryGrammar(), $this->getPostProcessor()
);
}
}
class QueryBuilder
{
public function __construct(ConnectionInterface $connection,
Grammar $grammar = null,
Processor $processor = null)
{
$this->connection = $connection;
$this->grammar = $grammar ?: $connection->getQueryGrammar();
$this->processor = $processor ?: $connection->getPostProcessor();
}
public function from($table)
{
$this->from = $table;
return $this;
}
}
QueryBuilder中的where
where方法其实就是将我们传入的列名、操作符、值,存入数组中。供后续使用,当参数column = ['name'=>'test']这个时,会执行addArrayOfWheres方法,利用addArrayOfWheres中的闭包重新实例化一个查询构造器$query又一次调用where('name', '=', 'test'),$query->wheres = [ ['Basic', 'name', '=', 'test', 'and']],$query->bindings['where'] = [['test']],然后执行addNestedWhereQuery方法设置$this->wheres = [['Nested', $query, 'and']],$this->bindings['where'] = [['test']]
class QueryBuilder
{
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
// 第一次 $column = ['name'=>'test'];$operator = null; $value = null; $boolean = 'and'
// 第二次 $column = 'name', $operator = '=', $value='test', $boolean = 'and'
if (is_array($column)) {
return $this->addArrayOfWheres($column, $boolean);
}
//给$value, $operator赋值,对于省略的操作符赋值为‘=’
list($value, $operator) = $this->prepareValueAndOperator(
$value, $operator, func_num_args() === 2
);
if ($column instanceof Closure) {
return $this->whereNested($column, $boolean);
}
//对于给出的一些非法操作符,默认替换为‘=’
if ($this->invalidOperator($operator)) {
list($value, $operator) = [$operator, '='];
}
//传入的值可以为回调函数这样就当作一个子查询来操作
if ($value instanceof Closure) {
return $this->whereSub($column, $operator, $value, $boolean);
}
if (is_null($value)) {
return $this->whereNull($column, $boolean, $operator !== '=');
}
if (Str::contains($column, '->') && is_bool($value)) {
$value = new Expression($value ? 'true' : 'false');
}
$type = 'Basic';
//wheres[] = ['Basic', 'name', '=', 'test', 'and']
$this->wheres[] = compact(
'type', 'column', 'operator', 'value', 'boolean'
);
//将value添加到where的绑定数组中
if (! $value instanceof Expression) {
$this->addBinding($value, 'where');
}
return $this;
}
protected function addArrayOfWheres($column, $boolean, $method = 'where')
{
//$query = $this; $column = ['name'=>'test']; $method='where'; $boolean = 'and'
return $this->whereNested(function ($query) use ($column, $method, $boolean) {
foreach ($column as $key => $value) {
if (is_numeric($key) && is_array($value)) {
//递归再调用where
$query->{$method}(...array_values($value));
} else {
//这里又一次调用where方法$this->where('name','=','test','and')
$query->$method($key, '=', $value, $boolean);
}
}
}, $boolean);
}
}
public function whereNested(Closure $callback, $boolean = 'and')
{
//执行闭包
call_user_func($callback, $query = $this->forNestedWhere());
return $this->addNestedWhereQuery($query, $boolean);
}
public function forNestedWhere()
{
//重新实例化一个查询构建器对象
return new static($this->connection, $this->grammar, $this->processor)->from($this->from);
}
public function addNestedWhereQuery($query, $boolean = 'and')
{
//$query->wheres = [['Basic', 'name', '=', 'test', 'and']]
if (count($query->wheres)) {
$type = 'Nested';
//wheres[] = ['Nested', $query, 'and']
$this->wheres[] = compact('type', 'query', 'boolean');
//$query->bindings['where'] = [['test']]
//$this->bindings['where'] = [['test']]
$this->addBinding($query->getRawBindings()['where'], 'where');
}
return $this;
}
QueryBuilder中的get
在执行get时,会先通过Garmmer类(以后再具体来看这个类的实现)生成要执行的sql语句,作为数据库连接对象的select方法的参数执行sql,然后将返回结果通过Processor类处理之后使用Collection包裹起来返回。
class QueryBuilder
{
public function get($columns = ['*'])
{
return collect($this->onceWithColumns($columns, function () {
return $this->processor->processSelect($this, $this->runSelect());
}));
}
protected function onceWithColumns($columns, $callback)
{
$original = $this->columns;
if (is_null($original)) {
$this->columns = $columns;
}
$result = $callback();
$this->columns = $original;
return $result;
}
protected function runSelect()
{
//实质还是调用了数据库连接器中的select
return $this->connection->select(
$this->toSql(), $this->getBindings(), ! $this->useWritePdo
);
}
public function toSql()
{
//通过Grammar类生成sql语句
return $this->grammar->compileSelect($this);
}
public function getBindings()
{
//将绑定数组转换为一维数组
return Arr::flatten($this->bindings);
}
}
数据库相关几个类之间的关系
名字 | 作用 |
---|---|
DB | DatabaseManager的一个服务代理 |
DatabaseManager | 给外部提供的一个接口,外部调用数据库都走这个类 |
ConnectionFactory | DatabaseManager的一个属性,DatabaseManager创建数据库连接对象的工具 |
Connection | 数据库增删改查实现的地方 |
Connector | 生成PDO的地方 |
Builder | 连接Connection和Grammer,将Grammer生成的sql提供给Connection执行 |
Grammer | 生成能执行的sql语句 |
Processor | 处理Connection执行完sql的结果 |
Eloquent ORM
我们经常会写Model::find(1)或者(new Model)->find(1)但是model类中并找不到find方法,既然找不到又能运行一般都是魔术方法,接下来我们看看他是如何运行的。可以看出当调用一个不存在的静态方法时最终调用的都是__call方法。__call方法会先生成一个EloquentBuilder类,然后调用EloquentBuilder里的find方法。这篇先到这里吧,model的以后再补有点写不下去了。
class Model
{
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
return $this->newQuery()->$method(...$parameters);
}
public static function __callStatic($method, $parameters)
{
//当一个静态方法找不到时就当作非静态的方法调用
return (new static)->$method(...$parameters);
}
public function newQuery()
{
return $this->registerGlobalScopes($this->newQueryWithoutScopes());
}
public function newQueryWithoutScopes()
{
return $this->newModelQuery()
->with($this->with)
->withCount($this->withCount);
}
public function newModelQuery()
{
return $this->newEloquentBuilder(
$this->newBaseQueryBuilder()
)->setModel($this);
}
}
class EloquentBuilder
{
public function find($id, $columns = ['*'])
{
if (is_array($id) || $id instanceof Arrayable) {
return $this->findMany($id, $columns);
}
return $this->whereKey($id)->first($columns);
}
public function whereKey($id)
{
if (is_array($id) || $id instanceof Arrayable) {
$this->query->whereIn($this->model->getQualifiedKeyName(), $id);
return $this;
}
return $this->where($this->model->getQualifiedKeyName(), '=', $id);
}
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($column instanceof Closure) {
$column($query = $this->model->newModelQuery());
$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
} else {
$this->query->where(...func_get_args());
}
return $this;
}
}
start=>start: 接受回调
end=>end: 返回结果
cond=>condition: 检查输入的参数
cond1=>condition: 检查结算单编号
op1=>operation: 查找系统中结算单号下的发票信息
cond2=>condition: 检查系统中发票数量<=传入的checkInvoices长度
cond3=>condition: 对比系统中每一个发票号码、编号、价税合计是否和传入的一致
op2=>operation: 将该结算单下的所有回调记录置为无效,并插入新纪录
start->cond
cond(yes)->op1->cond2
cond2(yes)->cond3
cond3(yes)->op2->end
cond2(no)->end
cond(no)->end