简单解析Lavarel的数据库ORM
前言
在《如何理解laravel服务加载流程》中分析了服务的执行流程,得知框架的所有扩展功能都由服务(Provider)提供。
那么需要先猜想,数据库操作也是通过服务产生的。
分析
查看配置文件有没有服务配置
config/app.php
<?php
return [
// ...
'providers' => [
// ...
// 很明显,确实是通过服务来提供数据库支持的
Illuminate\Database\DatabaseServiceProvider::class,
// ...
];
// ...
];
查看服务入口
src\Illuminate\Database\DatabaseServiceProvider.php
<?php
namespace Illuminate\Database;
class DatabaseServiceProvider extends ServiceProvider
{
// ...
public function boot()
{
// 向模型提供数据库连接解析器
// 就是new PDO的入口实例
Model::setConnectionResolver($this->app['db']);
// 向模型提供事件调度器
// 就是用来绑定和触发事件的实例
// 现在的框架通常都会有一个事件总线
// 在psr标准中事件总线实例需要绑定在容器中,并按需由容器提供
Model::setEventDispatcher($this->app['events']);
}
// 先注册服务再引导启动服务
public function register()
{
// 清除初始化状态
// 模型初始化放在后面再讲
Model::clearBootedModels();
// 注册模型所需的单例
// 就是一堆$this->app->singleton的操作
$this->registerConnectionServices();
// 注册fakephp的
// 用途不明
$this->registerEloquentFactory();
// 用途不明
$this->registerQueueableEntityResolver();
}
protected function registerConnectionServices()
{
// ...
$this->app->singleton('db', function ($app) {
// 连接管理器
return new DatabaseManager($app, $app['db.factory']);
});
// ...
}
// ...
}
Eloquent模型
src\Illuminate\Database\Eloquent\Model.php
注:下述没有说明的代码段都来自Model.php
构造函数初始化过程
<?php
namespace Illuminate\Database\Eloquent;
//.....
abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToString, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
//
public function __construct(array $attributes = [])
{
// 如果没引导启动就马上启动
$this->bootIfNotBooted();
// bootIfNotBooted会把需要初始化的内容写入一个静态队列
// 执行初始化队列
$this->initializeTraits();
$this->syncOriginal();
$this->fill($attributes);
}
// ...
// bootIfNotBooted最后会自行到这里
// 前面的执行都比较简单,所以可以直接看这个方法
protected static function bootTraits()
{
// 获取当前类名,如果当前类是子类就返回子类类名
// self::class是获取自己的类名
$class = static::class;
$booted = [];
static::$traitInitializers[$class] = [];
// 模型初始化过程会向上查找所有use过的trait
foreach (class_uses_recursive($class) as $trait) {
$method = 'boot'.class_basename($trait);
// 检索到trait中有名为bootTraitName的方法后会马上执行
if (method_exists($class, $method) && ! in_array($method, $booted)) {
forward_static_call([$class, $method]);
$booted[] = $method;
}
// 检索到trait中有名为initializeTraitName的方法会将方法名写入初始化队列
// 后面交给initializeTraits方法执行
if (method_exists($class, $method = 'initialize'.class_basename($trait))) {
static::$traitInitializers[$class][] = $method;
static::$traitInitializers[$class] = array_unique(
static::$traitInitializers[$class]
);
}
}
}
}
Eloquent中如何产生数据库连接
首先需要在文档中知悉如何使用模型
摘自learnku.com的一段代码:
use App\Models\Flight; foreach (Flight::all() as $flight) { echo $flight->name; }
分析all方法
// 从BaseQuery中执行查询 // 并返回所有集合 public static function all($columns = ['*']) { return static::query()->get( is_array($columns) ? $columns : func_get_args() ); } // 创建一个新的模型实例 // 并在这个实例中产生新的EloquentBuilder public static function query() { return (new static)->newQuery(); } // 获取一个新的Query实例 public function newQuery() { return $this->registerGlobalScopes($this->newQueryWithoutScopes()); } // 注册全局查询范围 public function registerGlobalScopes($builder) { foreach ($this->getGlobalScopes() as $identifier => $scope) { $builder->withGlobalScope($identifier, $scope); } return $builder; } // 获取没有查询范围的EloquentQuery实例 // 这里是为了产生一个全新的实例 public function newQueryWithoutScopes() { return $this->newModelQuery() ->with($this->with) ->withCount($this->withCount); } // 获取Eloquent/Builder实例 // 并绑定当前模型到builder实例 public function newModelQuery() { return $this->newEloquentBuilder( $this->newBaseQueryBuilder() )->setModel($this); } // 产生数据库连接 // 并获取基础Query实例 protected function newBaseQueryBuilder() { return $this->getConnection()->query(); } // 获取转述Query实例 public function newEloquentBuilder($query) { return new Builder($query); } // 获取从Illuminate\Database\DatabaseManager中获取Illuminate\Database\Connection实例 public function getConnection() { return static::resolveConnection($this->getConnectionName()); } // 产出连接 public static function resolveConnection($connection = null) { return static::$resolver->connection($connection); }
$resolver指向哪里:上文中的服务提供者里的
Model::setConnectionResolver
的入参是DatabaseManager
实例,所以$resolver
指向的是DatabaseManager
。
数据库管理器如何产出连接
src\Illuminate\Database\DatabaseManager.php
<?php namespace Illuminate\Database; class DatabaseManager implements ConnectionResolverInterface { public function connection($name = null) { [$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]; } protected function makeConnection($name) { $config = $this->configuration($name); // 上下文分析得知extensions这个属性默认都是空的 // 所以可以直接调到方法最后 if (isset($this->extensions[$name])) { return call_user_func($this->extensions[$name], $config, $name); } if (isset($this->extensions[$driver = $config['driver']])) { return call_user_func($this->extensions[$driver], $config, $name); } // 从连接工厂中创建连接 return $this->factory->make($config, $name); } }
src\Illuminate\Database\Connectors\ConnectionFactory.php
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 createSingleConnection(array $config) { // 创建一个pdo实例 $pdo = $this->createPdoResolver($config); // 创建一个对应的数据库连接 return $this->createConnection( $config['driver'], $pdo, $config['database'], $config['prefix'], $config ); } // createPdoResolver方法会执行到这里 protected function createPdoResolverWithoutHosts(array $config) { // 这里返回的是一个闭包 // 为了在实际执行的时候才会产生连接 return function () use ($config) { return $this->createConnector($config)->connect($config); }; } // 创建连接器 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); } // 上述的->connect()就是执行下面这几个实例里的connect方法。 // connect()方法会返回一个pdo连接实例,写的都比较简单就不分析了。 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']}]."); }
简单解析Lavarel的数据库ORM
http://blog.icy8.cn/posts/57267/