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



最近我一直忙于将一个旧项目从Symfony 2.7升级到3.4。该项目使用自定义的多租户设置,并依赖EventListener在基于子域的数据库之间切换。这是有效的,因为我可以在services.yml文件中定义EventListener的优先级,以便在任何其他服务之前加载它。通过这样做,我的所有其他代码都使用侦听器设置的数据库。

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

到目前为止还不错,但不幸的是,该逻辑不适用于我的任何租户特定命令。这些命令使用参数选项来定义应使用的数据库。由于我无法为这些命令定义优先级,其他服务已经加载,我无法从命令中覆盖数据库连接。下面的代码是我在命令和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()
);

上面的代码在Symfony 2.7中运行良好,但我的猜测是,在该版本和Symfony 3.4之间,服务的工作方式发生了变化,我无法再使用当前的数据库连接覆盖它们。我在迁移文档中没有遇到任何关于这个问题的更改,因此对如何解决这个问题一无所知。我希望这里有人对如何解决这个问题有任何想法或建议。

提前感谢并致以亲切的问候,

Kevin

问题是无法在运行时更改连接参数,我之前在Symfony 3.4中也遇到过同样的情况,我做了一个变通办法,通过扩展connection来支持运行时的更改参数。

我终于为创建了一个Symfony捆绑包

https://github.com/RamyHakam/doctrine-db-switcher-bundle但它只适用于Symfony 4+

然而,我可以和你分享它的想法,它应该在Symfony 3.4 上工作

你应该像这个例子一样配置你的连接

connections:
default:
driver: ''
charset: 
host: ''
port: ''
dbname: ''
user: ''
password: ''
wrapper_class: AppDoctrineDBALTenantConnection

其中wrapper_class is your new conneciton

类示例:



namespace AppDoctrineDBAL;

use DoctrineCommonEventManager;
use DoctrineDBALConfiguration;
use DoctrineDBALConnection;
use DoctrineDBALDriver;
use DoctrineDBALEvents;
use DoctrineDBALEvent;

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 DoctrineDBALDBALException
*/
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 EventConnectionEventArgs($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;
}

然后在您的事件侦听器中,您可以将连接切换到另一个DB

示例:


namespace AppEventListener;


use AppDoctrineDBALTenantConnection;
use AppMainDomainModelTenant;
use AppMainInfrastructurePersistenceDoctrineRepositoryTenantRepository;
use SymfonyComponentDependencyInjectionContainerInterface;
use SymfonyComponentEventDispatcherEventSubscriberInterface;
use SymfonyComponentHttpFoundationSessionSessionInterface;
use SymfonyComponentHttpKernelEventRequestEvent;

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

最新更新