Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/symfony/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php Symfony2,动态数据库连接/早期覆盖条令服务_Php_Symfony_Doctrine Orm - Fatal编程技术网

Php Symfony2,动态数据库连接/早期覆盖条令服务

Php Symfony2,动态数据库连接/早期覆盖条令服务,php,symfony,doctrine-orm,Php,Symfony,Doctrine Orm,我有一个核心配置数据库,每一行都是一个带有一些基本配置的“应用程序”。 选择应用程序后,我希望使用该行(ID)的属性连接到数据库,并且主机也可能会根据该行进行更改 我想要的是注册一个服务,该服务使用这些详细信息设置条令服务,如果您在站点上的某个位置需要它(我知道这是基于URI的) 我正在使用实体管理器和各种条令侦听器/事件子类 我使用过ConnectionFactory,但这似乎会导致订阅者出现问题 连接透明地修改Doctrine服务的东西的最佳方式是什么,这样控制器就可以在不知道连接到哪个DB

我有一个核心配置数据库,每一行都是一个带有一些基本配置的“应用程序”。
选择应用程序后,我希望使用该行(ID)的属性连接到数据库,并且主机也可能会根据该行进行更改

我想要的是注册一个服务,该服务使用这些详细信息设置条令服务,如果您在站点上的某个位置需要它(我知道这是基于URI的)

我正在使用实体管理器和各种条令侦听器/事件子类

我使用过ConnectionFactory,但这似乎会导致订阅者出现问题

连接透明地修改Doctrine服务的东西的最佳方式是什么,这样控制器就可以在不知道连接到哪个DB主机和DB名称的情况下进行操作

此类型的每个数据库都将具有相同的结构,因此所有实体映射都是正确的

我正在寻找一个真正干净的实现,希望使用服务容器来避免任何“黑客”


有人知道这样做吗?

这里是新的改进的非反射版本

#services.yml
acme_app.dynamic_connection:
    class: %acme.dynamic_doctrine_connection.class%
    calls:
        - [setDoctrineConnection, [@doctrine.dbal.default_connection]]


<?php

namespace Acme\Bundle\AppBundle;

use Doctrine\DBAL\Connection;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
use Exception;

class DynamicDoctrineConnection
{
    /**
     * @var Connection
     */
    private $connection;

    /**
     * Sets the DB Name prefix to use when selecting the database to connect to
     *
     * @param  Connection       $connection
     * @return SiteDbConnection $this
     */
    public function setDoctrineConnection(Connection $connection)
    {
        $this->connection = $connection;

        return $this;
    }

    public function setUpAppConnection()
    {
        if ($this->request->attributes->has('appId')) {
            $connection = $this->connection;
            $params     = $this->connection->getParams();

            // we also check if the current connection needs to be closed based on various things
            // have left that part in for information here
            // $appId changed from that in the connection?
            // if ($connection->isConnected()) {
            //     $connection->close();
            // }

            // Set default DB connection using appId
            //$params['host']   = $someHost;
            $params['dbname'] = 'Acme_App'.$this->request->attributes->get('appId');

            // 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;
    }
}
#services.yml
acme_app.dynamic_连接:
类:%acme.dynamic\u连接。类%
电话:
-[setDoctrineConnection,[@doctrine.dbal.default\u connection]]

我查看了您的服务并尝试实现它,但看起来您缺少了一些需要传递到构造函数中的参数。以下是一个应该可以使用的更新版本:

#services.yml
parameters:
    acme_page.dynamic_doctrine_connection.class: Acme\Bundle\PageBundle\DynamicDoctrineConnection

services:
    acme_page.dynamic_doctrine_connection:
        class:      %acme_page.dynamic_doctrine_connection.class%
        arguments:  [@request, @doctrine.dbal.client_connection, @doctrine]
        scope:      request
        calls:
            - [setContainer, [@service_container]]
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

//DynamicDoctrineConnection.php
<?php

namespace Acme\Bundle\PageBundle;

use Symfony\Component\HttpFoundation\Request;
use Doctrine\DBAL\Connection;
use Doctrine\Bundle\DoctrineBundle\Registry;

/**
 * Creates a Doctrine connection from attributes in the Request
 */
class DynamicDoctrineConnection
{
    private $request;
    private $defaultConnection;
    private $doctrine;

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

    public function onKernelRequest()
    {
        if ($this->request->attributes->has('appId')) {

            $dbName             = 'Acme_App_'.$this->request->attributes->get('appId');

            $this->defaultConnection->close();

            $reflectionConn     = new \ReflectionObject($this->defaultConnection);
            $reflectionParams   = $reflectionConn->getProperty('_params');
            $reflectionParams->setAccessible(true);

            $params             = $reflectionParams->getValue($this->defaultConnection);
            $params['dbname']   = $dbName;

            $reflectionParams->setValue($this->defaultConnection, $params);
            $reflectionParams->setAccessible(false);

            $this->doctrine->resetEntityManager('default');
    }
}
#services.yml
参数:
acme\u page.dynamic\u doctrine\u connection.class:acme\Bundle\PageBundle\DynamicDoctrineConnection
服务:
acme\u页面。动态\u原则\u连接:
类:%acme\u页。动态\u连接。类%
参数:[@request,@doctrine.dbal.client_connection,@doctrine]
范围:请求
电话:
-[setContainer,[@service_container]]
标签:
-{name:kernel.event_侦听器,事件:kernel.request,方法:onKernelRequest}
//DynamicOctRineConnection.php

结合起来,这两个帖子帮助我解决了我自己非常相似的问题。这是我的解决方案,可能对其他人有用:

<?php

namespace Calitarus\CollaborationBundle\EventListener;

use Symfony\Component\HttpFoundation\Request;
use Doctrine\DBAL\Connection;
use Exception;
use Monolog\Logger;



class DatabaseSwitcherEventListener {

    private $request;
    private $connection;
    private $logger;

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


    public function onKernelRequest() {
        if ($this->request->attributes->has('_site')) {
            $site = $this->request->attributes->get('_site');

            $connection = $this->connection;
            $params     = $this->connection->getParams();

            $db_name = 'br_'.$this->request->attributes->get('_site');
            // TODO: validate that this site exists
            if ($db_name != $params['dbname']) {
                $this->logger->debug('switching connection from '.$params['dbname'].' to '.$db_name);
                $params['dbname'] = $db_name;
                if ($connection->isConnected()) {
                    $connection->close();
                }
                $connection->__construct(
                    $params, $connection->getDriver(), $connection->getConfiguration(),
                    $connection->getEventManager()
                );

                try {
                    $connection->connect();
                } catch (Exception $e) {
                    // log and handle exception
                }
            }
        }
    }
}
我有这个路由配置来获取_site参数,在我的例子中,它是URL的一部分,但是您可能可以通过其他方式获取它,具体取决于您的设置:

resource: "@CCollabBundle/Controller"
type:     annotation
prefix:   /{_site}
defaults:
 _site: default

在symfony 4中,您可以使用包装器类实现:

# doctrine.yaml
doctrine:
    dbal:
      connections:
        default:
          wrapper_class: App\Service\Database\DynamicConnection
该类只是扩展了原始连接:

class DynamicConnection extends \Doctrine\DBAL\Connection
{

    public function changeDatabase(string $dbName)
    {
        $params = $this->getParams();

        if ($this->isConnected())
            $this->close();

        if (isset($params['url'])) {
            $params['url'] = preg_replace(
                sprintf("/(?<=\/)%s/", preg_quote($this->getDatabase())),
                $dbName,
                $params['url']
            );
        }

        if (isset($params['dbname']))
            $params['dbname'] = $dbName;

        parent::__construct(
            $params,
            $this->_driver,
            $this->_config,
            $this->_eventManager
        );

    }
}
类动态连接扩展\doctor\DBAL\Connection
{
公共函数changeDatabase(字符串$dbName)
{
$params=$this->getParams();
如果($this->isConnected())
$this->close();
if(isset($params['url'])){
$params['url']=preg_replace(
sprintf(“/(?Symfony 4

在服务定义中使用decorator模式的最干净方式:

首先创建一个自定义类,如App\Factory\Authentication\DatabaseConnectionFactory 然后,该类将使用doctor.dbal.connection\u工厂的实例进行实例化

#services.xml
App\Factory\Authentication\DatabaseConnectionFactory:
        decorates: doctrine.dbal.connection_factory
        arguments:
            $wrappedConnectionFactory: '@App\Factory\Authentication\DatabaseConnectionFactory.inner'
/** App\Factory\Authentication\DatabaseConnectionFactory
 * @param array              $params
 * @param Configuration|null $config
 * @param EventManager|null  $eventManager
 * @param array              $mappingTypes
 *
 * @throws \DomainException
 *
 * @return mixed
 */
public function createConnection(array $params, Configuration $config = null, EventManager $eventManager = null, array $mappingTypes = [])
{
    $params['url'] = $this->databaseConnectionUrlService->getDatabaseConnectionUrlForApiUser($this->apiUser, $params['url'] );

    return $this->wrappedConnectionFactory->createConnection($params, $config, $eventManager, $mappingTypes);
}
在我们的自定义连接工厂类中,模拟createConnection()函数,并通过调用wrappedConnectionFactory(=doctrine.dbal.connection\u工厂)上的函数来执行原始createConnection()逻辑


我也整理了一下,注入了默认连接,因此可以删除ContainerWare扩展。$this->request->attributes从哪里来?嘿,已经更新了我在twitter上提到的答案。新的和改进的,没有使用反射。这是正确的,因为在我的示例中,我使用子域来确定哪个database访问。控制台命令必须明确地获得此信息,并进行自己的切换。
changeDatabase
Connection
中不存在,因此它没有任何效果!@dan zauner,那么如何使用它呢?我想建议您在对象上运行时调用
changeDatabase
(这意味着必须使用DynamicConnection类来输入)。在我看来,它不太好
/** App\Factory\Authentication\DatabaseConnectionFactory
 * @param array              $params
 * @param Configuration|null $config
 * @param EventManager|null  $eventManager
 * @param array              $mappingTypes
 *
 * @throws \DomainException
 *
 * @return mixed
 */
public function createConnection(array $params, Configuration $config = null, EventManager $eventManager = null, array $mappingTypes = [])
{
    $params['url'] = $this->databaseConnectionUrlService->getDatabaseConnectionUrlForApiUser($this->apiUser, $params['url'] );

    return $this->wrappedConnectionFactory->createConnection($params, $config, $eventManager, $mappingTypes);
}