5

I'd like to modify a table's schema/DB name at runtime as is possible with the table name, but the ClassMetadataInfo class does not appear to expose an interface to get/set this property.

I can make do with modifying table names at runtime if absolutely necessary, but it is not an ideal solution due to the amount of tables we would then have to store in a single schema/DB.

Is there a way to achieve what I'd like to do? Thanks in advance.

Note: I need to be able to provide a fully qualified table name using a schema placeholder in my annotation-based entity mapping (like __schema_placeholder__.table_name, for cross-database joins). At runtime I would then like to dynamically remap the entity from __schema_placeholder__.table_name => real_schema_name.table_name.

4

2 回答 2

31

您可以通过使用侦听器/订阅者连接到学说事件系统来动态调整表名(和映射)。

即“loadClassMetadata”是您可以创建监听器/订阅者的学说事件之一,如食谱文章如何注册事件监听器和订阅者中所述。

例子

config.yml

services:
    mapping.listener:
        class: Acme\YourBundle\EventListener\MappingListener
        tags:
            - { name: doctrine.event_listener, event: loadClassMetadata }

MappingListener

use Doctrine\ORM\Event\LoadClassMetadataEventArgs;

class MappingListener
{
    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
    {
        $classMetadata = $eventArgs->getClassMetadata();
        $table = $classMetadata->table;

        $oldName = $table['name'];      // ... or $classMetaData->getTableName()

        // your logic here ...

        $table['name'] = 'new_table_name';

        $classMetadata->setPrimaryTable($table);

        // ... or add a field-mapping like this

        $fieldMapping = array(
          'fieldName' => 'about',
          'type' => 'string',
           'length' => 255
        );
        $classMetadata->mapField($fieldMapping);

ClassMetadata扩展了ClassMetadataInfo并提供了一个公共变量“”(包含您的注释或 yml 提供的映射信息),您可以对其进行修改!

公共表变量是一个包含以下条目的数组:

  • 名字 =>
  • 架构 =>
  • 索引 => 数组
  • 唯一约束 => 数组

您可以在保存/更新之前在控制器中动态注册事件侦听器/订阅者。

$mappingListener = new MappingListener();

// ... maybe even modify the listener using reflection

$evm = $this->get('doctrine')->getManager()->->getEventManager();
$evm->addEventListener('loadClassMetadata', $mappingListener);

此外,您可以引入多个数据库连接/名称并在您的应用程序中访问它们。

应用程序/配置/config.yml

doctrine:
    dbal:
        default_connection:   default
        connections:
            default:
                driver:   "%database_driver%"
                host:     "%database_host%"
                port:     "%database_port%"
                dbname:   "%database_name%"
                user:     "%database_user%"
                password: "%database_password%"
                charset:  UTF8
            customer:
                driver:   "%database_driver2%"
                host:     "%database_host2%"
                port:     "%database_port2%"
                dbname:   "%database_name2%"
                user:     "%database_user2%"
                password: "%database_password2%"
                charset:  UTF8

然后使用...获取不同的实体管理器

  $em = $this->get('doctrine')->getManager('default');
  $em2 = $this->get('doctrine')->getManager('customer');

或存储库

$customers = $this->get('doctrine')
    ->getRepository('AcmeCustomerBundle:Customer', 'customer')
    ->findAll()
;

...或动态添加连接

$this->get('doctrine')
  ->connection('mysql://username:password@localhost/test', 'dynamic_connection');

在食谱章节如何使用多个实体管理器和连接中阅读有关该主题的更多信息。

于 2013-06-16T18:54:11.123 回答
2

您可以使用@Table 中未记录的“选项”参数并将变量传递给侦听器,您可以使用该变量来决定使用哪个数据库。

'options' 用于在生成模式时在 SQL 中包含特定于 DBMS 的选项。例如:“charset”="utf8mb4"、"engine"="InnoDB" 等

选择变量名称时,请确保它不是您的 DBMS 支持的有效选项。在下面的示例中,我选择了“模式”:

@ORM\Table(name="person", options={"schema"="readonly"})

之后,我在 services.yml 中创建了一个名为“schema”的参数数组,它将通过 ParameterBag 传递给服务。这些值要么在 yml 中编辑,要么从环境变量中提取。

parameters:
    env(SCHEMA_READONLY): 'readonly_database' #default if env variable has not been set
    schema:
        readonly: 'readonly_database'
        client: 'client_database'
        runtime: '%env(SCHEMA_READONLY)%' #pulled from env variables

现在我的听众看起来像这样:

class MappingListener
{
    protected $parameterBag;

    public function __construct(ParameterBagInterface $parameterBag) {
        $this->parameterBag = $parameterBag;
    }

    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) {
        $schemaParamaters = $this->parameterBag->has('schema') ? $this->parameterBag->get('schema') : array();

        $classMetadata = $eventArgs->getClassMetadata();

        if(isset($classMetadata->table['options']['schema']) && isset($schemaParamaters[$classMetadata->table['options']['schema']])) {
            $classMetadata->setPrimaryTable(['schema' => $schemaParamaters[$classMetadata->table['options']['schema']]]);
        }
    } 
}

这将导致所有具有表选项“模式”的实体都将设置为使用在 service.yml 中的参数或环境变量中定义的数据库。

这意味着您可以选择设置架构,或将其保留为默认值,而无需编辑实体或任何其他类。

于 2019-06-04T12:46:17.880 回答