Laravel核心解读--Database(一)基础介绍

在咱们学习和使用一个开发框架时,不管使用什么框架,如何链接数据库、对数据库进行增删改查都是学习的重点,在Laravel中咱们能够经过两种方式与数据库进行交互:php

  • DB, DB是与PHP底层的PDO直接进行交互的,经过查询构建器提供了一个方便的接口来建立及运行数据库查询语句。
  • Eloquent Model, Eloquent是创建在DB的查询构建器基础之上,对数据库进行了抽象的ORM,功能十分丰富让咱们能够避免写复杂的SQL语句,并用优雅的方式解决了数据表之间的关联关系。

上面说的这两个部分都包括在了Illuminate/Database包里面,除了做为Laravel的数据库层Illuminate/Database仍是一个PHP数据库工具集, 在任何项目里你均可以经过composer install illuminate/databse安装并使用它。mysql

Database服务注册和初始化

Database也是做为一种服务注册到服务容器里提供给Laravel应用使用的,它的服务提供器是Illuminate\Database\DatabaseServiceProviderlaravel

public function register()
{
    Model::clearBootedModels();

    $this->registerConnectionServices();

    $this->registerEloquentFactory();

    $this->registerQueueableEntityResolver();
}

第一步:Model::clearBootedModels()。在 Eloquent 服务启动以前为了保险起见须要清理掉已经booted的Model和全局查询做用域git

/**
 * Clear the list of booted models so they will be re-booted.
 *
 * @return void
 */
public static function clearBootedModels()
{
    static::$booted = [];

    static::$globalScopes = [];
}

第二步:注册ConnectionServicesgithub

protected function registerConnectionServices()
{
    $this->app->singleton('db.factory', function ($app) {
        return new ConnectionFactory($app);
    });

    $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.factory用来建立数据库链接实例,它将被注入到DatabaseManager中,在讲服务容器绑定时就说过了依赖注入的其中一个做用是延迟初始化对象,因此只要在用到数据库链接实例时它们才会被建立。
  • db DatabaseManger 做为Database面向外部的接口,DB这个Facade就是DatabaseManager的静态代理。应用中全部与Database有关的操做都是经过与这个接口交互来完成的。
  • db.connection 数据库链接实例,是与底层PDO接口进行交互的底层类,可用于数据库的查询、更新、建立等操做。

因此DatabaseManager做为接口与外部交互,在应用须要时经过ConnectionFactory建立了数据库链接实例,最后执行数据库的增删改查是由数据库链接实例来完成的。sql

第三步:注册Eloquent工厂数据库

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')
        );
    });
}

启动数据库服务闭包

public function boot()
{
    Model::setConnectionResolver($this->app['db']);

    Model::setEventDispatcher($this->app['events']);
}

数据库服务的启动主要设置 Eloquent Model 的链接分析器(connection resolver),让model可以用db服务链接数据库。还有就是设置数据库事件的分发器 dispatcher,用于监听数据库的事件。app

DatabaseManager

上面说了DatabaseManager是整个数据库服务的接口,咱们经过DB门面进行操做的时候实际上调用的就是DatabaseManager,它会经过数据库链接对象工厂(ConnectionFacotry)得到数据库链接对象(Connection),而后数据库链接对象会进行具体的CRUD操做。咱们先看一下DatabaseManager的构造函数:composer

public function __construct($app, ConnectionFactory $factory)
{
    $this->app = $app;
    $this->factory = $factory;
}

ConnectionFactory是在上面介绍的绑定db服务的时候传递给DatabaseManager的。好比咱们如今程序里执行了DB::table('users')->get(), 在DatabaseManager里并无table方法而后就会触发魔术方法__call

class DatabaseManager implements ConnectionResolverInterface
{
    protected $app;
    protected $factory;
    protected $connections = [];

    public function __call($method, $parameters)
    {
        return $this->connection()->$method(...$parameters);
    }
    
    public function connection($name = null)
    {
        list($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];
    }

}

connection方法会返回数据库链接对象,这个过程首先是解析链接名称parseConnectionName

protected function parseConnectionName($name)
{
    $name = $name ?: $this->getDefaultConnection();
    // 检查connection name 是否以::read, ::write结尾  好比'ucenter::read'
    return Str::endsWith($name, ['::read', '::write'])
                        ? explode('::', $name, 2) : [$name, null];
}

public function getDefaultConnection()
{
    // laravel默认是mysql,这里假定是经常使用的mysql链接
    return $this->app['config']['database.default'];
}

若是没有指定链接名称,Laravel会使用database配置里指定的默认链接名称, 接下来makeConnection方法会根据链接名称来建立链接实例:

protected function makeConnection($name)
{
    //假定$name是'mysql', 从config/database.php中获取'connections.mysql'的配置
     $config = $this->configuration($name);

    //首先去检查在应用启动时是否经过链接名注册了extension(闭包), 若是有则经过extension得到链接实例
    //好比在AppServiceProvider里经过DatabaseManager::extend('mysql', function () {...})
    if (isset($this->extensions[$name])) {
        return call_user_func($this->extensions[$name], $config, $name);
    }
    
    //检查是否为链接配置指定的driver注册了extension, 若是有则经过extension得到链接实例
    if (isset($this->extensions[$driver])) {
        return call_user_func($this->extensions[$driver], $config, $name);
    }
    
    // 经过ConnectionFactory数据库链接对象工厂获取Mysql的链接类    
    return $this->factory->make($config, $name);
}

ConnectionFactory

上面makeConnection方法使用了数据库链接对象工程来获取数据库链接对象,咱们来看一下工厂的make方法:

/**
 * 根据配置建立一个PDO链接
 *
 * @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);
}

protected function parseConfig(array $config, $name)
{
    return Arr::add(Arr::add($config, 'prefix', ''), 'name', $name);
}

在创建链接以前, 先经过parseConfig向配置参数中添加默认的 prefix 属性与 name 属性。

接下来根据配置文件中是否设置了读写分离。若是设置了读写分离,那么就会调用 createReadWriteConnection 函数,生成具备读、写两个功能的 connection;不然的话,就会调用 createSingleConnection 函数,生成普通的链接对象。

protected function createSingleConnection(array $config)
{
    $pdo = $this->createPdoResolver($config);

    return $this->createConnection(
        $config['driver'], $pdo, $config['database'], $config['prefix'], $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);
        ......                
    }

    throw new InvalidArgumentException("Unsupported driver [$driver]");
}

建立数据库链接的方法createConnection里参数$pdo是一个闭包:

function () use ($config) {
    return $this->createConnector($config)->connect($config);
};

这就引出了Database服务中另外一部份链接器Connector, Connection对象是依赖链接器链接上数据库的,因此在探究Connection以前咱们先来看看链接器Connector。

Connector

illuminate/database中链接器Connector是专门负责与PDO交互链接数据库的,咱们接着上面讲到的闭包参数$pdo往下看

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']}]");
}

这里咱们仍是以mysql举例看一下Mysql的链接器。

class MySqlConnector extends Connector implements ConnectorInterface 
{
    public function connect(array $config)
    {
        //生成PDO链接数据库时用的DSN链接字符串
        $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']}`;");
        }

        //为链接设置字符集和collation
        $this->configureEncoding($connection, $config);
        //设置time zone
        $this->configureTimezone($connection, $config);
        //为数据库会话设置sql mode
        $this->setModes($connection, $config);

      return $connection;
    }
}

这样就经过链接器与PHP底层的PDO交互链接上数据库了。

Connection

全部类型数据库的Connection类都是继承了Connection父类:

class MySqlConnection extends Connection
{
     ......
}

class Connection implements ConnectionInterface
{
    public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
    {
        $this->pdo = $pdo;

        $this->database = $database;

        $this->tablePrefix = $tablePrefix;

        $this->config = $config;

        $this->useDefaultQueryGrammar();

        $this->useDefaultPostProcessor();
    }
    ......   
    public function table($table)
    {
        return $this->query()->from($table);
    }
    ......
    public function query()
    {
        return new QueryBuilder(
            $this, $this->getQueryGrammar(), $this->getPostProcessor()
        );
    }
    ......
}

Connection就是DatabaseManager代理的数据库链接对象了, 因此最开始执行的代码DB::table('users')->get()通过咱们上面讲的历程,最终是由Connection来完成执行的,table方法返回了一个QueryBuilder对象,这个对象里定义里那些咱们常常用到的where, get, first等方法, 它会根据调用的方法生成对应的SQL语句,最后经过Connection对象执行来得到最终的结果。 详细内容咱们等到之后讲查询构建器的时候再看。

总结

说的东西有点多,咱们来总结下文章里讲到的Database的这几个组件的角色

名称 做用
DB DatabaseManager的静态代理
DatabaseManager Database面向外部的接口,应用中全部与Database有关的操做都是经过与这个接口交互来完成的。
ConnectionFactory 建立数据库链接对象的类工厂
Connection 数据库链接对象,执行数据库操做最后都是经过它与PHP底层的PDO交互来完成的
Connector 做为Connection的成员专门负责经过PDO链接数据库

咱们须要先理解了这几个组件的做用,在这些基础之上再去顺着看查询构建器的代码。

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

相关文章
相关标签/搜索