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

Posted

技术标签:

【中文标题】是否有另一种方法可以在 Eloquent 模型上“设置连接”?【英文标题】:Is there another way to "setConnection" on an Eloquent Model? 【发布时间】:2017-05-07 11:17:00 【问题描述】:

我目前正在处理“多数据库即时交换连接”之类的项目。

所以我最终要做的是:

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

$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();

对于 Artisan 调用,我有点理解为什么 Laravel 需要引用保存在配置数组中的字符串中的连接。

但是在 Eloquent 模型本身上,我发现必须将我的数据库连接写入配置数组有点麻烦。所以它可以通过模型中的“单例方法”\Config::get().. 来获取。

有没有更优雅的东西,我可以直接注入配置而无需将其写入某个超级全局?

还是我错过了什么?

【问题讨论】:

一个mysql实例有多个DATABASEs?还是多个单独的连接参数? 一个连接,但不同的数据库 【参考方案1】:

creating a configuration array for each connection 可能会更好,然后您可以通过指定要使用的连接轻松地在连接之间切换。

如果您需要在同一型号上使用多个连接,您可以使用on method:

所以它会像User::on('dbconnection2')->find(1)

如果只是想为不同的模型使用不同的连接,可以在模型上设置protected $connection属性:

class User extends Model

    protected $connection = 'dbconnection2';

希望对您有所帮助。

【讨论】:

【参考方案2】:

您可以为您的模型创建一个工厂,并在引导您的应用时将连接传递给它:

<?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;
    

然后在你的代码中:

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

像这样!祝你好运! :-)

【讨论】:

这很漂亮。【参考方案3】:

我构建了一个多租户 laravel 应用程序,但很惊讶没有开箱即用的方法。

我有一个可通过不同子域使用的应用程序,子域应该是不同配置的关键,例如数据库连接。

您可以轻松地将其调整为您需要的任何标准,而不是子域。

通过动态 Config::set()

所以我的第一次尝试是始终使用“默认”连接并创建一个动态调用Config::set("database.connection.default.host", "1.3.5.7"); 等所有其他设置的中间件。

但也有一些缺点。例如,它非常慢,因为我从数据库中读取了所有值,后来又从 redis 中读取。但是更大的问题是例如设置到 redis 缓存的连接,因为在调用中间件覆盖配置设置之前,redis 连接已经建立。

通过自己的配置文件

所以我的第二种也是目前的方法是通过配置文件来实现。所以我创建了以下文件结构:

config/_myapp.php config/subdomain1/_myapp.php

注意:下划线很重要,因为文件是按字母顺序读取的,我们的新文件必须首先读取才能在其他配置文件中使用。

目标是使用config('_myapp.DB_HOST') 从 config/subdomain1/_myapp.php 中检索 DB_HOST 值,并将其用作 config/database.php 中的值。 所以 config/_myapp.php 只返回特定子域的 _myapp.php 内容。

在配置/_myapp.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 [

];

并在 config/_myapp.php 中:

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

例如在 config/database.php 或任何您需要域特定配置的地方使用它:

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

请询问是否有问题,我花了很长时间才弄清楚。

【讨论】:

【参考方案4】:

这完全取决于您如何处理多个连接。我从事过类似的需求项目。

我们有主从/租户数据库连接。配置https://gist.github.com/safoorsafdar/c6c623f9ec7b440f563d76995faf7aec#file-database-php 租户数据库/迁移/种子通过在系统中创建新帐户即时创建 可以从根控件软删除数据库。 在用户登录时,我们检查它是否是master,连接到master连接,否则获取租户连接,将信息存储在会话中以供Web访问。检查Tenant Setup after login。 数据库连接相关操作中心类。查看DatabaseConnection。 中心租户会话处理程序类,以帮助从会话中解析租户 ID。查看TenantContextSession 我们有单独的模型和抽象类供租户和主人设置模型连接。检查 您可能还需要在系统中处理迁移,请查看此内容 https://gist.github.com/safoorsafdar/cc9252a2f14301c3da942ca7bec7d66e

租户模型抽象类

<?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'));
        

    

登录后的租户设置

$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);

数据库连接

<?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', '');
    

我希望这将有助于您的场景,我已尝试提及我们必须涵盖的所有方面,但如果仍有一些令人困惑的地方需要我的解释,请告诉我。

【讨论】:

以上是关于是否有另一种方法可以在 Eloquent 模型上“设置连接”?的主要内容,如果未能解决你的问题,请参考以下文章

当用户按下回车键时,是不是有另一种方法可以在移动 Safari(iOS 上)上触发自动更正/自动完成?

是否有另一种方法可以在不使用 if 语句的情况下有条件地增加数组值? C++

是否有另一种方法可以从 Spring MVC 中的 HttpServletRequest 对象获取用户的时区? [复制]

是否有另一种方式(更有效的方式)来编写代码,以便它可以运行得更快?

是否有另一种方法来显式初始化父类?

QT 没有 QTNetworkManager 是不是有另一种加载图像的方法?