【问题标题】:Programmatically modify table's schema name in Doctrine2?在 Doctrine2 中以编程方式修改表的模式名称?
【发布时间】:2013-06-16 15:30:56
【问题描述】:

我想在运行时修改表的架构/数据库名称as is possible with the table name,但ClassMetadataInfo 类似乎没有公开获取/设置此属性的接口。

如果绝对必要,我可以在运行时修改表名,但这不是一个理想的解决方案,因为我们必须将大量表存储在单个模式/数据库中。

有没有办法实现我想做的事情?提前致谢。

注意:我需要能够在基于注释的实体映射中使用模式占位符提供完全限定的表名(例如 __schema_placeholder__.table_name,用于跨数据库连接)。然后,在运行时,我想从__schema_placeholder__.table_name => real_schema_name.table_name 动态重新映射实体。

【问题讨论】:

    标签: php symfony doctrine-orm


    【解决方案1】:

    您可以通过将doctrine event-system 与侦听器/订阅者挂钩来动态调整表名(和映射)。

    即“loadClassMetadata”是doctrine's events 之一,您可以按照食谱文章How to Register Event Listeners and Subscribers 中的说明为其创建侦听器/订阅者。

    示例

    config.yml

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

    映射监听器

    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 并提供一个公共变量“table”(包含您的注释或 yml 提供的映射信息),您可以修改它!

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

    • 姓名 =>
    • 架构 =>
    • 索引 => 数组
    • uniqueConstraints => 数组

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

    $mappingListener = new MappingListener();
    
    // ... maybe even modify the listener using reflection
    
    $evm = $this->get('doctrine')->getManager()->->getEventManager();
    $evm->addEventListener('loadClassMetadata', $mappingListener);
    

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

    app/config/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()
    ;
    

    ... 或 add connections 动态

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

    在食谱章节How to work with Multiple Entity Managers and Connections中阅读有关该主题的更多信息。

    【讨论】:

    • 谢谢,但这并不能解决我的特殊问题,因为我还需要进行跨数据库连接,因此必须在 Doctrine 中提供完全限定的表名 (schema.table) ORM 映射。我可能会使用 YAML 映射配置中的参数来执行此操作,但我正在使用注释并且不希望转换。我需要能够将实体映射到__schema_placeholder__.tableName,然后通过用实际名称替换此架构占位符来动态更改映射。
    • 我已经用新信息扩展了我的答案,这应该可以帮助您解决问题。请至少通过投票来显示一些信誉......如果您需要更多信息,可以接受或发表评论。
    • 这看起来正是我所需要的。这就是我在凌晨 3 点阅读“事件”一章所得到的。赞成并接受,谢谢:)
    【解决方案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 或环境变量中的参数中定义的数据库。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-04
      • 2015-02-27
      • 2017-08-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多