如何在动态生成的EntityManagers中使用Doctrine 2的控制台工具?

时间:2017-05-02 13:50:08

标签: php database laravel doctrine-orm

我们在我们的应用程序中使用Doctrine 2,但由于我们的基础结构,我们没有数据库连接的静态配置。相反,我们在服务提供者中为我们需要连接的每个数据库都有一个单例集合,然后在连接时选择随机数据库主机。

不幸的是,我们发现Doctrine的getRepository()函数性能有所下降。我认为问题是Doctrine需要在运行时(甚至在生产中)生成代理类,因为我们无法弄清楚如何配置CLI工具以便在构建时创建它们。

我们正在使用Laravel框架进行应用程序。

这是我们的Laravel服务提供程序的一个示例,它使存储库可用于依赖注入。

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

这是为Doctrine生成EntityManagers的类:

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

我们的服务类使用依赖注入来获取服务提供者中定义的存储库单例。当我们第一次使用单例时,Doctrine将使用服务提供者中定义的实体类,并获得与存储库关联的连接。

我们是否可以通过此配置启用CLI工具?有没有其他方法可以优化它以用于生产?

感谢。

1 个答案:

答案 0 :(得分:0)

由于Doctrine IRC频道的建议,我能够解决问题。由于CLI工具只能处理单个数据库,因此我创建了一个doctrine-cli目录,其中包含base-config.php文件和我们使用的每个数据库的子目录。

这是一个示例文件结构:

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

base-config.php文件如下所示:

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

以下是数据库目录中的示例cli-config.php

<?php
use App\Database\Constants\EntityConstants;
require __DIR__ . "/../base-config.php";

$config = new DoctrineCLIBaseConfig(
    EntityConstants::ENTITY_CLASS_DATABASE1,
    "database1"
);

return $config->getHelperSet();

现在,我可以遍历每个目录并运行如下命令:

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

对于我们的构建过程,我编写了一个简单的shell脚本,它遍历目录并运行orm:generate-proxies命令。