. */ namespace Doctrine\ORM\Mapping\Driver; use Doctrine\Common\Cache\ArrayCache, Doctrine\Common\Annotations\AnnotationReader, Doctrine\DBAL\Schema\AbstractSchemaManager, Doctrine\DBAL\Schema\SchemaException, Doctrine\ORM\Mapping\ClassMetadataInfo, Doctrine\ORM\Mapping\MappingException, Doctrine\Common\Util\Inflector, Doctrine\Common\Persistence\Mapping\Driver; /** * The DatabaseDriver reverse engineers the mapping metadata from a database. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.org * @since 2.0 * @author Guilherme Blanco * @author Jonathan Wage * @author Benjamin Eberlei */ class DatabaseDriver implements Driver { /** * @var AbstractSchemaManager */ private $_sm; /** * @var array */ private $tables = null; private $classToTableNames = array(); /** * @var array */ private $manyToManyTables = array(); /** * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading * docblock annotations. * * @param AnnotationReader $reader The AnnotationReader to use. */ public function __construct(AbstractSchemaManager $schemaManager) { $this->_sm = $schemaManager; } private function reverseEngineerMappingFromDatabase() { if ($this->tables !== null) { return; } foreach ($this->_sm->listTableNames() as $tableName) { $tables[$tableName] = $this->_sm->listTableDetails($tableName); } $this->tables = array(); foreach ($tables AS $tableName => $table) { /* @var $table 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 = $table->getPrimaryKey()->getColumns(); sort($pkColumns); sort($allForeignKeyColumns); if ($pkColumns == $allForeignKeyColumns) { if (count($table->getForeignKeys()) > 2) { throw new \InvalidArgumentException("ManyToMany table '" . $tableName . "' with more or less than two foreign keys are not supported by the Database Reverese Engineering Driver."); } $this->manyToManyTables[$tableName] = $table; } else { // lower-casing is necessary because of Oracle Uppercase Tablenames, // assumption is lower-case + underscore separated. $className = Inflector::classify(strtolower($tableName)); $this->tables[$tableName] = $table; $this->classToTableNames[$className] = $tableName; } } } /** * {@inheritdoc} */ public function loadMetadataForClass($className, \Doctrine\Common\Persistence\Mapping\ClassMetadata $metadata) { $this->reverseEngineerMappingFromDatabase(); if (!isset($this->classToTableNames[$className])) { throw new \InvalidArgumentException("Unknown class " . $className); } $tableName = $this->classToTableNames[$className]; $metadata->name = $className; $metadata->table['name'] = $tableName; $columns = $this->tables[$tableName]->getColumns(); $indexes = $this->tables[$tableName]->getIndexes(); try { $primaryKeyColumns = $this->tables[$tableName]->getPrimaryKey()->getColumns(); } catch(SchemaException $e) { $primaryKeyColumns = array(); } if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) { $foreignKeys = $this->tables[$tableName]->getForeignKeys(); } else { $foreignKeys = array(); } $allForeignKeyColumns = array(); foreach ($foreignKeys AS $foreignKey) { $allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns()); } $ids = array(); $fieldMappings = array(); foreach ($columns as $column) { $fieldMapping = array(); if ($primaryKeyColumns && in_array($column->getName(), $primaryKeyColumns)) { $fieldMapping['id'] = true; } else if (in_array($column->getName(), $allForeignKeyColumns)) { continue; } $fieldMapping['fieldName'] = Inflector::camelize(strtolower($column->getName())); $fieldMapping['columnName'] = $column->getName(); $fieldMapping['type'] = strtolower((string) $column->getType()); if ($column->getType() instanceof \Doctrine\DBAL\Types\StringType) { $fieldMapping['length'] = $column->getLength(); $fieldMapping['fixed'] = $column->getFixed(); } else if ($column->getType() instanceof \Doctrine\DBAL\Types\IntegerType) { $fieldMapping['unsigned'] = $column->getUnsigned(); } $fieldMapping['nullable'] = $column->getNotNull() ? false : true; if (isset($fieldMapping['id'])) { $ids[] = $fieldMapping; } else { $fieldMappings[] = $fieldMapping; } } if ($ids) { if (count($ids) == 1) { $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); } foreach ($ids as $id) { $metadata->mapField($id); } } foreach ($fieldMappings as $fieldMapping) { $metadata->mapField($fieldMapping); } foreach ($this->manyToManyTables AS $manyTable) { foreach ($manyTable->getForeignKeys() AS $foreignKey) { if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) { $myFk = $foreignKey; foreach ($manyTable->getForeignKeys() AS $foreignKey) { if ($foreignKey != $myFk) { $otherFk = $foreignKey; break; } } $localColumn = current($myFk->getColumns()); $associationMapping = array(); $associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower(current($otherFk->getColumns())))); $associationMapping['targetEntity'] = Inflector::classify(strtolower($otherFk->getForeignTableName())); if (current($manyTable->getColumns())->getName() == $localColumn) { $associationMapping['inversedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns())))); $associationMapping['joinTable'] = array( 'name' => strtolower($manyTable->getName()), 'joinColumns' => array(), 'inverseJoinColumns' => array(), ); $fkCols = $myFk->getForeignColumns(); $cols = $myFk->getColumns(); for ($i = 0; $i < count($cols); $i++) { $associationMapping['joinTable']['joinColumns'][] = array( 'name' => $cols[$i], 'referencedColumnName' => $fkCols[$i], ); } $fkCols = $otherFk->getForeignColumns(); $cols = $otherFk->getColumns(); for ($i = 0; $i < count($cols); $i++) { $associationMapping['joinTable']['inverseJoinColumns'][] = array( 'name' => $cols[$i], 'referencedColumnName' => $fkCols[$i], ); } } else { $associationMapping['mappedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns())))); } $metadata->mapManyToMany($associationMapping); break; } } } foreach ($foreignKeys as $foreignKey) { $foreignTable = $foreignKey->getForeignTableName(); $cols = $foreignKey->getColumns(); $fkCols = $foreignKey->getForeignColumns(); $localColumn = current($cols); $associationMapping = array(); $associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower($localColumn))); $associationMapping['targetEntity'] = Inflector::classify($foreignTable); for ($i = 0; $i < count($cols); $i++) { $associationMapping['joinColumns'][] = array( 'name' => $cols[$i], 'referencedColumnName' => $fkCols[$i], ); } $metadata->mapManyToOne($associationMapping); } } /** * {@inheritdoc} */ public function isTransient($className) { return true; } /** * Return all the class names supported by this driver. * * IMPORTANT: This method must return an array of class not tables names. * * @return array */ public function getAllClassNames() { $this->reverseEngineerMappingFromDatabase(); return array_keys($this->classToTableNames); } }