Symfony2中的多个动态数据库连接

5
我有一个Symfony2应用程序,我想通过使用一个数据库pr租户来实现多租户(有些人认为这不是真正的多租户,但这并不是重点)。 文档描述了如何实现这一点。然而,我想能够动态创建租户,并将新的数据库连接详细信息(和实体管理器)直接写入config.yml文件似乎很混乱。我宁愿有一个单独的数据库来保存租户及其连接,然后根据标识符(例如从应用程序的子域 - clientname.app.com中获取的标识符)选择适当的连接/ em。
使用这种方法,我应该能够实现这一点,但同时可能会破坏运行命令行命令以更新数据库模式等时指定数据库连接和/或实体管理器的能力。
假设我想做的事情是有意义的,有没有聪明的方法来实现这一点?

嗨Eirik,你最终解决了这个问题吗?我遇到了相同的问题,并考虑了相同的方法。提前感谢你! - Peter
@Pknife 我最终创建了一个脚本,动态创建数据库并更新config.yml文件。不过,我在这方面遇到了一些缓存问题,由于这是一个业余项目,因此尚未投入生产。 - Eirik A. Johansen
4个回答

3

我使用静态数据库处理登录和租户信息,使用第二个数据库来保存用户数据。

app/config/config.yml:

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver:   "%database_driver%"
                host:     "%database_host%"
                port:     "%database_port%"
                dbname:   "%database_name%"
                user:     "%database_user%"
                password: "%database_password%"
                charset:  UTF8
            tenantdb:
                driver:   "%database_driver%"
                host:     "%database_host%"
                port:     "%database_port%"
                dbname:   "%database_name%"
                user:     "%database_user%"
                password: "%database_password%"
                charset:  UTF8
    orm:
        default_entity_manager: default
        entity_managers:
            default:
                 connection: default
                 mappings:
                    MyCoreBundle: ~
            tenantdb:
                 connection: tenantdb
                 mappings:
                     MyAppBundle: ~

然后,在控制器中,不要再使用

         $something = $this->getDoctrine()
                           ->getManager()
                           ->getRepository('MyAppBundle:Thing')
                           ->findAll();

我们做了:

         $something = $this->getDoctrine()
                           ->getManager('tenantdb')
                           ->getRepository('MyAppBundle:Thing', 'tenantdb')
                           ->findAll();

您可以在此处找到详细信息: http://symfony.com/doc/current/cookbook/doctrine/multiple_entity_managers.html

然后,基于 Symfony2,动态DB连接/早期重写Doctrine服务, 我设置了一个服务来根据请求的子域名(例如tenant1.example.com tenant2.example.com)切换数据库。

src/MyCoreBundle/Resources/config/services.yml:

services:
    my.database_switcher:
        class: MyCoreBundle\EventListener\DatabaseSwitcherEventListener
        arguments:  [@request, @doctrine.dbal.tenantdb_connection]
        scope:      request
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

MyCoreBundle\EventListener\DatabaseSwitcherEventListener.php

namespace MyCoreBundle\EventListener;

use Symfony\Component\HttpFoundation\Request;
use Doctrine\DBAL\Connection;

class DatabaseSwitcherEventListener {

    private $request;
    private $connection;

    public function __construct(Request $request, Connection $connection) {
        $this->request = $request;
        $this->connection = $connection;
    }

    public function onKernelRequest() {
        $connection = $this->connection;
        if (! $connection->isConnected()) {
            $params = $this->connection->getParams();
            $subdomain = __GET_SUBDOMAIN__();
            $oldname = preg_replace (
                "/_tenant_$subdomain|_template/",
                '',
                $params['dbname']
            );
            $params['dbname'] =  $oldname . ($subdomain ? "_tenant_$subdomain"
                                                        : "_template");
            $connection->__construct(
                $params,
                $connection->getDriver(),
                $connection->getConfiguration(),
                $connection->getEventManager()
            );
            $connection->connect();
        }
    }

}

为了方便起见,我们有一个名为XXX_template的“额外”租户数据库,系统管理员在进行全局更改时连接到该数据库。 计划是在创建租户时将此数据库复制到租户数据库。


这种动态分配数据库名称的方法存在一个问题,即像 php app/console doctrine:database:create --connection=tenantdb 这样的命令不再起作用,因为它们从配置文件中读取名称而没有参考事件监听器。 - Andy Preston

2

创建一个服务,根据用户的凭证生成定制的实体管理器。

$this->get('my.db.service')->getEmForUser('bob');

然后您的服务应该像这样:

class EntityManagerService
{

   function __construct($doctrine)
   { ... }

   function getEmForUser($user)
   {
      //look up Bob's connection details in your connection db
      //and get them using the globally configured entity manager

      //create Entity Manager using bob's config

      return $em.

    }

这是最可重复使用的方法,它符合Symfony2使用的依赖注入模式。您将希望返回此类的实例。请参考以下链接:https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/EntityManager.php

1
感谢您的回复。然而,这并没有真正解决我的关注点,即如何在保持命令行功能的同时使连接详细信息可以动态更新。 - Eirik A. Johansen

0

不知道我是否完全理解了你的问题,但是我使用以下方法连接到不同的数据库:

    $connectionFactory = $this->container->get('doctrine.dbal.connection_factory');
    $conn = $connectionFactory->createConnection(array(
        'driver' => 'pdo_mysql',
        'user' => 'mattias',
        'password' => 'nkjhnjknj',
        'host' => 'fs1.uuyh.se',
        'dbname' => 'csmedia',
    ));
    return $conn;

0

我们在项目中遇到了同样的问题。

我们在供应商包中创建了一个名为:connectionManager 的新服务。

该服务是一个多例,通过参数返回 entityManager。


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