简体   繁体   English

有没有另一种方法可以在 Eloquent 模型上“设置连接”?

[英]Is there another way to “setConnection” on an Eloquent Model?

I am currently handling a "multi db on the fly swap connections" sort of project.我目前正在处理“多数据库动态交换连接”类型的项目。

So what I end up doing is the following:所以我最终做的是以下内容:

$connectionName = uniqid();
\Config::set('database.connections.' . $connectionName, [/** db options **/]);
\Artisan::call('migrate', ['--database' => $connectionName]);

or或者

$connectionName = uniqid();           
\Config::set('database.connections.' . $connectionName,[/** db options **/]);

$user = new User();
$user->setConnection($connectionName);
$user->first_name = 'Daisy';
$user->last_name = 'Demo';
$user->is_not_being_ignored_by_santa_this_year = 0;
$user->email = //and so so on
$user->save();

For the Artisan call I sort of understand why Laravel needs to refer to a connection in a string, saved in a config array.对于 Artisan 调用,我有点理解为什么 Laravel 需要引用保存在配置数组中的字符串中的连接。

However on the Eloquent Model itself I find it somehow cumbersome to have to write my DB connection into a config array.然而,在 Eloquent 模型本身上,我发现将我的数据库连接写入配置数组有点麻烦。 So it can be picked up by the "Singleton approach" \\Config::get().. in the Model.因此,它可以通过模型中的“单例方法”\\Config::get().. 来获取。

Is there something more elegant, where I can inject a configuration directly without having to write it into some super global ?有没有更优雅的东西,我可以直接注入配置而不必将其写入一些超级全局?

Or am I missing something ?或者我错过了什么?

You would probably be better off creating a configuration array for each connection then you could switch between connections pretty easily by specifying which connection to use.您可能最好为每个连接创建一个配置数组,然后您可以通过指定要使用的连接轻松地在连接之间切换。

If you need to use multiple connections on the same model you can use the on method :如果您需要在同一模型上使用多个连接,您可以使用on方法

so it would be something like User::on('dbconnection2')->find(1)所以它会像User::on('dbconnection2')->find(1)

If you just want to use different connections for different models, you can set the protected $connection property on the model:如果只想为不同的模型使用不同的连接,可以在模型上设置 protected $connection属性:

class User extends Model
{
    protected $connection = 'dbconnection2';
}

Hope that helps.希望有帮助。

You could create a factory for your models and pass it the connection on bootstrapping your app:您可以为您的模型创建一个工厂,并在引导您的应用程序时将连接传递给它:

<?php

class ModelFactory
{
    private $db;

    public function __construct($dbConnection)
    {
        $this->db = $dbConnection;
    }

    public function createNewModel($class)
    {
        $object = new $class();
        $object->setConnection($this->db);
        return $object;
    }
}

Then in your code:然后在你的代码中:

$user = $factory->createModel(User::class);

Something like this!像这样的东西! Good luck!祝你好运! :-) :-)

I build a multitenant laravel app and was surprised that there is no out-of-the-box way of doing that.我构建了一个多租户 laravel 应用程序,并惊讶于没有开箱即用的方法来做到这一点。

I have one app available via different subdomains and the subdomain should be the key to different configs, like the database connection.我有一个通过不同子域可用的应用程序,子域应该是不同配置的关键,比如数据库连接。

You can easily adapt that to whatever criteria you need instead of the subdomain.您可以轻松地将其调整为您需要的任何标准,而不是子域。

Via dynamically Config::set()通过动态 Config::set()

So my first attempt was to always use the "default" connection and to create a middleware that dynamically calls Config::set("database.connection.default.host", "1.3.5.7");所以我的第一次尝试是始终使用“默认”连接并创建一个动态调用Config::set("database.connection.default.host", "1.3.5.7");的中间件Config::set("database.connection.default.host", "1.3.5.7"); and so on for all the other settings.等等所有其他设置。

But there are some downsides.但也有一些缺点。 For example it was pretty slow because I read all the values from the database and later on from redis.例如,它非常慢,因为我从数据库读取所有值,然后从 redis 读取。 But the much bigger problem was to set the connection to the redis cache for example, because the redis connection is already established before the middleware is called to override the config settings.但是更大的问题是例如设置到 redis 缓存的连接,因为在调用中间件覆盖配置设置之前已经建立了 redis 连接。

Via own config files通过自己的配置文件

So my second and current approach is to make it all via config files.所以我的第二个也是当前的方法是通过配置文件来完成这一切。 So I created the following file structure:所以我创建了以下文件结构:

  • config/_myapp.php配置/_myapp.php
  • config/subdomain1/_myapp.php配置/子域1/_myapp.php

Note: The underscore is important because the files are read in alphabetic order and our new file has to be read at first to use it in other config files.注意:下划线很重要,因为文件是按字母顺序读取的,我们的新文件必须首先读取才能在其他配置文件中使用它。

The goal is to use config('_myapp.DB_HOST') to retrieve the DB_HOST value from config/subdomain1/_myapp.php and use that as value in config/database.php.目标是使用config('_myapp.DB_HOST')从 config/subdomain1/_myapp.php 检索 DB_HOST 值并将其用作 config/database.php 中的值。 So config/_myapp.php only returns the content of _myapp.php for the specific subdomain.所以 config/_myapp.php 只返回特定子域的 _myapp.php 的内容。

in config/_myapp.php: <?php在 config/_myapp.php: <?php

// get the current subdomain from $_SERVER['HTTP_HOST']
$subdomain = \App\Helper::getSubDomain();

define('SUBDOMAIN', $subdomain);

if(!empty($subdomain) && file_exists($file = __DIR__."/$subdomain/".basename(__FILE__)))
    return require($file);

return [

];

and in config/_myapp.php:并在 config/_myapp.php 中:

return [
    ...
    'DB_HOST' => '1.3.5.7',
    ...
];

Use this for example in config/database.php or whereever you need domain specific config:例如在 config/database.php 或任何需要域特定配置的地方使用它:

...
   'host' => config('_myapp.DB_HOST'),
...

Please ask if something doesn't work, took me quite some time to figure that stuff out.请询问是否有问题,我花了很长时间才弄明白。

Its all depends on how you handling your multiple connections.I have worked on similar requirement project.这完全取决于您如何处理多个连接。我曾从事过类似的需求项目。

  • we have master slave/tenant database connection.我们有主从/租户数据库连接。 with configuration https://gist.github.com/safoorsafdar/c6c623f9ec7b440f563d76995faf7aec#file-database-php与配置https://gist.github.com/safoorsafdar/c6c623f9ec7b440f563d76995faf7aec#file-database-php
  • tenant database/migration/seeds create on the fly with create new account in the system租户数据库/迁移/种子动态创建,并在系统中创建新帐户
  • can soft delete database from the root control.可以从根控件软删除数据库。
  • on the user login, we check if its master, connect to master connection otherwise get tenant connection, store information in the session for web access.在用户登录时,我们检查其是否为 master,连接到 master 连接,否则获取租户连接,将信息存储在 session 中以供 web 访问。 check Tenant Setup after login . Tenant Setup after login检查Tenant Setup after login
  • Database connection related operation central class.数据库连接相关的操作中心类。 View DatabaseConnection .查看DatabaseConnection
  • center tenant session handler class to help with resolve tenant id from the session.中心租户会话处理程序类,以帮助解决会话中的租户 ID。 View TenantContextSession查看TenantContextSession
  • we have separate models with abstract class for tenant and master to get set the model connection.我们有单独的模型和抽象类供租户和主人设置模型连接。 Check查看
  • You might also need to handle migration in your system, have look into this https://gist.github.com/safoorsafdar/cc9252a2f14301c3da942ca7bec7d66e您可能还需要处理系统中的迁移,请查看此https://gist.github.com/safoorsafdar/cc9252a2f14301c3da942ca7bec7d66e

Tenant Model Abstract Class租户模型抽象类

<?php

namespace App\Models\Abstracts;

use App\Models\Abstracts\AbstractBaseModel;
use App\Models\Picklist\PicklistValue;

class TenantAbstractBaseModel extends AbstractBaseModel
{
    function __construct(array $attributes = array())
    {
        parent::__construct($attributes);
        if ( ! is_null(app('tenant.context')->getConnectionName())) {
            $this->setConnection(app('tenant.context')->getConnectionName());
        }

        //todo; should be dynamic
        if (is_null(app('tenant.context')->getConnectionName())
            && app()->runningInConsole()
        ) {
            //todo; need to resolve database connection through terminal and application.
            //dd(config('tenant.tenant_connection'));
            //$this->setConnection(config('tenant.tenant_connection'));
        }

    }
}

Tenant Setup after login登录后的租户设置

$connection = config('tenant.tenant_connection');
                //config()->set('database.default', config('tenant.tenant_connection'));
            app('tenant.context')->setConnectionName($connection);
            app('tenant.context')->setTenantId($company_id);

             //$database = config('database.connections.' . $connection . '.database') . $company_id;
             $company_system_name
                    = $this->auth->user()->company->system_name;
             config()->set('database.connections.'.$connection.'.database',
                    $company_system_name);
             //config()->set('database.connections.' . $connection . '.database', $database);
           config()->set('database.default', $connection);

DatabaseConnection数据库连接

<?php

namespace App\Tenancy\Tenant;

use Config;
use DB;
use App\Tenancy\Exceptions\TenantDatabaseException;
use App\Tenancy\Models\Tenant;

/**
 * Class DatabaseConnection
 *
 * Helps with tenant database connections
 */
class DatabaseConnection
{
    /**
     * See the multi-tenant configuration file. Configuration set
     * to use separate databases.
     */
    const TENANT_MODE_SEPARATE_DATABASE = 'database';

    /**
     * See the multi-tenant configuration file. Configuration set
     * to use prefixed table in same database.
     */
    const TENANT_MODE_TABLE_PREFIX = 'prefix';
    /**
     * Current active global tenant connection.
     *
     * @var string
     */
    protected static $current;
    /**
     * @var string
     */
    public $name;
    /**
     * @var Tenant
     */
    protected $tenant;
    /**
     * @var \Illuminate\Database\Connection
     */
    protected $connection;

    public function __construct(Tenant $tenant)
    {
        $this->tenant = $tenant;

        $this->name = "tenant.{$this->tenant->hash_id}";

        $this->setup();
    }

    /**
     * Sets the tenant database connection.
     */
    public function setup()
    {
        Config::set("database.connections.{$this->name}", $this->config());
    }

    /**
     * Generic configuration for tenant.
     *
     * @return array
     */
    protected function config()
    {
        $clone             = Config::get(sprintf('database.connections.%s',
            static::tenantConnectionName()));
        $clone['database'] = $this->tenant->system_name;

        return $clone;
    }

    /**
     * Central getter for system connection name.
     *
     * @return string
     */
    public static function systemConnectionName()
    {
        return Config::get('tenant.master_connection', 'mysql');
    }

    /**
     * Checks whether current connection is set as global tenant connection.
     *
     * @return bool
     */
    public function isCurrent()
    {
        return $this->name === static::getCurrent();
    }

    /**
     * Loads the currently set global tenant connection name.
     *
     * @return string
     */
    public static function getCurrent()
    {
        return static::$current;
    }

    /**
     * Sets current global tenant connection.
     */
    public function setCurrent()
    {
        static::$current = $this->name;
        Config::set(sprintf('database.connections.%s',
            static::tenantConnectionName()), $this->config());

        DB::purge(static::tenantConnectionName());
    }

    /**
     * Central getter for tenant connection name.
     *
     * @return string
     */
    public static function tenantConnectionName()
    {
        return Config::get('tenant.tenant_connection', 'tenant_mysql');
    }

    /**
     * Loads connection for this database.
     *
     * @return \Illuminate\Database\Connection
     */
    public function get()
    {
        if (is_null($this->connection)) {
            $this->setup();
            $this->connection = DB::connection($this->name);
        }

        return $this->connection;
    }

    /**
     * @return bool
     */
    public function create()
    {
        $clone = $this->config();

        return DB::connection(static::systemConnectionName())
            ->transaction(function () use ($clone) {
                if ( ! DB::connection(static::systemConnectionName())
                    ->statement("create database if not exists `{$clone['database']}`")
                ) {
                    throw new TenantDatabaseException("Could not create database {$clone['database']}");
                }
                if ( ! DB::connection(static::systemConnectionName())
                    ->statement("grant all on `{$clone['database']}`.* to `{$clone['username']}`@'{$clone['host']}' identified by '{$clone['password']}'")
                ) {
                    throw new TenantDatabaseException("Could not create or grant privileges to user {$clone['username']} for {$clone['database']}");
                }

                return true;
            });
    }

    /**
     * @throws \Exception
     *
     * @return bool
     */
    public function delete()
    {
        $clone = $this->config();

        return DB::connection(static::systemConnectionName())
            ->transaction(function () use ($clone) {
                if ( ! DB::connection(static::systemConnectionName())
                    ->statement("revoke all on `{$clone['database']}`.* from `{$clone['username']}`@'{$clone['host']}'")
                ) {
                    throw new TenantDatabaseException("Could not revoke privileges to user {$clone['username']} for {$clone['database']}");
                }
                if ( ! DB::connection(static::systemConnectionName())
                    ->statement("drop database `{$clone['database']}`")
                ) {
                    throw new TenantDatabaseException("Could not drop database {$clone['database']}");
                }

                return true;
            });
    }
}

*TenantContextSession * *租户上下文会话*

<?php

namespace App\Repositories\Tenant;

use App\Repositories\Tenant\TenantContextRepositoryContract;

/**
 * Description of TenantContextSession
 *
 * @author safoor
 */
class TenantContextSession implements TenantContextRepositoryContract
{
//    public function __construct() {
//        $this->clearTenantSession();
//    }

    /**
     * Sets the connection name for the actual context
     * this tenant
     * @param $name
     * @return mixed
     */
    public function setConnectionName($name)
    {
        if (session()->has('tenant_connection')) {
            session()->set('tenant_connection', '');
        }

        session()->put('tenant_connection', $name);
    }

    /**
     * Get the name of the current connection in context
     * @return mixed
     */
    public function getConnectionName()
    {
        return session()->get('tenant_connection');
    }

    /**
     * Sets the id value filter data in the current context
     * @param $id
     * @return mixed
     */
    public function setTenantId($id)
    {
        if (session()->has('tenant_id')) {
            session()->set('tenant_id', '');
        }
        session()->put('tenant_id', $id);
    }

    /**
     *
     * @return mixed
     */
    public function getTenantId()
    {
        return session()->get('tenant_id');
    }

    public function clearTenantSession()
    {
        session()->flush();
        session()->set('tenant_id', '');
        session()->set('tenant_connection', '');
    }
}

I hope this will help to work with your scenario, I have tried to mention all of aspect we had to covered, but if still there is something confusing and need explanation from my side please let me know.我希望这对您的场景有所帮助,我已尝试提及我们必须涵盖的所有方面,但如果仍有一些令人困惑的问题并需要我的解释,请告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM