通过laravel数据库源码简单梳理 快速入门 |《Laravel 5.5 中文文档 5.5》| Laravel China 社区文中的知识点。解析最简单laravel数据库查询语句(如下)执行流程。
$results = DB::select('select * from users where id = :id', ['id' => 1]);
准备
DB
为应用程序的服务器容器中可用的数据库类提供的一个静态接口。在Facades |《Laravel 5.5 中文文档 5.5》| Laravel China 社区中列出了每个Facade类及其对应的底层类。
Facade | 类 | 服务容器绑定 |
---|---|---|
DB | Illuminate\Database\DatabaseManager | db |
因此,我也可以使用app('db')
的方式使用DB
facade。
而 db
与Illuminate\Database\DatabaseManager
的绑定通过DatabaseServiceProvider
来实现。
class DatabaseServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot(){...}
/**
* Register the service provider.
*
* @return void
*/
public function register(){...}
}
boot
方法在服务容器启动时调用。
public function boot()
{
Model::setConnectionResolver($this->app['db']);
Model::setEventDispatcher($this->app['events']);
}
boot
方法设置类Model
的静态属性$resolver
和$dispatcher
。
register
方法在服务容器启动前调用。
public function register()
{
Model::clearBootedModels();
$this->registerConnectionServices();
$this->registerEloquentFactory();
$this->registerQueueableEntityResolver();
}
registerConnectionServices
方法中注册主要的数据库绑定。db.factory
和ConnectionFactory
进行绑定、db
和DatabaseManager
进行绑定、db.connection
和$app['db']->connection()
绑定。
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();
});
}
调用
在DB
类上的调用会经过_callSatatic
方法作用在DatabaseManager
类对象中。
// \Illuminate\Support\Facades\Facade::__callStatic
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
// \Illuminate\Support\Facades\Facade::getFacadeRoot
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
// \Illuminate\Support\Facades\DB::getFacadeAccessor
protected static function getFacadeAccessor()
{
return 'db';
}
对DatabaseManager
对象的方法的调用经过_call
魔术方法作用在各数据库连接上。
// \Illuminate\Database\DatabaseManager::__call
public function __call($method, $parameters)
{
return $this->connection()->$method(...$parameters);
}
connection
将检查$connections
数组,获取数据库链接。如果没有连接将创建连接。
public function connection($name = null)
{
[$database, $type] = $this->parseConnectionName($name);
$name = $name ?: $database;
// If we haven't created this connection, we'll create it based on the config
// provided in the application. Once we've created the connections we will
// set the "fetch mode" for PDO which determines the query return types.
if (! isset($this->connections[$name])) {
$this->connections[$name] = $this->configure(
$this->makeConnection($database), $type
);
}
return $this->connections[$name];
}
链接的创建将由连接工厂类ConnectionFactory
实现。
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);
}
数据库连接
ConnectionFactory
的make
方法可以创建Connection
类对象。
// \Illuminate\Database\Connectors\ConnectionFactory::make
public function make(array $config, $name = null)
{
$config = $this->parseConfig($config, $name);
if (isset($config['read'])) {
return $this->createReadWriteConnection($config);
}
return $this->createSingleConnection($config);
}
make
方法通过配置项区分读写库。
// \Illuminate\Database\Connectors\ConnectionFactory::createReadWriteConnection
protected function createReadWriteConnection(array $config)
{
$connection = $this->createSingleConnection($this->getWriteConfig($config));
return $connection->setReadPdo($this->createReadPdo($config));
}
// \Illuminate\Database\Connectors\ConnectionFactory::createSingleConnection
protected function createSingleConnection(array $config)
{
$pdo = $this->createPdoResolver($config);
return $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);
}
createPdoResolver
方法使用使用Connector
类来配置PDO连接参数。createConnector
方法根据配置项创建不同数据库类型的链接器。
// \Illuminate\Database\Connectors\ConnectionFactory::createPdoResolver
protected function createPdoResolver(array $config)
{
return array_key_exists('host', $config)
? $this->createPdoResolverWithHosts($config)
: $this->createPdoResolverWithoutHosts($config);
}
// \Illuminate\Database\Connectors\ConnectionFactory::createPdoResolverWithoutHosts
protected function createPdoResolverWithoutHosts(array $config)
{
return function () use ($config) {
return $this->createConnector($config)->connect($config);
};
}
// \Illuminate\Database\Connectors\ConnectionFactory::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']}]");
}
链接器的connect
方法将根据配置项创建PDO连接。
// \Illuminate\Database\Connectors\MySqlConnector::connect
public function connect(array $config)
{
$dsn = $this->getDsn($config);
$options = $this->getOptions($config);
// We need to grab the PDO options that should be used while making the brand
// new connection instance. The PDO options control various aspects of the
// connection's behavior, and some might be specified by the developers.
$connection = $this->createConnection($dsn, $config, $options);
if (! empty($config['database'])) {
$connection->exec("use `{$config['database']}`;");
}
$this->configureEncoding($connection, $config);
// Next, we will check to see if a timezone has been specified in this config
// and if it has we will issue a statement to modify the timezone with the
// database. Setting this DB timezone is an optional configuration item.
$this->configureTimezone($connection, $config);
$this->setModes($connection, $config);
return $connection;
}
// \Illuminate\Database\Connectors\Connector::createConnection
public function createConnection($dsn, array $config, array $options)
{
[$username, $password] = [
$config['username'] ?? null, $config['password'] ?? null,
];
try {
return $this->createPdoConnection(
$dsn, $username, $password, $options
);
} catch (Exception $e) {
return $this->tryAgainIfCausedByLostConnection(
$e, $dsn, $username, $password, $options
);
}
}
// \Illuminate\Database\Connectors\Connector::createPdoConnection
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);
}
ConnectionFactory
的createConnection
方法根据配置项创建不同数据库类型的连接对象。
\Illuminate\Database\Connectors\ConnectionFactory::createConnection
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}]");
}
连接对象内部保存着对PDO连接的引用。
// \Illuminate\Database\Connection::__construct
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();
}
执行SQL
MySqlConnection
继承了Connection
,Connection
中定义了数据库增删改查的操作方法:
Connection
类属性中保存着pdo的引用,所有的的查询作用于pdo上。
// \Illuminate\Database\Connection::select
public function select($query, $bindings = [], $useReadPdo = true)
{
return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
if ($this->pretending()) {
return [];
}
// For select statements, we'll simply execute the query and return an array
// of the database result set. Each element in the array will be a single
// row from the database table, and will either be an array or objects.
$statement = $this->prepared($this->getPdoForSelect($useReadPdo)
->prepare($query));
$this->bindValues($statement, $this->prepareBindings($bindings));
$statement->execute();
return $statement->fetchAll();
});
}
// \Illuminate\Database\Connection::getPdoForSelect
protected function getPdoForSelect($useReadPdo = true)
{
return $useReadPdo ? $this->getReadPdo() : $this->getPdo();
}
select
方法的sql参数经过Grammar
编译,与sql进行绑定。
// \Illuminate\Database\Connection::prepareBindings
public function prepareBindings(array $bindings)
{
$grammar = $this->getQueryGrammar();
foreach ($bindings as $key => $value) {
// We need to transform all instances of DateTimeInterface into the actual
// date string. Each query grammar maintains its own date string format
// so we'll just ask the grammar for the format to get from the date.
if ($value instanceof DateTimeInterface) {
$bindings[$key] = $value->format($grammar->getDateFormat());
} elseif (is_bool($value)) {
$bindings[$key] = (int) $value;
}
}
return $bindings;
}
// \Illuminate\Database\Connection::bindValues
public function bindValues($statement, $bindings)
{
foreach ($bindings as $key => $value) {
$statement->bindValue(
is_string($key) ? $key : $key + 1, $value,
is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR
);
}
}
Grammar
类中定义了把where
,select
,update
等方法解析为sql语句的方法。
整体流程