本文为 Diving Laravel 的译文。 在这篇文章中我们将会探索 Laravel 中的多数据库连接。

在大多数的程序中只需要对一个数据库的连接进行处理。但是,也有一部分的 Laravel 应用需要处理多个数据库的连接。虽然有一些第三方的 library 能够为我们处理这些问题,但是我们如果能够理解 Laravel 是怎样处理数据库的连接对我们会很有帮助,下面让我们一起研究一下吧。

与数据库建立一个连接

当你在 Laravel 中进行查询时, Illuminate\Database\DatabaseManager 负责数据库的连接配置。每个连接都有一个独特的名称,并且你可以选择一个默认的配置,如果没有提供连接配置的名称:

// 使用默认的配置进行连接
DB::table('users')->all();

// 使用 "tenant" 配置进行连接
DB::connection('tenant')->table('users')->all();

在 Laravel 一次请求的生命周期内,连接只会建立一次,之后就会复用这个连接。

PDO

在 PHP 中 PDO是一个标准的连接数据库的接口, Laravel 使用 PDO 进行各种各样的查询。 当然,你可以配置读写分离的 PDO 对象,关于配置的详细介绍可以查看 Laravel 官方文档

在大多数的多租户应用中(multi-tenancy apps)都有一个分开的数据表来存储所有的租户信息。简单来讲,假设我们有一个主连接和租户连接,具体如下:

'tenant' => [
  'driver' => 'mysql',
  'host' => env('DB_HOST', '127.0.0.1'),
  'port' => env('DB_PORT', '3306'),
  // ...
],

'system' => [
  'driver' => 'mysql',
  'host' => env('DB_HOST', '127.0.0.1'),
  'port' => env('DB_PORT', '3306'),
  // ...
],

system 连接一直都使用同一个连接,那么查询对应的租户很简单:

DB::connection('system')->table('tenants')->all();

当我们想要查询指定租户的数据时,这时就变得有趣了起来。因为每个租户的数据库连接设定都不太相同,因此,我们不能简单的将所有的租户数据库配置写到 config/database.php 中。相反,我们在程序执行中进行配置。

config(['database.connections.tenant.database' => 'tenant1']);

上面的代码将读取 tenant1 中的设置并进行配置,你可以根据同样的方法来修改数据库的 username,password 等相关信息。

当 DatabaseManager 想要建立 tenant 连接时,就会使用我们上面配置好的连接。 然而,如果租户的连接已经建立,那么对配置文件的更改不会生效,因为在连接第一次建立的时候 Laravel 会对其进行缓存而不会重新创建一个新的实例。

要解决这个问题,你需要确保在使用我们的连接之前清空之前的缓存

config(['database.connections.tenant.database' => 'tenant1']);

DB::purge('tenant');

DB::reconnect('tenant');

使用 purge()reconnect() 将会确保我们使用了上面的配置。

在哪里使用

上面的代码我们可以在下面的几个地方使用:

  1. HTTP 请求
  2. 命令行
  3. 队列任务

让我们新建一个 TenancyProvider 并且将其加入到 config/app.php 中确保其生效, 我们可以用下面的方法进行配置:

public function register()
{
    if($this->app->runningInConsole())
	 {
			return;
    }

    if($request->getHttpHost() == 'tenant1.app.com')
    {
			config(['database.connections.tenant.database' => 'tenant1']);
    
			DB::purge('tenant');
    
			DB::reconnect('tenant');
     }
}

通过上面的代码我们可以根据请求的域名来使用不同的数据库配置。至于队列任务,我们可以使用 tenant_id 来进行判断,代码如下:

$this->app['queue']->createPayloadUsing(function () {
      return Tenant::get() ? [
              'tenant_id' => Tenant::get()->id
             ] : [];
});

其中,Tenant::get() 里面是判断当前应当使用哪个配置的逻辑。现在每个 任务将会包含 telant_id 然后我们可以监听 JobProcessing 事件然后进行数据库配置

$this->app['events']->listen(\Illuminate\Queue\Events\JobProcessing::class, function($event){
    if (isset($event->job->payload()['tenant_id'])) {
        Tenant::set($event->job->payload()['tenant_id']);
    }
});