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

时间:2015-07-08 00:36:27

标签: php mysql database laravel-5 multi-tenant

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

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

为实现这一目标,我创建了一个类Setup

  1. 创建组织
  2. 创建数据库
  3. 配置与该数据库的连接并连接到该数据库
  4. 运行适当的迁移。
  5. 所有这些都包含在构造函数中,因此在调用Setup::create时,所有这些都可以正常运行。

    大多数数据库配置和连接都来自tutorial

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

    1. 廷克
    2. 网络浏览器。
    3. 令我惊讶的是,结果在两种情况下都是不同的,并且就连接到另一个数据库而言从来没有想过。

      与修补程序的交互

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

      DB::connection()->getDatabaseName()
      

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

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

      与浏览器互动

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

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

      首先,当我仍在中央数据库时,会显示其名称,但是在调用Setup::create后,它会切换到子数据库 - 预期 - 但是,一次刷新后,我再次访问中央数据库 - 这是完全意外的 -

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

      附加

        

      在修补程序中进行测试,我已经到了我已经注释掉迁移代码的地步,并且离开了数据库的创建以及与它的连接。   令我惊讶的是,它没有连接到数据库。   所以我开始认为迁移代码与连接数据库有关,或者修补程序有不同的行为我完全进入。

      重要

      • 我遇到了提到使用QueryBuilders的解决方案的线程
      • 请不要提供这样的答案,因为我的目标是将数据库完全切换到我可以毫无问题地使用雄辩模型的事件。
        • 我的意思是,我希望能够在连接到数据库后使用Model::create而不是DB::connection()->....

      技术细节

      我在Ubuntu Machine上使用带有mysql-server的Laravel 5。

1 个答案:

答案 0 :(得分:2)

我偶然发现了这个question并得到了答案。

我创建了一个名为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或类似的任何错误,请务必查看我提供的问题链接,并正确使用use语句。

关于即将到来的答案

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

让我觉得必须有一种方法可以以“全局”的方式将数据库连接设置为子数据库,以实现整个功能,例如中间件或其他东西。

我很乐意看到答案,实现这样的事情。

更新

我想出了一个更简洁的方法来做到这一点。

我认为你和我在同一个地方,希望根据每个控制器有条件地更改数据库......比如,每个控制器都需要一个不同的数据库,只是为了争论。

我们将用来解决这个问题的是`Middlewares。

首先,解释我们将要做的事情。

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

  1. 转到命令行,输入:

    php artisan make:middleware SetDatabaseConnectionMiddleware

  2. 使用现成的样板创建中间件。

    或者,如果您喜欢它,请转到您的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];
      }
      
    2. 这将返回一个包含动作名称和控制器名称的数组,如果您想限制性地返回控制器的名称,请随意从代码中删除'action' => $action

      1. 在您的中间件内部,它将是这样的:
      2.     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.现在,您已经正确创建了中间件,让我们告诉您的应用程序在哪里找到它以及以什么名称。

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

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

        3. 这样我们就知道如何调用它。

          1. 最后,设置它。

            1. 转到Controller.php(所有控制器继承的抽象类)
            2. public function __construct() { $this->middleware('set_proper_database'); }

              这应该为你做。

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

              //资源:

              1。Controller And Action Name

              2。Middleware Documentation

              3。Further Middleware Documentation 备注我很欣赏有关我的样式和代码缩进的版本,因为我似乎很难在这里正确地设计我的代码,但是徒劳无功,我使用的缩进没有任何效果。