Подтвердить что ты не робот

Множественный менеджер объектов для FOSUserBundle

Чтобы использовать различные Entity Manager/Connection на основе URL-адреса в Symfony, достаточно просто. Со следующей конфигурацией маршрутизации

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

и из следующей Cookbook;

Как работать с несколькими диспетчерами сущностей и подключениями

Мой контроллер будет выглядеть примерно так:

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).

Изменить: обновленная информация о маршрутизации

Edit2:

Я все еще новичок в 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');

Что еще я могу здесь пропустить?

4b9b3361

Ответ 1

Как вы можете видеть, 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, который может использовать разные подключения к db. В настройке см. 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. Я написал простой пример для двух таблиц данных (если вам нужно больше баз данных, вы можете написать factory для этой службы)

определить свои услуги в 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

Ответ 2

FosUserBundle не может иметь более одного менеджера сущностей.

Самый простой способ, которым я нашел использование двух баз данных, - переопределить "checkLoginAction" SecurityController.

<?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);   
            }
        }

}