скажем, у меня есть 3 базы данных:
И я хочу подключиться к ним динамически с URL-адреса, такого как этот http://localhost/my-project/web/app_dev.php/db1/books, поэтому я знаю, к какой базе данных подключаться по URL-адресу (в данном случае prefix_db1)
И в основном идея заключалась в том, чтобы подготовить слушателя, который будет запускаться с каждым HTTP-запрос, получить имя базы данных из URL-адреса, а затем переопределить параметры doctrin, что-то вроде этого:
В пределах services.yml:
dynamic_connection:
class: AppBundle\service\DynamicDBConnector
arguments: ['@request_stack']
calls:
- [ setDoctrineConnection, ['@doctrine.dbal.default_connection'] ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Мой слушатель:
<?php
namespace AppBundle\service;
use Doctrine\DBAL\Connection;
use Symfony\Component\HttpFoundation\RequestStack;
use Exception;
class DynamicDBConnector
{
/**
* @var Connection
*/
private $connection;
/*
* @var Request
*/
private $request;
public function __construct(RequestStack $requestStack)
{
$this->request = $requestStack->getCurrentRequest();
}
/**
* Sets the DB Name prefix to use when selecting the database to connect to
*
* @param Connection $connection
* @return DynamicDBConnector $this
*/
public function setDoctrineConnection(Connection $connection)
{
$this->connection = $connection;
return $this;
}
public function onKernelRequest()
{
if ($this->request->attributes->has('_company')) {
$connection = $this->connection;
$params = $this->connection->getParams();
$companyName = $this->request->get('_company');
// I did the concatenation here because in paramaters.yml I just put the prefix (database_name: prefix_) so after the concatenation I get the whole database name "prefix_db1"
$params['dbname'] = $params['dbname'] . $companyName;
// Set up the parameters for the parent
$connection->__construct(
$params,
$connection->getDriver(),
$connection->getConfiguration(),
$connection->getEventManager()
);
try {
$connection->connect();
} catch (Exception $e) {
// log and handle exception
}
}
return $this;
}
}
Теперь это сработало очень хорошо, я протестировал его, используя простой список книг, и каждый раз, когда я меняю URL-адрес, я получаю список, связанный с каждой базой данных:
http://localhost/my-project/web/app_dev.php/db1/books // I get books of database prefix_db1
http://localhost/my-project/web/app_dev.php/db2/books // I get books of database prefix_db2
А теперь перейдем к задаче :):
Теперь проблема в том, что когда я защищаю свой проект системой аутентификации и пытаюсь войти в систему (конечно, каждая база данных имеет таблицу user), используя этот URL-адрес http://localhost/my-project/web/app_dev.php/db1/login
Я получаю это исключение:
An exception occured in driver: SQLSTATE[HY000] [1049] Base 'prefix_' unknown
Как вы можете видеть, symfony попыталась войти в систему, используя database_name, объявленный в parameters.yml, что означает, что security_checker symfony был запущен до моего слушателя и до переопределения params.
Doctrine.
Мой вопрос:
Есть ли способ запустить мой слушатель перед любым другим слушателем HTTP-запросов? или, возможно, альтернативное решение, чтобы убедиться, что любой запрос к базе данных должен быть с правильным именем базы данных.
Извините за длинный пост.
Обновлено:
Из официальной документации symfony:
https://symfony.com/doc/2.3/cookbook/event_dispatcher/event_listener.html
The other optional tag attribute is called priority, which defaults to 0 and it controls the order in which listeners are executed (the highest the priority, the earlier a listener is executed). This is useful when you need to guarantee that one listener is executed before another. The priorities of the internal Symfony listeners usually range from -255 to 255 but your own listeners can use any positive or negative integer.
Я установил приоритет моего слушателя на 10000:
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 10000 }
Но проблема не исчезла, мой слушатель по-прежнему не запускался до symfony!
Что общего у вашей базы данных? вместо этого вы можете создать 3 отдельных экземпляра своего приложения, по одному для каждой базы данных. (Извините, я не могу помочь вам с проблемой префикса)
они все одинаковые, на самом деле в будущем будет более 3 баз данных они будут создаваться динамически с помощью команд доктрины doctrine:database:createdoctrine:schema:update --force
Возможный дубликат Symfony2, динамическое соединение с БД / раннее переопределение службы Doctrine






вы должны добавить имя базы данных в свой config.yml следующим образом:
orm:
auto_generate_proxy_classes: '%kernel.debug%'
# naming_strategy: doctrine.orm.naming_strategy.underscore
# auto_mapping: true
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
DataMiningBundle: ~
AppBundle: ~
UserBundle: ~
your_second_db:
connection: your_second_db (decalared in parameters.yml)
mappings:
yourBundle: ~
и вызовите его со своего контроллера:
$em = $doctrine->getConnection('your_second_db');
Нет, я не могу, потому что базы данных будут создаваться динамически из другой формы, они не предопределены :)
я не думаю, что @SlimenTN не знал этого мистера Хейтема
Нашел решение
Идея состоит в том, чтобы изменить класс Connection по умолчанию, который symfony использует для создания соединения с базой данных:
doctrine:
dbal:
connections:
default:
wrapper_class: AppBundle\Doctrine\DynamicConnection
driver: pdo_mysql
host: '%database_host%'
port: '%database_port%'
dbname: '%database_name%'
user: '%database_user%'
password: '%database_password%'
charset: UTF8
После этого мы можем изменить данные параметры в конструкторе:
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
class DynamicConnection extends Connection
{
public function __construct(array $params, Driver $driver, $config, $eventManager)
{
$params['dbname'] = 'teqsdqsdqst';
parent::__construct($params, $driver, $config, $eventManager);
}
}
Теперь нам просто нужно получить параметр из URL и установить внутри $params['dbname'].
.
Таким образом мы гарантируем, что symfony всегда будет использовать этот класс для создания соединения, и нам больше не нужно запускать слушателей с помощью HTTP-запросы
Как получить параметры из метода конструктора? Я пробовал использовать RequestStack, но мне это не дает.
@Giovani, пожалуйста, проверьте отмеченный ответ, возможно, это поможет, потому что я слежу за ним, и у меня он работает нормально :)
Отличное решение, но если вы хотите получить параметр _company из URL-адреса, вы можете получить контейнер внутри конструктора через объект EventManager, переданный в параметрах, и получить из него текущий запрос, на самом деле контейнер вводится в ContainerAwareEventManager, подкласс EventManager
class DynamicDBConnector extends Connection
{
public function __construct($params, $driver, $config, $eventManager)
{
if (!$this->isConnected()){
// Create default config and event manager if none given (case in command line)
if (!$config) {
$config = new Configuration();
}
if (!$eventManager) {
$eventManager = new EventManager();
}
$refEventManager = new \ReflectionObject($eventManager);
$refContainer = $refEventManager->getProperty('container');
$refContainer->setAccessible('public'); //We have to change it for a moment
/*
* @var \Symfony\Component\DependencyInjection\ContainerInterface $container
*/
$conrainer = $refContainer->getValue($eventManager);
/*
* @var Symfony\Component\HttpFoundation\Request
*/
$request = $conrainer->get('request_stack')->getCurrentRequest();
if ($request != null && $request->attributes->has('_company')) {
$params['dbname'] .= $request->attributes->get('_company');
}
$refContainer->setAccessible('private'); //We put in private again
parent::__construct($params, $driver, $config, $eventManager);
}
}
}
@goto у каждой базы данных есть свои пользователи, поэтому мне нужно знать, какой из них, прежде чем подключаться к ней