如何在 运行 时间连接到不同的数据库?

How do I connect to different databases at run time?

我正在制作一个多租户多数据库应用程序,它有一个中央数据库和许多子数据库。

该应用程序在中央数据库中创建一个组织实例,并为每个组织创建一个包含不同表的子数据库。

为了实现这一点,我制作了一个 class class Setup

  1. 创建组织
  2. 创建其数据库
  3. 配置与该数据库的连接并连接到它
  4. 运行正确的迁移到它。

所有内容都包含在构造函数中,因此在调用 Setup::create 时所有这些都可以正常运行。

大部分数据库配置和连接都受此启发tutorial

为了测试我的逻辑是否符合要求,我通过以下方式与我的应用程序交互:

  1. 修补匠
  2. Web 浏览器。

令我惊讶的是,这两种情况下的结果都不同,而且就连接到另一个数据库而言从来没有像我们想要的那样。

与 tinker 的互动

在创建调用我的 Setup::create 并且输出告诉我一切正常后,我尝试自己检查我现在使用的是哪个数据库使用:

DB::connection()->getDatabaseName()

它输出我们刚刚为组织创建并连接到的子数据库名称,这是合乎逻辑的并相应地进行。

但是,我尝试通过为它创建一个新配置然后使用我提供的 DB 方法连接到它来连接到另一个数据库,它不起作用,我仍然在子-我所在的数据库。


与浏览器交互 :

这一次,将我的 Setup::create 正确包装在我的控制器代码中,我尝试再次测试所有内容,我还在我的布局中制作了一行以输出当前数据库:

<?php echo DB::connection()->getDatabaseName() ?>

起初,当我还在中央数据库时,它的名字出现了,但是在调用Setup::create之后,它切换到子数据库-这是预期的- 但是,刷新一次后,我又进入了中央数据库 - 这完全出乎意料-

那么,这里发生了什么?以及如何在我希望的时候以我希望的方式连接到所有不同的数据库?

额外:

Testing in tinker, I have went to the point where I have commented out the migration code, and left the creation of the database and also the connection to it. To my suprise, it does not connect to the database. so I started thinking that the migration code has something to do with connecting to the database, or maybe tinker has different behaviors I completely ingore.

重要:

技术细节:

我在 Ubuntu 机器上使用 Laravel 5 和 mysql-server。

我偶然发现了这个 question,它有我的答案。

我做了一个 class 叫做 DatabaseConnection:

class DatabaseConnection extends Model
{

        static $instances=array();

        protected $database;

        protected $connection;

        public function __construct($options = null)
        {
            // Set the database
            $database = $options['database'];
            $this->database = $database;

            // Figure out the driver and get the default configuration for the driver
            $driver  = isset($options['driver']) ? $options['driver'] : Config::get("database.default");
            $default = Config::get("database.connections.$driver");

            // Loop through our default array and update options if we have non-defaults
            foreach($default as $item => $value)
            {
                $default[$item] = isset($options[$item]) ? $options[$item] : $default[$item];
            }

            $capsule = new Capsule;
            $capsule->addConnection($default);
            $capsule->setEventDispatcher(new Dispatcher(new Container));
            $capsule->setAsGlobal();
            $capsule->bootEloquent();

            // Create the connection
            $this->connection = $capsule->getConnection();

            DatabaseConnection::$instances[] = $capsule;
            return $this->connection;
        }
}

所以,每当我在一个操作子数据库表的控制器中时,我只是这样做:

public function RandomActionInMyController()
{
      $db_connection = new DatabaseConnection(['database' => 'name_of_db']);
       $someModel = new Model/Model::find()..// Basically anything
        return myreturnstuff;
}

额外奖金:

静态属性$instances在我的DatabaseConnection中的使用 归结为检索我最新的数据库连接以方便使用。

例如,如果我想检索它,它将被包装在一个函数中,例如

function CurrentOrLatestDbConnection()
{
    if( !empty(DatabaseConnection::$instances) )
    {
        return end(DatabaseConnection::$instances)->getConnection()->getDatabaseName();
    }
}

注释 :

如果您遇到 Unknown class 'Container'Capsule 之类的错误,请务必检查我提供的问题 link,然后使用 use语句正确。

关于即将到来的答案 :

在我看来,此数据库连接存在于控制器操作的括号内,因此当我继续执行另一个指定无连接的操作时,它会自动 return 到中央数据库。

这让我想到,必须有一种方法可以通过'global'方式将数据库连接到子数据库设置为整个功能,例如中间件或其他东西。

我很想看到一个答案,实现这样的事情。

更新 :

我想出了一个更简洁的方法。

我假设你和我一样,想根据每个控制器有条件地改变数据库...比如说,你的每个控制器都需要一个不同的数据库,只是为了争论。

我们将使用`中间件来解决这个问题。

首先,解释一下我们要做什么..

我们将检查控制器的名称(甚至操作),然后设置我们希望设置的正确数据库。

  1. 转到您的命令行,输入:

    php artisan make:middleware SetDatabaseConnectionMiddleware

创建带有现成样板的中间件。

或者,如果您喜欢困难的方式,请转到您的 app_name/app/Http/Middleware 并手动创建一个。

  1. 转到你的辅助方法文件(如果你已经有一个,如果没有,伙计做一个!)

     function getControllerAndActionName()
    {
    
    $action = app('request')->route()->getAction();
    
    $controller = class_basename($action['controller']);
    
    list($controller, $action) = explode('@', $controller);
    
    return ['action' => $action, 'controller' => $controller];
    }
    

这将 return 给你一个包含动作名称和控制器名称的数组,如果你想 return 只限制控制器的名称,请随时从中删除 'action' => $action代码。

  1. 在你的中间件内部,它看起来是这样的:

    namespace App\Http\Middleware;

    use Closure;
    use DatabaseConnection;

    class SetProperDatabase
    {
    /**
    * Handle an incoming request.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  \Closure  $next
    * @return mixed
    */
    public function handle($request, Closure $next)
    {
         $database_name = '';
         $controllerAndActionName = getControllerAndActionName();
         $controller_name = $controllerAndActionName['controller'];
         $action_name = $controllerAndActionName['action'];
         if($controller_name == 'my_controller_nameController')
         {

         $database_name = 'your_proper_database_name';
         }
         else
         {
          $database_name = 'other_db';
         }

         $database_connection = new DatabaseConnection(['database' => $database_name']);

          return $next($request);
    }
    }

4.Now,您已经正确创建了中间件,让我们告诉您的应用在哪里可以找到它以及使用什么名称。

  1. 转到您的 app_name/app/Http/Kernel.php
  2. 在您的 $routeMiddleware 变量中,添加此行

    'set_proper_database' => \App\Http\Middleware\SetProperDatabase::class,

这样我们就知道怎么调用了。

  1. 终于开始设置了。

    1. 转到您的Controller.php(所有控制器继承的摘要class)

    public function __construct() { $this->middleware('set_proper_database'); }

这应该可以为您完成。

如果您有任何其他问题,请随时发表评论。

// 资源:

1.

2.Middleware Documentation

3.Further Middleware Documentation 备注 : 我希望看到一些关于我的样式和代码缩进的版本,因为我似乎在这里努力正确设置我的代码样式但徒劳无功,我使用的缩进没有效果。