Importing tables from external database in Symfony

2019-01-21 06:13发布

问题:

I have a Symfony2 project with its own database, and now I want to connect to another database (another project) so I can modify some tables.

I created the new connection in config_dev.yml

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver:   pdo_mysql
                host:     localhost
                dbname:   database1
                user:     root
                password: 
            buv:
                driver:   pdo_mysql
                host:     localhost
                dbname:   database2
                user:     root
                password:

I tried to import the schema with the following command:

$ php app/console doctrine:mapping:import --em=buv MyBundle yml

[Doctrine\DBAL\Schema\SchemaException] Index '' does not exist on table 'old_table'

But some of the tables in database2 have no PKs! And the full import dosn't work. But I only want to import two tables, so I tried:

$ php app/console doctrine:mapping:import --em=buv --filter="tablename" MyBundle yml

But I'm getting the same error, seems that --filter isn't working.

The documentation in the console command doctrine:mapping:import only says to put the entity name in the filter option. But I don't have an entity yet.

回答1:

If I get you correctly, you want to import your existing database?

What I do is:

php app/console doctrine:mapping:convert xml ./src/App/MyBundle/Resources/config/doctrine/metadata/orm --from-database --force

Then do a selective convert to annotation:

php app/console doctrine:mapping:import AppMyBundle annotation --filter="users_table"

If you wanted to yml, change annotation to yml.

warning: when you import to annotation or yml, it will delete your current entity file.



回答2:

It is a requirement for Doctrine to have an identifier/primary key. Take a look at this page: http://www.doctrine-project.org/docs/orm/2.0/en/reference/basic-mapping.html#identifiers-primary-keys

But there is a way to generate mappings and entities from tables that do not have a primary key. A table with no primary key is an unusual and bad database design but such a scenario exists in case of legacy databases.

Solution:
Note: All references below refer to Doctrine 2.0
1. Find the file DatabaseDriver.php (in Doctrine/ORM/Mapping/Driver/DatabaseDriver.php)
2. Find the method reverseEngineerMappingFromDatabase. Modify the code as stated below.
The original code is:

private function reverseEngineerMappingFromDatabase()
    {
        if ($this->tables !== null) {
            return;
        }

        $tables = array();

        foreach ($this->_sm->listTableNames() as $tableName) {
            $tables[$tableName] = $this->_sm->listTableDetails($tableName);
        }

        $this->tables = $this->manyToManyTables = $this->classToTableNames = array();
        foreach ($tables as $tableName => $table) {
            /* @var $table \Doctrine\DBAL\Schema\Table */
            if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
                $foreignKeys = $table->getForeignKeys();
            } else {
                $foreignKeys = array();
            }

            $allForeignKeyColumns = array();
            foreach ($foreignKeys as $foreignKey) {
                $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
            }

            if ( ! $table->hasPrimaryKey()) {
                throw new MappingException(
                    "Table " . $table->getName() . " has no primary key. Doctrine does not ".
                    "support reverse engineering from tables that don't have a primary key."
                );
            }

            $pkColumns = $table->getPrimaryKey()->getColumns();
            sort($pkColumns);
            sort($allForeignKeyColumns);

            if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) {
                $this->manyToManyTables[$tableName] = $table;
            } else {
                // lower-casing is necessary because of Oracle Uppercase Tablenames,
                // assumption is lower-case + underscore separated.
                $className = $this->getClassNameForTable($tableName);
                $this->tables[$tableName] = $table;
                $this->classToTableNames[$className] = $tableName;
            }
        }
    }


The modified code is:

private function reverseEngineerMappingFromDatabase()
    {
        if ($this->tables !== null) {
            return;
        }

        $tables = array();

        foreach ($this->_sm->listTableNames() as $tableName) {
            $tables[$tableName] = $this->_sm->listTableDetails($tableName);
        }

        $this->tables = $this->manyToManyTables = $this->classToTableNames = array();
        foreach ($tables as $tableName => $table) {
            /* @var $table \Doctrine\DBAL\Schema\Table */
            if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
                $foreignKeys = $table->getForeignKeys();
            } else {
                $foreignKeys = array();
            }

            $allForeignKeyColumns = array();
            foreach ($foreignKeys as $foreignKey) {
                $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
            }

            $pkColumns=array();
            if ($table->hasPrimaryKey()) {
                $pkColumns = $table->getPrimaryKey()->getColumns();
                sort($pkColumns);
            }

            sort($allForeignKeyColumns);

            if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) {
                $this->manyToManyTables[$tableName] = $table;
            } else {
                // lower-casing is necessary because of Oracle Uppercase Tablenames,
                // assumption is lower-case + underscore separated.
                $className = $this->getClassNameForTable($tableName);
                $this->tables[$tableName] = $table;
                $this->classToTableNames[$className] = $tableName;
            }
        }
    }


3. Find the method loadMetadataForClass in the same file. Modify the code as stated below.
Find the code stated below:

try {
   $primaryKeyColumns = $this->tables[$tableName]->getPrimaryKey()->getColumns();
} catch(SchemaException $e) {
    $primaryKeyColumns = array();
}


Modify it like this:

try {
     $primaryKeyColumns = ($this->tables[$tableName]->hasPrimaryKey())?$this->tables[$tableName]->getPrimaryKey()->getColumns():array();
} catch(SchemaException $e) {
     $primaryKeyColumns = array();
}



The above solution creates mappings(xml/yml/annotation) even for tables that don't have a primary key.



回答3:

I've successfully imported some database entities by adding a schema_filter in the doctrine dbal config (~/app/config/config.yml)

# Doctrine Configuration
doctrine:
    dbal:
        driver:   %database_driver%
        host:     %database_host%
        port:     %database_port%
        dbname:   %database_name%
        user:     %database_user%
        password: %database_password%
        charset:  UTF8
        schema_filter: /^users_table/

app/console doctrine:mapping:import --force MyBundle yml

Then revert config.yml.



回答4:

I created a solution based on all comments that simplifies the code

on class namespace Doctrine\ORM\Mapping\Driver; DatabaseDriver.php

On line 277, change:

if (!$table->hasPrimaryKey()) {
      // comment this Throw exception
      // throw new MappingException(
      // “Table “ . $table->getName() . “ has no primary key.
      // Doctrine does not “.
      // “support reverse engineering from tables that don’t
      // have a primary key.”
      // );
} else {
     $pkColumns = $table->getPrimaryKey()->getColumns();
}

And, on line 488, add:

if( $table->hasPrimaryKey() ) //add this if to avoid fatalError
 return $table->getPrimaryKey()->getColumns();

To avoid any future problems, after mapping your database, return the settings to avoid any problems later. Good luck!



回答5:

Note that --filter in your command should be populated with the Entity Class name and not the Table name. If the entity does not yet exists, the Entity Class name must compliment your table name. So if your table is user_table, the filter value would be UserTable.

And then to work around that your DB has some tables that Doctrine cannot handle, you should whitelist the tables you do want allow Doctrine to manage. You can do this in your config file like, so:

doctrine:
    dbal:
        # ... 
        schema_filter: /^(users_table|emails)$/

alternatively you can specify this in your cli-config.php file.

/** @var Doctrine\ORM\Configuration $config */
$config->setFilterSchemaAssetsExpression('/^(users_table|email)$/');


回答6:

You have to update the getTablePrimaryKeys function to:

private function getTablePrimaryKeys(Table $table)
{
    try {       
        $primaryKeyColumns = ($this->tables[$table->getName()]->hasPrimaryKey())?$this->tables[$table->getName()]->getPrimaryKey()->getColumns():array();
    } catch(SchemaException $e) {
        $primaryKeyColumns = array();
    }

    return array();
}


回答7:

php bin/console doctrine:mapping:convert xml ./src/NameBundle/Resources/doctrine/metadata/orm

php bin/console doctrine:mapping:import NameBundle yml

php bin/console doctrine:generate:entities NameBundle


回答8:

At the DatabaseDriver.php file reverseEngineerMappingFromDatabase function you can change

throw new MappingException("Table " . $table->getName() . " has no primary key. Doctrine does not "."support reverse engineering from tables that don't have a primary key.");

with

if(! $table->hasColumn('id')){
   $table->addColumn('id', 'integer', array('autoincrement' => true));
}
   $table->setPrimaryKey(array('id'));