Helpex - Trao đổi & giúp đỡ Đăng nhập

Kết nối cơ sở dữ liệu động symfony2

Đặng Trường Liên
· 19:01 26/05/2013
00:53:18 08/06/2021

Dự án symfony2 của tôi có một cơ sở dữ liệu chính và nhiều cơ sở dữ liệu con. Mỗi cơ sở dữ liệu con được tạo cho mỗi người dùng, thông tin xác thực cơ sở dữ liệu được lưu trữ trong cơ sở dữ liệu chính. Khi người dùng đăng nhập, thông tin đăng nhập cơ sở dữ liệu cụ thể của người dùng được tìm nạp từ cơ sở dữ liệu chính và lý tưởng là kết nối cơ sở dữ liệu con nên được thiết lập. Tôi cũng tìm kiếm trên Google, và tôi đã xem qua một số giải pháp và cuối cùng đã làm được những việc sau:

#config.yml

doctrine:
dbal:
    default_connection:       default
    connections:
        default:
            dbname:           maindb
            user:             root
            password:         null
            host:             localhost
        dynamic_conn:
            dbname:           ~
            user:             ~
            password:         ~
            host:             localhost
orm:
    default_entity_manager:   default
    entity_managers:
        default:
            connection:       default
            auto_mapping:     true
        dynamic_em:
            connection:       dynamic_conn
            auto_mapping:     true

Tôi đã tạo một kết nối mặc định để kết nối với cơ sở dữ liệu chính và một kết nối trống cho cơ sở dữ liệu con, tương tự như vậy, tôi đã tạo các trình quản lý thực thể. Sau đó, tôi đã tạo trình nghe sự kiện mặc định và thêm mã sau vào 'onKernelRequest':

public function onKernelRequest(GetResponseEvent $event) //works like preDispatch in Zend
{
    //code to get db credentials from master database and stored in varaiables
    ....
    $connection = $this->container->get(sprintf('doctrine.dbal.%s_connection', 'dynamic_conn'));
    $connection->close();

    $refConn = new \ReflectionObject($connection);
    $refParams = $refConn->getProperty('_params');
    $refParams->setAccessible('public'); //we have to change it for a moment

    $params = $refParams->getValue($connection);
    $params['dbname'] = $dbName;
    $params['user'] = $dbUser;
    $params['password'] = $dbPass;

    $refParams->setAccessible('private');
    $refParams->setValue($connection, $params);
    $this->container->get('doctrine')->resetEntityManager('dynamic_em');
    ....
}

Đoạn mã trên đặt các tham số cơ sở dữ liệu con và đặt lại trình quản lý thực thể dynamic_em.

Khi tôi làm như sau trong một số bộ điều khiển, nó hoạt động tốt và dữ liệu nếu được tìm nạp từ cơ sở dữ liệu con.

$getblog= $em->getRepository('BloggerBlogBundle:Blog')->findById($id); //uses doctrine

Tuy nhiên, khi tôi sử dụng ngữ cảnh bảo mật như được thấy trong đoạn mã sau, tôi gặp lỗi 'KHÔNG ĐƯỢC CHỌN CƠ SỞ DỮ LIỆU'.

$securityContext = $this->container->get('security.context');
$loggedinUserid = $securityContext->getToken()->getUser()->getId();

Làm cách nào để đặt kết nối cơ sở dữ liệu động và sử dụng cả ngữ cảnh bảo mật?

CẬP NHẬT: -

Sau nhiều thời gian dành cho việc thử và sai, và tìm kiếm xung quanh, tôi nhận ra rằng điều đó security.contextđược đặt trước khi thực thi onKernelRequest. Bây giờ câu hỏi là làm sao để đưa các chi tiết kết nối cơ sở dữ liệu vào security.context, và nơi để tiêm?

Chúng ta cần đến điểm mà ngữ cảnh bảo mật và DBAL được thiết lập và mã thông báo bảo mật được tạo và chúng ta có thể thao tác các chi tiết kết nối cơ sở dữ liệu.

Do đó, như người trong liên kết sau đã nêu, tôi đã thực hiện các thay đổi đối với mã của mình, vì đó chính xác là những gì tôi muốn làm. http://forum.symfony-project.org/viewtopic.php?t=37398&p=124413

Điều đó khiến tôi thêm mã sau vào dự án của mình:

#config.yml //remains unchanged, similar to above code

Một pass trình biên dịch được tạo như sau:

// src/Blogger/BlogBundle/BloggerBlogBundle.php
namespace Blogger\BlogBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

use Blogger\BlogBundle\DependencyInjection\Compiler\CustomCompilerPass;

class BloggerBlogBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $container->addCompilerPass(new CustomCompilerPass());
    }
}

Vượt qua trình biên dịch như sau:

# src/Blogger/BlogBundle/DependencyInjection/Compiler/CustomCompilerPass.php

class CustomCompilerPassimplements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $connection_service = 'doctrine.dbal.dynamic_conn_connection';
        if ($container->hasDefinition($connection_service))
        {
            $def = $container->getDefinition($connection_service);
            $args = $def->getArguments();
            $args[0]['driverClass'] = 'Blogger\BlogBundle\UserDependentMySqlDriver';
            $args[0]['driverOptions'][] = array(new Reference('security.context'));
            $def->replaceArgument(0, $args[0]);
        }
   }
}

Mã lớp trình điều khiển như sau:

# src/Blogger/BlogBundle/UserDependentMySqlDriver.php

use Doctrine\DBAL\Driver\PDOMySql\Driver;

class UserDependentMySqlDriver extends Driver
{    
    public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
    {
        $dbname = .....  //store database name in variable
        $params['dbname'] = $dbname;
        return parent::connect($params, $username, $password, array());
    }
}

Đoạn mã trên đã được thêm vào dự án của tôi và tôi giả định rằng đây là công việc thực tế dành cho vấn đề của tôi.

Nhưng bây giờ tôi gặp lỗi sau:

ServiceCircularReferenceException: Đã phát hiện tham chiếu vòng tròn cho dịch vụ "security.context", đường dẫn: "profiler_listener -> profiler -> security.context -> security.authentication.manager -> fos_user.user_provider.username_email -> fos_user.user_manager -> doct.orm. dynamic_manager_entity_manager -> doct.dbal.dynamic_conn_connection ".

Làm cách nào, tôi có thể làm cho mã của mình hoạt động được không? Tôi cá rằng tôi đang làm sai điều gì đó ở đây và tôi sẽ đánh giá cao bất kỳ gợi ý và sự giúp đỡ nào.

17 hữu ích 5 bình luận 17k xem chia sẻ
Hồ Đức Thịnh
· 14:34 07/11/2013
14:34:06 07/11/2013

Ở đây, bạn cần thực hiện logic của riêng bạn, trong công việc kinh doanh của riêng bạn.

Hãy xem tài liệu của Doctrine về "cách tạo trình quản lý thực thể".

Sau đó, tạo một dịch vụ với API rõ ràng:

$this->get('em_factory')->getManager('name-of-my-client'); // returns an EntityManager

Bạn không thể làm điều đó với DoctrineBundle mặc định, nó không thể sử dụng được cho các tính năng động.

class EmFactory
{
    public function getManager($name)
    {
        // you can get those values:
        // - autoguess, based on name
        // - injection through constructor
        // - other database connection
        // just create constructor and inject what you need
        $params = array('username' => $name, 'password' => $name, ....);

        // get an EM up and running
        // see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html#obtaining-the-entitymanager

        return $em;
    }
}

Và khai báo như một dịch vụ.

5 hữu ích 1 bình luận chia sẻ
Vũ Ánh Trang
· 09:37 11/08/2013
09:37:40 11/08/2013

Tôi muốn đề xuất giải pháp khác cho vấn đề ban đầu của bạn. Bạn có thể sử dụng PhpFileLoader để xác định động các tham số cho config.yml của mình.

  1. Trích xuất các thông số kết nối cơ sở dữ liệu chính của bạn thành tệp riêng biệt:

    # src/Blogger/BlogBundle/Resources/config/parameters.yml
    
    parameters:
        main_db_name:           maindb
        main_db_user:           root
        main_db_password:       null
        main_db_host:           localhost
    
  2. Tạo tập lệnh PHP mới (giả sử DynamicParametersLoader.php) sẽ đưa các tham số mới vào vùng chứa ứng dụng. Tôi nghĩ rằng bạn không thể sử dụng ứng dụng symfony của mình trong script này, nhưng bạn có thể đọc thông tin đăng nhập db chính từ biến $ container. Như sau:

    # src/Blogger/BlogBundle/DependecyInjection/DynamicParametersLoader.php
    <?php
    
    $mainDbName = $container->getParameter('main_db_name'); 
    $mainDbUser = $container->getParameter('main_db_user');
    $mainDbPassword = $container->getParameter('main_db_password');
    $mainDbHost = $container->getParameter('main_db_host');
    
    # whatever code to query your main database for dynamic DB credentials. You cannot use your symfony2 app services here, so it ought to be plain PHP.
    ...
    
    # creating new parameters in container
    $container->setParameter('dynamic_db_name', $dbName);
    $container->setParameter('dynamic_db_user', $dbUser);
    $container->setParameter('dynamic_db_password', $dbPass);
    
  3. Bây giờ bạn cần cho Symfony biết về tập lệnh của bạn và tệp tham số mới.yml:

    # config.yml
    imports:
        - { resource: parameters.yml }
        - { resource: ../../DependencyInjection/DynamicParametersLoader.php }
    
  4. Tại bước này, bạn có thể thoải mái sử dụng các tham số đã được đưa vào trong cấu hình của mình:

    # config.yml
    ...
            dynamic_conn:
                dbname:           %dynamic_db_name%
                user:             %dynamic_db_user%
                password:         %dynamic_db_password%
    ...
    
3 hữu ích 3 bình luận chia sẻ
Trịnh Nguyên Hồng
· 10:12 05/07/2014
10:12:46 05/07/2014

Có một giải pháp rất tốt bằng cách sử dụng trình nghe sự kiện được đăng ở đây:

Symfony2, Kết nối DB động / Ghi đè sớm Dịch vụ Doctrine

1 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ php database symfony doctrine-orm , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm