Laravel 4:多租户应用程序,每个租户有自己的数据库和一个全局数据库。

27

目前我有一个使用CodeIgniter编写的托管多租户的应用程序。

但是我真的很喜欢Laravel 4,我想开始将应用程序迁移到Laravel。

这是目前的设置:

  • 每个租户都有自己的数据库。
  • 只有一组应用程序文件。
  • 当我们创建新的租户时,会创建一个新的数据库,并运行安装脚本,数据库将被填充一些初始信息。
  • 每个租户也拥有自己的子域名。这是我们可以检测使用哪个数据库的方式。
  • 有一个主数据库,保存租户信息、用户和其他一些通用表。
  • 当需要模式更新时,我们只需创建一个更新脚本,该脚本将为每个租户运行。这通过适用于Codeigniter的特殊编码CLI脚本完成。

在Codeigniter中,启动和终止新的数据库连接相对容易。

在Laravel中,我有以下问题/疑问:

  • 如何在运行时启动/结束数据库连接?
  • 我想使用迁移,但我想为每个租户运行它们。迁移目前仅在“主”数据库连接上运行,而且它只运行一次。
  • 种子数据也是如此。

这些是我主要的问题,我还有其他一些小问题,但可以解决。

希望有人能够为我提供一些指导。

5个回答

23

我只是试图猜测一下,所以请注意 :) 每当调用 DB 时使用的 DatabaseManager 类都有一个 extend 方法。这里是源代码链接。DB::connection() 方法应该返回 Illuminate\Database\Connection 的一个实例。从这个出发,我会创建一个新的用户连接,如下所示:

$user = Auth::user();
DB::extend($user->username, function() use ($user) {
   // $pdo = new PDO(); set this up how you see fit
    return new Illuminate\Database\Connection($pdo, $user->databaseName, $tablePrefix);
});

就我个人而言,我会为每个用户添加一个新方法 User::databaseConnection(),并在扩展DatabaseManager时调用它。

DB::extend($user->username, function() use ($user) {
    return $user->databaseConnection();
});

在您的应用程序中,您应该能够通过以下方式调用已注册用户的连接:

DB::connection(Auth::user()->username);

更新

根据您调用租户连接的频率和时间,您可能需要使用IOC容器。

App::bind('tenantDB', function()
{
     return DB::connection(Auth::user()->username);
});

App::make('tenantDB')->insert(...);

我忘记了迁移和填充数据。对于迁移,您可以设置文件路径。

php artisan migrate:make foo --path=app/migrations

如果您使用Config类来设置默认数据库或DB::setDefaultConnection($username),我会假设所有迁移和填充都将为当前连接完成。当该过程完成后,您可以切换回主数据库。

更新2

Laravel开发人员非常出色,我应该早点检查这个功能。您可以在任何已创建的数据库连接上执行迁移和填充。

artisan migrate --database='userConnectionName' 
artisan db:seed --database='userConnectionName'

看看Barry的回答,那可能比扩展DatabaseManager要简单得多。

如果您想查看这些命令的所有选项,请运行:

artisan help migrate
artisan help db:seed

这是一个相当不错的解决方案。你能为我澄清一件事吗? 每个租户的迁移会如何运行?就像我说的,只有一个应用程序文件集,并且租户没有以任何方式硬编码。所有租户都必须运行当前版本的应用程序,因此他们也必须相应地进行迁移。 - Victor Gutierrez
所以我认为我刚刚给你找到了一个更好的解决方案。迁移命令确实有一个数据库选项,因此 php artisan migrate --database='userConnectionName' 应该可以工作。对于种子数据填充,您可能仍然需要更改默认连接。我建议您有几个选择:(1)编写一个自定义命令来执行种子数据填充,或者(2)提出一个关于种子数据填充命令的 --database 选项的功能请求。 - Blessing
抱歉,刚才关于种子数据的评论撤回。使用 --database 选项也可以进行种子数据填充,如 artisan db:seed --database='userConnectionName'。这些 Laravel 开发者真是太牛了! - Blessing
我知道那个选项,但这意味着数据库连接必须硬编码在配置文件中。我找到的唯一简单的解决方案是创建一个新任务,循环遍历所有租户,并将默认连接更改为该租户的数据库。种子数据也是如此。 - Victor Gutierrez
我已经使用Barry的想法在多个数据库上实现了迁移和填充。我创建了一个名为“test”的连接,并运行了artisan:artisan migrate --seed --database=tenant。创建一个“tenant”连接,然后通过Config::set()更改选项似乎是实现您想要的最干净的方法。为了确保artisan正在使用特定的租户数据库,请添加代码以设置/验证连接选项到start/artisan.php文件中。 - Blessing
我对Redis不熟悉,我的问题是如果我选择这种方法,我应该为每个租户拥有一个集群还是为整个应用程序拥有一个默认集群? - Raja Khoury

6

您可以使用租户数据库凭据创建1个数据库,并在应用程序中动态设置它们:

$tenant = Tenant::where('username', '=', $username)->first();
Config::set('database.connections.tenant.username', $tenant->db_username);
Config::set('database.connections.tenant.password', $tenant->db_password);
Config::set('database.connections.tenant.database', $tenant->db_database);

这将需要在您的database.php文件中创建2个连接(例如app和tenant),并在您的模型中指定使用哪个数据库(1用于存储租户,1用于特定于租户的数据库)。
还需要创建一个路由/脚本来创建/更新表。不确定如何处理具有多个数据库的迁移。

在设置配置后,如何获取连接实例? - Abishek
2
你可以直接这样做:DB::connection('tenant') 或在模型中指定连接方式:public static $connection = 'tentant'; - Barryvdh

4
您可以使用以下语法在laravel中创建动态DB连接:
Config::set('database.connections.key', array(
    'driver'    => 'mysql',
    'host'      => 'localhost',
    'database'  => 'dbname',
    'username'  => 'dbuser',
    'password'  => 'dbpass',
    'charset'   => 'utf8',
    'collation' => 'utf8_unicode_ci',
    'prefix'    => '',
));

这样,DB::connection('key'); 就能够正常工作了。


0

我最近遇到了一个类似的问题,需要为几个模型使用不同的数据库。

我发现以下方法可以解决这个问题。

  1. 在app/config/database.php中添加一个新的连接MY_NEW_CONNECTION
  2. 对于新连接,将数据库名称设置为任何名称
  3. 在你的filters.php或routes.php或控制器的__construct方法中添加以下内容:

    $db = '获取你的数据库名称'; Config::set('database.connections.MY_NEW_CONNECTION.database',$db); DB::setDefaultConnection('MY_NEW_CONNECTION');


-3

1) 你可以在你的 database.php 配置文件中定义多个命名连接

'connections' => array(
    'tenant1' => array(
     ...
    ),
    'tenant2' => array(
     ...
    ),

然后你可以像这样选择要使用哪一个。

$something = DB::connection('tenant1')->select(...);

2) 这不是一个完整的解决方案,因为我认为它需要对核心进行一些黑客攻击,但您可以选择在哪个连接上运行迁移。也许您可以遍历您的租户列表并在所有租户上运行它。

Schema::connection('tenant1')->create('users', function($table)

3) 很遗憾,我认为目前还不支持多连接的种子。你可能需要自己编写种子功能。


4
有许多租户,手动将它们全部添加到 database.php 中并不理想。这就是为什么我询问如何动态创建连接的原因。 - Victor Gutierrez
我不确定是否可能。您可以使用来自主数据库的信息动态生成连接数组。 - Anand Capur
此外,您可以使用Config::set函数动态地向配置文件中添加/移除数据库。 - Anand Capur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接