Programmatically modify table's schema name in Doctrine2?

后端 未结 2 537
轻奢々
轻奢々 2020-12-14 05:03

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 interf

相关标签:
2条回答
  • 2020-12-14 05:30

    You can dynamically adjust the table names ( and mappings ) by hooking into the doctrine event-system with listeners/subscribers.

    i.e. "loadClassMetadata" is one of doctrine's events you can create a listener/subscriber for as described in the cookbook article How to Register Event Listeners and Subscribers.

    Example

    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 extends ClassMetadataInfo and provides a public variable "table" ( containing the mapping information provided by your annotations or yml ) which you can modify !

    The public table variable is an array with the following entries:

    • name =>
    • schema =>
    • indexes => array
    • uniqueConstraints => array

    You can dynamically register event-listeners/subscribers in your controller prior to saving/updating.

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

    Furthermore you can introduce multiple database connections/names and access them in your application.

    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
    

    Then get different entity managers using ...

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

    or repositories

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

    ... or add connections dynamically

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

    Read more about the topic in the cookbook chapter How to work with Multiple Entity Managers and Connections.

    0 讨论(0)
  • 2020-12-14 05:47

    You can make use of the undocumented 'options' parameter in @Table and pass a variable to the listener which you can use to decide which database to use.

    'options' is used to include DBMS specific options in the SQL when generating the schema. For example: "charset"="utf8mb4", "engine"="InnoDB", etc

    When choosing a variable name, make sure it is not a valid option supported by your DBMS. In the below example I have chosen 'schema':

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

    After that I have created a parameter array in services.yml called 'schema', which will be passed to the service via the ParameterBag. The values are either edited in the yml, or pulled from the environmental variables.

    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
    

    Now my listener looks like this:

    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']]]);
            }
        } 
    }
    

    This results in all entities with the table option 'schema' will be set to use the database that is defined in the parameters in the service.yml or in environmental variables.

    This means you can choose to set the schema, or leave it as default, without editing the entities or any other class.

    0 讨论(0)
提交回复
热议问题