简体   繁体   English

Symfony 3.4 - 从命令切换到动态数据库

[英]Symfony 3.4 - switch to dynamic database from command

Recently I've been busy upgrading an older project from Symfony 2.7 to 3.4.最近我一直忙于将一个旧项目从 Symfony 2.7 升级到 3.4。 The project uses a custom multi tenant setup and relies on a EventListener to switch between databases based on subdomain.该项目使用自定义多租户设置,并依靠 EventListener 在基于子域的数据库之间切换。 This works since I can define the priority of that EventListener in my services.yml file so it's loaded before any other services.这是可行的,因为我可以在我的services.yml文件中定义该 EventListener 的优先级,以便在任何其他服务之前加载它。 By doing that all my other code uses that database set by the listener.通过这样做,我的所有其他代码都使用了侦听器设置的数据库。

    current_site_listener:
        class: AppBundle\Event\Listener\CurrentSiteListener
        arguments: ["@doctrine.dbal.default_connection", "%base_host%"]
        tags:
          - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 10 }

So far so good, but unfortunately that logic does not work for any of my tenant specific commands.到目前为止一切顺利,但不幸的是,该逻辑不适用于我的任何租户特定命令。 These commands use an argument option to define which database should be used.这些命令使用参数选项来定义应该使用哪个数据库。 Since I can't define a priority for these commands the other services are already loaded and I am not able to override the database connection from within the command.由于我无法为这些命令定义优先级,因此其他服务已经加载,我无法从命令中覆盖数据库连接。 The code below is what I'm using in both the command and EventListener and works fine in the latter (and also worked fine in Symfony 2.7 within the command).下面的代码是我在命令和 EventListener 中使用的代码,并且在后者中运行良好(并且在命令中的 Symfony 2.7 中也运行良好)。

        $connectionParams = array(
            'dbname' => $client['database_name'],
            'user' => $client['database_login'],
            'password' => $client['database_password'],
            'host' => $client['database_host'],
            'driver' => 'pdo_mysql',
            'charset' =>  'UTF8'
        );

        /** @var Connection $connection */
        $connection = $this->container->get('doctrine.dbal.default_connection');

        if ($connection->isConnected()) {
            $connection->close();
        }
        $connection->__construct(
            $connectionParams, $connection->getDriver(), $connection->getConfiguration(),
            $connection->getEventManager()
        );

The code above was working fine in Symfony 2.7 but my guess is that between that version and Symfony 3.4 something has changed in the way services work and I am not able to override them anymore with my current database connection.上面的代码在 Symfony 2.7 中运行良好,但我的猜测是在该版本和 Symfony 3.4 之间,服务的工作方式发生了一些变化,我无法再用我当前的数据库连接覆盖它们。 I haven't come across any changes regarding this issue in the migration docs and am therefore clueless on how to solve this.我在迁移文档中没有遇到有关此问题的任何更改,因此对如何解决此问题一无所知。 I'm hoping someone on here has any ideas or suggestions on how to fix this.我希望这里有人对如何解决这个问题有任何想法或建议。

Thanks in advance and kind regards,在此先感谢和亲切的问候,

Kevin凯文

The problem is you can not change the connection parameters in runtime, I had the same before in Symfony 3.4 and I made a workaround that by extending connection to support change parameters in runtime.问题是您无法在运行时更改连接参数,我之前在 Symfony 3.4 中也有过相同的情况,我通过扩展connection以支持运行时更改参数来解决此问题。

I finally had created a Symfony bundle just for that我终于为此创建了一个 Symfony 包

https://github.com/RamyHakam/doctrine-db-switcher-bundle But It will work only on Symfony 4+ https://github.com/RamyHakam/doctrine-db-switcher-bundle但它只适用于 Symfony 4+

However, I can share the idea of it with you it should work on Symfony 3.4但是,我可以与您分享它的想法,它应该适用于 Symfony 3.4

You should configure your connection like this example你应该像这个例子一样配置你的连接

connections:
  default:
    driver: ''
    charset: 
    host: ''
    port: ''
    dbname: ''
    user: ''
    password: ''
    wrapper_class: App\Doctrine\DBAL\TenantConnection

Where wrapper_class is your new conneciton wrapper_class is your new conneciton

Class Example: Class 示例:


    
    namespace App\Doctrine\DBAL;
    
    use Doctrine\Common\EventManager;
    use Doctrine\DBAL\Configuration;
    use Doctrine\DBAL\Connection;
    use Doctrine\DBAL\Driver;
    use Doctrine\DBAL\Events;
    use Doctrine\DBAL\Event;
    
    class TenantConnection extends Connection
    {
        /** @var mixed */
        protected $params = [];
        /** @var bool */
        protected $isConnected = false;
        /** @var bool */
        protected $autoCommit = true;
    
        /**
         * TenantConnection constructor.
         *
         * @param $params
         * @param Driver $driver
         * @param Configuration|null $config
         * @param EventManager|null $eventManager
         * @throws \Doctrine\DBAL\DBALException
         */
        public function __construct($params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null)
        {
            $this->params = $params;
            parent::__construct($params, $driver, $config, $eventManager);
        }
    
        /**
         * @return bool
         */
        public function connect()
        {
            if ($this->isConnected) {
                return false;
            }
    
            $driverOptions = $this->params['driverOptions'] ?? [];
            $user = $this->params['user'] ?? null;
            $password = $this->params['password'] ?? null;
    
            $this->_conn = $this->_driver->connect($this->params, $user, $password, $driverOptions);
            $this->isConnected = true;
    
            if ($this->autoCommit === false) {
                $this->beginTransaction();
            }
    
            if ($this->_eventManager->hasListeners(Events::postConnect)) {
                $eventArgs = new Event\ConnectionEventArgs($this);
                $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
            }
    
            return true;
        }
    
        /**
         * @param string $dbName
         * @param string $dbUser
         * @param string $dbPassword
         */
        public function changeParams(string $dbName, string $dbUser, string $dbPassword)
        {
            $this->params['dbname'] = $dbName;
            $this->params['user'] = $dbUser;
            $this->params['password'] = $dbPassword;
        }
    
        public function reconnect()
        {
            if ($this->isConnected) {
                $this->close();
            }
    
            $this->connect();
        }
    
        /**
         * @return mixed|mixed[]
         */
        public function getParams()
        {
            return $this->params;
        }
    
        public function close()
        {
            $this->_conn = null;
    
            $this->isConnected = false;
        }

Then in your event listener, you can switch the connection to another DB然后在您的事件侦听器中,您可以将连接切换到另一个数据库

Example:例子:



    namespace App\EventListener;
    
    
    use App\Doctrine\DBAL\TenantConnection;
    use App\Main\Domain\Model\Tenant;
    use App\Main\Infrastructure\Persistence\Doctrine\Repository\TenantRepository;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    use Symfony\Component\HttpFoundation\Session\SessionInterface;
    use Symfony\Component\HttpKernel\Event\RequestEvent;
    
    class RequestListener implements EventSubscriberInterface
    {
        /**
         * @var ContainerInterface
         */
        private $container;
        /**
         * @var SessionInterface
         */
        private $session;
        /**
         * @var TenantRepository
         */
        private $tenantRepository;
    
        public function __construct(ContainerInterface $container,TenantRepository $tenantRepository)
        {
            $this->container = $container;
            $this->tenantRepository = $tenantRepository;
        }
    
        public static function getSubscribedEvents()
        {
            return [
                RequestEvent::class => 'onKernelRequest'
            ];
        }
    
        public function onKernelRequest( RequestEvent $event)
        {
            $userAgent = $event->getRequest()->headers->get('user_identifier');
    
            /**
             * @var TenantConnection $tenantConnection
             */
           $tenantConnection = $this->container->get('doctrine')->getConnection('tenant');
           /**@var Tenant $tenant */
            $tenants = $this->tenantRepository->findAll();
    
            $tenant = $tenants[1];
    
            $tenantConnection->changeParams($tenant->getDbName(), $tenant->getDbUserName(), $tenant->getDbPassword());
            $tenantConnection->reconnect();
        }
    }


  [1]: https://github.com/RamyHakam/doctrine-db-switcher-bundle

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

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