Importing tables from external database in Symfony2 with doctrine
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 tri开发者_JS百科ed 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.
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.
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.
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.
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!
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)$/');
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();
}
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'));
Try at least temporary change your doctrine config
doctrine:
dbal:
schema_filter: ~^(?!table1|table2)~
And run:
php bin/console doctrine:mapping:convert --force --from-database --filter="CamelCasedTableName" --namespace="App\Entity\\" annotation .
Spent a lot of hours finding this solution. Stange but upper answer by Courtney Miles didn't work for me. And this one works fine at least on symfony 4.4
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
精彩评论