40

如果相当容易的话,在 Symfony 中使用基于 URL 的不同实体管理器/连接。使用以下路由配置

connection:
    pattern:  /a/{connection}
    defaults: { _controller: AcmeTestBundle:User:index }

并来自以下食谱;

如何使用多个实体管理器和连接

我的控制器看起来像这样;

class UserController extends Controller
{
    public function indexAction($connection)
    {

        $products = $this->get('doctrine')
            ->getRepository('AcmeStoreBundle:Product', $connection)
            ->findAll()
        ;
        ..................

我将能够从不同的 em/connection/database 获取产品信息。

现在,如果我在我的路由中添加这样的东西;

login:
    pattern:  /a/{connection}/login
    defaults: { _controller: FOSUserBundle:Security:login }

如何轻松使登录以使用连接变量中定义的连接?

此设置假定每个数据库都有自己的用户登录信息(fos_user 表)。

编辑:更新的路由信息

编辑2:

不过,我还是 PHP/Symfony/Doctrine 的新手,所以如果我在这里完全错了,请原谅我。我试图在FOS\UserBundle\Doctrine\UserManager手动设置连接。下面是类的构造函数

//
use Doctrine\Common\Persistence\ObjectManager;
//

public function __construct(EncoderFactoryInterface $encoderFactory, CanonicalizerInterface $usernameCanonicalizer, CanonicalizerInterface $emailCanonicalizer, ObjectManager $om, $class)
{
    parent::__construct($encoderFactory, $usernameCanonicalizer, $emailCanonicalizer);

    $this->objectManager = $om;
    $this->repository = $om->getRepository($class);

    $metadata = $om->getClassMetadata($class);
    $this->class = $metadata->getName();
}

在控制器中,我们可以使用以下方法将 em 更改为 'testing'

$em = $this->get('doctrine')->getManager('testing');
$repository = $this->get('doctrine')->getRepository($class, 'testing')

为此,我将代码更改为以下代码以使用 EntityManager 而不是 ObjectManager。

//
//use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
//

public function __construct(EncoderFactoryInterface $encoderFactory, CanonicalizerInterface $usernameCanonicalizer, CanonicalizerInterface $emailCanonicalizer, EntityManager $om, $class)
{
    parent::__construct($encoderFactory, $usernameCanonicalizer, $emailCanonicalizer);

    $this->objectManager = $om;
    $this->repository = $om->getRepository($class);

    $metadata = $om->getClassMetadata($class);
    $this->class = $metadata->getName();
}

我的应用程序运行良好,没有错误。

从它与控制器的工作方式来看,我尝试通过向该行添加一个参数来更改连接,但它仍然使用默认连接。

$this->repository = $om->getRepository($class, 'testing');

我还能在这里错过什么?

4

2 回答 2

12

如您所见,FOSUserBundle 只能有一个 EntityManager。您可以从设置orm.xml中看到它

<service id="fos_user.entity_manager" factory-service="doctrine" factory-method="getManager" class="Doctrine\ORM\EntityManager" public="false">
    <argument>%fos_user.model_manager_name%</argument>
</service>

参数 %fos_user.model_manager_name% 在设置中指定为 model_manager_name

fos_user:
    db_driver:            ~ # Required
    user_class:           ~ # Required
    firewall_name:        ~ # Required
    model_manager_name:   ~

因此,构造函数中出现了 EntityManager 的实例,它不接受 getRepository 中的第二个参数。因此,标准的 FOSUserBundle 只能用于一个数据库。


但这不是故事的结束,它是 Symfony :) 我们可以写出 UserManager,它可以使用不同的数据库连接。在设置中看到 fos_user.user_manager 是 fos_user.user_manager.default。我们在 orm.xml 中找到它

<service id="fos_user.user_manager.default" class="FOS\UserBundle\Doctrine\UserManager" public="false">
    <argument type="service" id="security.encoder_factory" />
    <argument type="service" id="fos_user.util.username_canonicalizer" />
    <argument type="service" id="fos_user.util.email_canonicalizer" />
    <argument type="service" id="fos_user.entity_manager" />
    <argument>%fos_user.model.user.class%</argument>
</service>

我们可以重写这个类来添加一个额外的参数来决定你想要使用什么样的连接。通过ManagerFactory,您可以获得所需的ObjectManager。我为这两个数据库写了一个简单的例子(如果你需要更多的数据库,你可以为这个服务编写你的工厂)

在 services.yml 中定义你的服务

services:
    acme.user_manager.conn1:
        class: Acme\DemoBundle\Service\UserManager
        public: true
        arguments:
            - @security.encoder_factory
            - @fos_user.util.username_canonicalizer
            - @fos_user.util.email_canonicalizer
            - @doctrine
            - 'conn1_manager'
            - %fos_user.model.user.class%

    acme.user_manager.conn2:
        class: Acme\DemoBundle\Service\UserManager
        public: true
        arguments:
            - @security.encoder_factory
            - @fos_user.util.username_canonicalizer
            - @fos_user.util.email_canonicalizer
            - @doctrine
            - 'conn2_manager'
            - %fos_user.model.user.class%

你的经理

/**
 * Constructor.
 *
 * @param EncoderFactoryInterface $encoderFactory
 * @param CanonicalizerInterface  $usernameCanonicalizer
 * @param CanonicalizerInterface  $emailCanonicalizer
 * @param RegistryInterface       $doctrine
 * @param string                  $connName
 * @param string                  $class
 */
public function __construct(EncoderFactoryInterface $encoderFactory, CanonicalizerInterface $usernameCanonicalizer,
                            CanonicalizerInterface $emailCanonicalizer, RegistryInterface $doctrine, $connName, $class)
{
    $om = $doctrine->getEntityManager($connName);
    parent::__construct($encoderFactory, $usernameCanonicalizer, $emailCanonicalizer, $om, $class);
}

/**
 * Just for test
 * @return EntityManager
 */
public function getOM()
{
    return $this->objectManager;
}

和简单的测试

/**
 * phpunit -c app/ src/Acme/DemoBundle/Tests/FOSUser/FOSUserMultiConnection.php
 */
class FOSUserMultiConnection extends WebTestCase
{
    public function test1()
    {
        $client = static::createClient();

        /** @var $user_manager_conn1 UserManager */
        $user_manager_conn1 = $client->getContainer()->get('acme.user_manager.conn1');

        /** @var $user_manager_conn2 UserManager */
        $user_manager_conn2 = $client->getContainer()->get('acme.user_manager.conn2');

        /** @var $om1 EntityManager */
        $om1 = $user_manager_conn1->getOM();
        /** @var $om2 EntityManager */
        $om2 = $user_manager_conn2->getOM();

        $this->assertNotEquals($om1->getConnection()->getDatabase(), $om2->getConnection()->getDatabase());
    }
}

对不起,答案太大了。如果有什么不清楚到最后,我把代码放在github上

于 2013-04-28T10:39:26.933 回答
1

FosUserBundle 不能拥有多个实体管理器。

我发现使用 2 个数据库的最简单方法是覆盖 SecurityController 的“checkLoginAction”。

<?php
//in myuserBunle/Controller/SecurityController.php

class SecurityController extends BaseController
{

    /**
    * check the user information 
    */

    public function checkLoginAction(Request $request){
            $username = \trim($request->request->get("_username"));
            $user    =   $this->container->get('fos_user.user_manager')->findUserByUsername($username);
        $userDB2 =   .....


            $password = \trim($request->request->get('_password'));


            if ($user) {
              // Get the encoder  for the users password
              $encoder      =  $this->container->get('security.encoder_factory')->getEncoder($user);
              $encoded_pass =  $encoder->encodePassword($password, $user->getSalt());

              if (($user->getPassword() == $encoded_pass) || $this->checkSecondEM()) {
                $this->logUser($request, $user);
                return new RedirectResponse($this->container->get('router')->generate($this->container->get('session')->get('route'), $request->query->all() ));
              } else {
                // Password bad
                  return parent::loginAction($request);   
              }
            } else {
              // Username bad
                return parent::loginAction($request);   
            }
        }

}
于 2013-11-05T10:29:41.570 回答