dongzhou1865 2017-05-02 13:50
浏览 59

如何将Doctrine 2的控制台工具与动态生成的EntityManagers一起使用?

We are using Doctrine 2 in our app, but due to our infrastructure, we do not have a static configuration for database connections. Instead, we have a collection of singletons in a service provider for each database we need to connect to, and we select a random database host for then when we connect.

Unfortunately, we are seeing some performance degradation in Doctrine's getRepository() function. I believe the issue is that Doctrine needs to generate its proxy classes at runtime (even in production) because we cannot figure out how to configure the CLI tools in order to create them at build time.

We are using the Laravel framework for the application.

Here's an example of our Laravel service provider which makes the repositories available for dependency injection.

<?php
use App\Database\Doctrine\Manager as DoctrineManager;
use Proprietary\ConnectionFactory;
use App\Database\Entities;
use App\Database\Repositories;
use App\Database\Constants\EntityConstants;

class DoctrineServiceProvider extends ServiceProvider
{
    // Create a singleton for the Doctrine Manager. This class will handle entity manager generation.
    $this->app->singleton(DoctrineManager::class, function ($app)
    {
        return new DoctrineManager(
            $app->make(ConnectionFactory::class),
            [
                EntityConstants::ENTITY_CLASS_DATABASE1 => [app_path('Database/Entities/Database1')],
                EntityConstants::ENTITY_CLASS_DATABASE2 => [app_path('Database/Entities/Database2')],
            ],
            config('app.debug'),
            $this->app->make(LoggerInterface::class)
        );
    });

    // Register the first repository
    $this->app->singleton(Repositories\Database1\RepositoryA1::class, function ($app)
    {
        return $app[DoctrineManager::class]
            ->getEntityManager(EntityConstants::ENTITY_CLASS_DATABASE1)
            ->getRepository(Entities\Database1\RepositoryA1::class);
    });

    // Register the second repository
    $this->app->singleton(Repositories\Database1\RepositoryA2::class, function ($app)
    {
        return $app[DoctrineManager::class]
            ->getEntityManager(EntityConstants::ENTITY_CLASS_DATABASE1)
            ->getRepository(Entities\Database1\RepositoryA2::class);
    });

    // Register a repository for the second database
    $this->app->singleton(Repositories\Database2\RepositoryB1::class, function ($app)
    {
        return $app[DoctrineManager::class]
            ->getEntityManager(EntityConstants::ENTITY_CLASS_DATABASE2)
            ->getRepository(Entities\Database2\RepositoryB1::class);
    });
}

Here's the class that generates EntityManagers for Doctrine:

<?php
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Connections\MasterSlaveConnection;
use Proprietary\ConnectionFactory;

class Manager
{
    private $c_factory;
    private $paths;
    private $connections = [];
    private $entity_managers = [];

    public function __construct(
        ConnectionFactory $cf,
        array $paths
    )
    {
        $this->c_factory = $cf;
        $this->paths = $paths;
    }

    public function getConnection($name, $partition = false, $region = false)
    {
        // Get a list of servers for this database and format them for use with Doctrine
        $servers = self::formatServers($name, $this->c_factory->getServers($name, true, $partition, $region));

        // Generate a connection for the entity manager using the servers we have.
        $connection = DriverManager::getConnection(
            array_merge([
                'wrapperClass' => MasterSlaveConnection::class,
                'driver' => 'pdo_mysql',
            ], $servers)
        );

        return $connection;
    }

    public function getEntityManager($name, $partition = false, $region = false)
    {
        // Should these things be cached somehow at build time?
        $config = Setup::createAnnotationMetadataConfiguration($this->paths[$name], false);
        $config->setAutoGenerateProxyClasses(true);

        // Set up the connection
        $connection = $this->getConnection($name, $partition, $region);
        $entity_manager = EntityManager::create($connection, $config);
        return $entity_manager;
    }

    // Converts servers from a format provided by our proprietary code to a format Doctrine can use.
    private static function formatServers($db_name, array $servers)
    {
        $doctrine_servers = [
            'slaves' => [],
        ];

        foreach ($servers as $server)
        {
            // Format for Doctrine
            $server = [
                'user' => $server['username'],
                'password' => $server['password'],
                'host' => $server['hostname'],
                'dbname' => $db_name,
                'charset' => 'utf8',
            ];

            // Masters can also be used as slaves.
            $doctrine_servers['slaves'][] = $server;

            // Servers are ordered by which is closest, and Doctrine only allows a
            // single master, so if we already set one, don't overwrite it.
            if ($server['is_master'] && !isset($doctrine_servers['master']))
            {
                $doctrine_servers['master'] = $server;
            }
        }

        return $doctrine_servers;
    }
}

Our service classes use dependency injection to get the repository singletons defined in the service provider. When we use the singletons for the first time, Doctrine will use the entity class defined in the service provider and get the connection associated with the repository.

Is there any way we can enable the CLI tools with this configuration? Are there any other ways that we can optimize this for use in production?

Thanks.

  • 写回答

1条回答 默认 最新

  • duanjiushu5063 2017-10-31 15:15
    关注

    I was able to solve the problem thanks to a suggestion from the Doctrine IRC channel. Since the CLI tools can only handle a single database, I created a doctrine-cli directory containing a base-config.php file and a subdirectory for each of the databases we use.

    Here's an example file structure:

    doctrine-cli/
     |- database1/
     |   |- cli-config.php
     |- database2/
     |   |- cli-config.php
     |- base-config.php
    

    The base-config.php file looks like this:

    <?php
    use Symfony\Component\Console\Helper\HelperSet;
    use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
    use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
    use App\Database\Doctrine\Manager as DoctrineManager;
    use Proprietary\ConnectionFactory;
    require __DIR__ . '/../bootstrap/autoload.php';
    
    class DoctrineCLIBaseConfig
    {
        private $helper_set;
    
        public function __construct($entity_constant, $entity_namespace)
        {
            $app = require_once __DIR__ . '/../bootstrap/app.php';
    
            // Proprietary factory for getting our databse details
            $connection_factory = new ConnectionFactory(...);
    
            // Our class that parses the results from above and handles our Doctrine connection.
            $manager = new DoctrineManager(
                $connection_factory,
                [$entity_constant => [app_path('Database/Entities/' . $entity_namespace)]],
                false,
                null,
                null
            );
    
            $em = $manager->getEntityManager($entity_constant);
    
            $this->helper_set = new HelperSet([
                'db' => new ConnectionHelper($em->getConnection()),
                'em' => new EntityManagerHelper($em),
            ]);
        }
    
        public function getHelperSet()
        {
            return $this->helper_set;
        }
    }
    

    Here's an example cli-config.php from the database directory:

    <?php
    use App\Database\Constants\EntityConstants;
    require __DIR__ . "/../base-config.php";
    
    $config = new DoctrineCLIBaseConfig(
        EntityConstants::ENTITY_CLASS_DATABASE1,
        "database1"
    );
    
    return $config->getHelperSet();
    

    Now, I'm able to cycle through each of the directories and run commands like so:

    php ../../vendor/bin/doctrine orm:generate-proxies
    

    For our build process, I wrote a simple shell script that cycles through the directories and runs the orm:generate-proxies command.

    评论

报告相同问题?

悬赏问题

  • ¥15 微信小程序协议怎么写
  • ¥15 c语言怎么用printf(“\b \b”)与getch()实现黑框里写入与删除?
  • ¥20 怎么用dlib库的算法识别小麦病虫害
  • ¥15 华为ensp模拟器中S5700交换机在配置过程中老是反复重启
  • ¥15 java写代码遇到问题,求帮助
  • ¥15 uniapp uview http 如何实现统一的请求异常信息提示?
  • ¥15 有了解d3和topogram.js库的吗?有偿请教
  • ¥100 任意维数的K均值聚类
  • ¥15 stamps做sbas-insar,时序沉降图怎么画
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看