1
0
mirror of synced 2024-12-05 03:06:05 +03:00

[2.0] Work on SchemaTool and DBAL.

This commit is contained in:
romanb 2009-08-21 18:13:22 +00:00
parent 0612e6b0a6
commit b1d34fca1c
21 changed files with 510 additions and 239 deletions

View File

@ -8,15 +8,15 @@
<xs:annotation>
<xs:documentation><![CDATA[
This is the XML Schema for the object/relational
mapping file used by Doctrine.
mapping file used by the Doctrine ORM.
]]></xs:documentation>
</xs:annotation>
<xs:element name="doctrine-mapping">
<xs:complexType>
<xs:sequence>
<xs:element name="mapped-superclass" type="orm:mapped-superclass" minOccurs="0" />
<xs:element name="entity" type="orm:entity" minOccurs="0" />
<xs:element name="mapped-superclass" type="orm:mapped-superclass" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="entity" type="orm:entity" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
@ -52,27 +52,28 @@
<xs:complexType name="lifecycle-callbacks">
<xs:sequence>
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="1" />
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="entity">
<xs:sequence>
<xs:element name="indexes" type="orm:indexes" minOccurs="0"/>
<xs:element name="unique-constraints" type="orm:unique-constraints" minOccurs="0"/>
<xs:element name="discriminator-column" type="orm:discriminator-column" minOccurs="0"/>
<xs:element name="subclasses" type="xs:NMTOKENS" minOccurs="0"/>
<xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/>
<xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" />
<xs:element name="id" type="orm:id" />
<xs:element name="field" type="orm:field" minOccurs="0" />
<xs:element name="one-to-one" type="orm:one-to-one" minOccurs="0"/>
<xs:element name="one-to-many" type="orm:one-to-many" minOccurs="0" />
<xs:element name="many-to-many" type="orm:many-to-many" minOccurs="0" />
<xs:element name="field" type="orm:field" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="one-to-many" type="orm:one-to-many" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="many-to-many" type="orm:many-to-many" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="table" type="xs:NMTOKEN" />
<xs:attribute name="schema" type="xs:NMTOKEN" />
<xs:attribute name="repository-class" type="xs:NMTOKEN"/>
<xs:attribute name="inheritance-type" type="orm:inheritance-type"/>
<xs:attribute name="discriminator-value" type="xs:NMTOKEN"/>
</xs:complexType>
<xs:complexType name="mapped-superclass" >
@ -122,6 +123,39 @@
<xs:attribute name="field-name" type="xs:NMTOKEN" />
<xs:attribute name="length" type="xs:NMTOKEN" />
</xs:complexType>
<xs:complexType name="unique-constraint">
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:NMTOKENS" use="required"/>
</xs:complexType>
<xs:complexType name="unique-constraints">
<xs:sequence>
<xs:element name="unique-constraint" type="orm:unique-constraint" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="index">
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:NMTOKENS" use="required"/>
</xs:complexType>
<xs:complexType name="indexes">
<xs:sequence>
<xs:element name="index" type="orm:index" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="discriminator-mapping">
<xs:attribute name="value" type="xs:NMTOKEN" use="required"/>
<xs:attribute name="class" type="xs:NMTOKEN" use="required"/>
</xs:complexType>
<xs:complexType name="discriminator-map">
<xs:sequence>
<xs:element name="discriminator-mapping" type="orm:discriminator-mapping" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="generator">
<xs:attribute name="strategy" type="orm:generator-strategy" use="required" />
@ -138,13 +172,13 @@
<xs:complexType name="inverse-join-columns">
<xs:sequence>
<xs:element name="join-column" type="orm:join-column" />
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="join-column">
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="referencedColumnName" type="xs:NMTOKEN" use="required" />
<xs:attribute name="referenced-column-name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="true" />
<xs:attribute name="on-delete" type="orm:fk-action" />
@ -153,7 +187,7 @@
<xs:complexType name="join-columns">
<xs:sequence>
<xs:element name="join-column" type="orm:join-column" />
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
@ -171,7 +205,7 @@
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:element name="join-table" type="orm:join-table" />
</xs:sequence>
<xs:attribute name="targetEntity" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:NMTOKEN" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
</xs:complexType>
@ -179,8 +213,8 @@
<xs:sequence>
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
</xs:sequence>
<xs:attribute name="targetEntity" type="xs:NMTOKEN" use="required" />
<xs:attribute name="mappedBy" type="xs:NMTOKEN" />
<xs:attribute name="target-entity" type="xs:NMTOKEN" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
</xs:complexType>
@ -193,8 +227,8 @@
<xs:element name="join-columns" type="orm:join-columns"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="targetEntity" type="xs:NMTOKEN" use="required" />
<xs:attribute name="mappedBy" type="xs:NMTOKEN" />
<xs:attribute name="target-entity" type="xs:NMTOKEN" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
</xs:complexType>

View File

@ -362,14 +362,6 @@ abstract class AbstractPlatform
* must contain a logical expression or an array with logical expressions.
* These expressions will be matched against the first parameter.
*
* Example:
* <code>
* $q = new Doctrine_Query();
* $q->select('u.*')
* ->from('User u')
* ->where($q->expr->in( 'id', array(1,2,3)));
* </code>
*
* @param string $column the value that should be matched against
* @param string|array(string) values that will be matched against $column
* @return string logical expression
@ -390,14 +382,6 @@ abstract class AbstractPlatform
/**
* Returns SQL that checks if a expression is null.
*
* Example:
* <code>
* $q = new Doctrine_Query();
* $q->select('u.*')
* ->from('User u')
* ->where($q->expr->isNull('id'));
* </code>
*
* @param string $expression the expression that should be compared to null
* @return string logical expression
*/
@ -409,14 +393,6 @@ abstract class AbstractPlatform
/**
* Returns SQL that checks if a expression is not null.
*
* Example:
* <code>
* $q = new Doctrine_Query();
* $q->select('u.*')
* ->from('User u')
* ->where($q->expr->isNotNull('id'));
* </code>
*
* @param string $expression the expression that should be compared to null
* @return string logical expression
*/
@ -435,14 +411,6 @@ abstract class AbstractPlatform
* http://www.w3schools.com/sql/sql_between.asp. If you want complete database
* independence you should avoid using between().
*
* Example:
* <code>
* $q = new Doctrine_Query();
* $q->select('u.*')
* ->from('User u')
* ->where($q->expr->between('id', 1, 5));
* </code>
*
* @param string $expression the value to compare to
* @param string $value1 the lower value to compare with
* @param string $value2 the higher value to compare with
@ -504,18 +472,24 @@ abstract class AbstractPlatform
}
/**
* Gets the SQL statement(s) to create a table with the specified name, columns and options
* Gets the SQL statement(s) to create a table with the specified name, columns and constraints
* on this platform.
*
* @param string $table
* @param array $columns
* @param array $options
* @return array
* @param string $table The name of the table.
* @param array $columns The column definitions for the table.
* @param array $options The table constraints.
* @return array The sequence of SQL statements.
*/
public function getCreateTableSql($table, array $columns, array $options = array())
{
$columnListSql = $this->getColumnDeclarationListSql($columns);
if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
foreach ($options['uniqueConstraints'] as $uniqueConstraint) {
$columnListSql .= ', UNIQUE(' . implode(', ', array_values($uniqueConstraint)) . ')';
}
}
if (isset($options['primary']) && ! empty($options['primary'])) {
$columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')';
}

View File

@ -227,7 +227,7 @@ class MySqlPlatform extends AbstractPlatform
$fixed = (isset($field['fixed'])) ? $field['fixed'] : false;
return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
: ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
: ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
}
public function getClobDeclarationSql(array $field)
@ -408,7 +408,7 @@ class MySqlPlatform extends AbstractPlatform
$queryFields = $this->getColumnDeclarationListSql($fields);
// build indexes for all foreign key fields (needed in MySQL!!)
if (isset($options['foreignKeys'])) {
/*if (isset($options['foreignKeys'])) {
foreach ($options['foreignKeys'] as $fk) {
$local = $fk['local'];
$found = false;
@ -433,6 +433,12 @@ class MySqlPlatform extends AbstractPlatform
$options['indexes'][$local] = array('fields' => array($local => array()));
}
}
}*/
if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
foreach ($options['uniqueConstraints'] as $uniqueConstraint) {
$queryFields .= ', UNIQUE(' . implode(', ', array_values($uniqueConstraint)) . ')';
}
}
// add all indexes
@ -471,9 +477,7 @@ class MySqlPlatform extends AbstractPlatform
// get the type of the table
if (isset($options['type'])) {
$type = $options['type'];
}/* else {
$type = $this->getAttribute(Doctrine::ATTR_DEFAULT_TABLE_TYPE);
}*/
}
if ($type) {
$optionStrings[] = 'ENGINE = ' . $type;
@ -589,6 +593,7 @@ class MySqlPlatform extends AbstractPlatform
if ( ! $name) {
throw DoctrineException::updateMe('no valid table name specified');
}
foreach ($changes as $changeName => $change) {
switch ($changeName) {
case 'add':
@ -670,61 +675,6 @@ class MySqlPlatform extends AbstractPlatform
return 'ALTER TABLE ' . $name . ' ' . $query;
}
/**
* Get the stucture of a field into an array
*
* @author Leoncx
* @param string $table name of the table on which the index is to be created
* @param string $name name of the index to be created
* @param array $definition associative array that defines properties of the index to be created.
* Currently, only one property named FIELDS is supported. This property
* is also an associative with the names of the index fields as array
* indexes. Each entry of this array is set to another type of associative
* array that specifies properties of the index that are specific to
* each field.
*
* Currently, only the sorting property is supported. It should be used
* to define the sorting direction of the index. It may be set to either
* ascending or descending.
*
* Not all DBMS support index sorting direction configuration. The DBMS
* drivers of those that do not support it ignore this property. Use the
* function supports() to determine whether the DBMS driver can manage indexes.
*
* Example
* array(
* 'fields' => array(
* 'user_name' => array(
* 'sorting' => 'ASC'
* 'length' => 10
* ),
* 'last_login' => array()
* )
* )
* @throws PDOException
* @return void
* @override
*/
public function getCreateIndexSql($table, $name, array $definition)
{
$table = $table;
$type = '';
if (isset($definition['type'])) {
switch (strtolower($definition['type'])) {
case 'fulltext':
case 'unique':
$type = strtoupper($definition['type']) . ' ';
break;
default:
throw DoctrineException::updateMe('Unknown index type ' . $definition['type']);
}
}
$query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
$query .= ' (' . $this->getIndexFieldDeclarationListSql($definition['fields']) . ')';
return $query;
}
/**
* Obtain DBMS specific SQL code portion needed to declare an integer type
* field to be used in statements like CREATE TABLE.
@ -791,7 +741,7 @@ class MySqlPlatform extends AbstractPlatform
*/
public function getIndexDeclarationSql($name, array $definition)
{
$type = '';
$type = '';
if (isset($definition['type'])) {
switch (strtolower($definition['type'])) {
case 'fulltext':
@ -818,7 +768,6 @@ class MySqlPlatform extends AbstractPlatform
}
/**
* getIndexFieldDeclarationList
* Obtain DBMS specific SQL code portion needed to set an index
* declaration to be used in statements like CREATE TABLE.
*
@ -908,7 +857,7 @@ class MySqlPlatform extends AbstractPlatform
}
/**
* Get the platform name for this instance
* Get the platform name for this instance.
*
* @return string
*/

View File

@ -241,7 +241,7 @@ class OraclePlatform extends AbstractPlatform
public function getCreateTableSql($table, array $columns, array $options = array())
{
$indexes = isset($options['indexes']) ? $options['indexes']:array();
$indexes = isset($options['indexes']) ? $options['indexes'] : array();
$options['indexes'] = array();
$sql = parent::getCreateTableSql($table, $columns, $options);
@ -258,9 +258,9 @@ class OraclePlatform extends AbstractPlatform
if (isset($indexes) && ! empty($indexes)) {
foreach ($indexes as $indexName => $definition) {
// create nonunique indexes, as they are a part od CREATE TABLE DDL
// create nonunique indexes, as they are a part of CREATE TABLE DDL
if ( ! isset($definition['type']) ||
(isset($definition['type']) && strtolower($definition['type']) != 'unique')) {
(isset($definition['type']) && strtolower($definition['type']) != 'unique')) {
$sql[] = $this->getCreateIndexSql($table, $indexName, $definition);
}
}

View File

@ -616,11 +616,8 @@ class PostgreSqlPlatform extends AbstractPlatform
}
if (isset($options['foreignKeys'])) {
foreach ((array) $options['foreignKeys'] as $k => $definition) {
if (is_array($definition)) {
$sql[] = $this->getCreateForeignKeySql($name, $definition);
}
$sql[] = $this->getCreateForeignKeySql($name, $definition);
}
}

View File

@ -166,10 +166,10 @@ abstract class AbstractSchemaManager
}
/**
* List the columns for a given table
* List the columns for a given table.
*
* @param string $table The name of the table
* @return array $tableColumns
* @param string $table The name of the table.
* @return array $tableColumns The column descriptions.
*/
public function listTableColumns($table)
{

View File

@ -233,11 +233,6 @@ final class ClassMetadata
* The value specifies the name of the index. To create a multi-column index,
* just use the same name for several mappings.
*
* - <b>unique (string, optional, schema-only)</b>
* Whether a unique constraint should be generated for the column.
* The value specifies the name of the unique constraint. To create a multi-column
* unique constraint, just use the same name for several mappings.
*
* - <b>foreignKey (string, optional, schema-only)</b>
*
* @var array
@ -316,6 +311,8 @@ final class ClassMetadata
*
* name => <tableName>
* schema => <schemaName>
* indexes => array
* uniqueConstraints => array
*
* @var array
*/

View File

@ -73,10 +73,21 @@ class AnnotationDriver implements Driver
// Evaluate DoctrineTable annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\Table'])) {
$tableAnnot = $classAnnotations['Doctrine\ORM\Mapping\Table'];
$metadata->setPrimaryTable(array(
$primaryTable = array(
'name' => $tableAnnot->name,
'schema' => $tableAnnot->schema
));
);
if ($tableAnnot->indexes !== null) {
foreach ($tableAnnot->indexes as $indexAnnot) {
$primaryTable['indexes'][$indexAnnot->name] = array('fields' => $indexAnnot->columns);
}
}
if ($tableAnnot->uniqueConstraints !== null) {
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraint) {
$primaryTable['uniqueConstraints'][] = $uniqueConstraint->columns;
}
}
$metadata->setPrimaryTable($primaryTable);
}
// Evaluate InheritanceType annotation
@ -135,7 +146,6 @@ class AnnotationDriver implements Driver
);
} else if ($joinColumnsAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumns')) {
foreach ($joinColumnsAnnot->value as $joinColumn) {
//$joinColumns = $joinColumnsAnnot->value;
$joinColumns[] = array(
'name' => $joinColumn->name,
'referencedColumnName' => $joinColumn->referencedColumnName,

View File

@ -21,7 +21,7 @@
namespace Doctrine\ORM\Mapping;
use \Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\Annotation;
/* Annotations */
@ -94,6 +94,16 @@ final class ElementCollection extends Annotation {
final class Table extends Annotation {
public $name;
public $schema;
public $indexes;
public $uniqueConstraints;
}
final class UniqueConstraint extends Annotation {
public $name;
public $columns;
}
final class Index extends Annotation {
public $name;
public $columns;
}
final class JoinTable extends Annotation {
public $name;

View File

@ -55,6 +55,21 @@ class XmlDriver extends AbstractFileDriver
if (isset($xmlRoot['inheritance-type'])) {
$metadata->setInheritanceType((string)$xmlRoot['inheritance-type']);
}
// Evaluate <indexes...>
if (isset($xmlRoot->indexes)) {
foreach ($xmlRoot->indexes->index as $index) {
$metadata->primaryTable['indexes'][$index['name']] = array('fields' =>
explode(',', $index['columns']));
}
}
// Evaluate <unique-constraints..>
if (isset($xmlRoot->{'unique-constraints'})) {
foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $unique) {
$metadata->primaryTable['uniqueConstraints'][] = explode(',', $index['columns']);
}
}
// Evaluate <field ...> mappings
if (isset($xmlRoot->field)) {
@ -69,6 +84,12 @@ class XmlDriver extends AbstractFileDriver
if (isset($fieldMapping['length'])) {
$mapping['length'] = (int)$fieldMapping['length'];
}
if (isset($fieldMapping['precision'])) {
$mapping['precision'] = (int)$fieldMapping['precision'];
}
if (isset($fieldMapping['scale'])) {
$mapping['scale'] = (int)$fieldMapping['scale'];
}
$metadata->mapField($mapping);
}
}
@ -96,10 +117,10 @@ class XmlDriver extends AbstractFileDriver
foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
$mapping = array(
'fieldName' => (string)$oneToOneElement['field'],
'targetEntity' => (string)$oneToOneElement['targetEntity']
'targetEntity' => (string)$oneToOneElement['target-entity']
);
if (isset($oneToOneElement['mappedBy'])) {
$mapping['mappedBy'] = (string)$oneToOneElement['mappedBy'];
if (isset($oneToOneElement['mapped-by'])) {
$mapping['mappedBy'] = (string)$oneToOneElement['mapped-by'];
} else {
$joinColumns = array();
if (isset($oneToOneElement->{'join-column'})) {
@ -127,8 +148,8 @@ class XmlDriver extends AbstractFileDriver
foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
$mapping = array(
'fieldName' => (string)$oneToManyElement['field'],
'targetEntity' => (string)$oneToManyElement['targetEntity'],
'mappedBy' => (string)$oneToManyElement['mappedBy']
'targetEntity' => (string)$oneToManyElement['target-entity'],
'mappedBy' => (string)$oneToManyElement['mapped-by']
);
if (isset($oneToManyElement->cascade)) {
$mapping['cascade'] = $this->_getCascadeMappings($oneToManyElement->cascade);
@ -142,7 +163,7 @@ class XmlDriver extends AbstractFileDriver
foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
$mapping = array(
'fieldName' => (string)$manyToOneElement['field'],
'targetEntity' => (string)$manyToOneElement['targetEntity']
'targetEntity' => (string)$manyToOneElement['target-entity']
);
$joinColumns = array();
if (isset($manyToOneElement->{'join-column'})) {
@ -167,11 +188,11 @@ class XmlDriver extends AbstractFileDriver
foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
$mapping = array(
'fieldName' => (string)$manyToManyElement['field'],
'targetEntity' => (string)$manyToManyElement['targetEntity']
'targetEntity' => (string)$manyToManyElement['target-entity']
);
if (isset($manyToManyElement['mappedBy'])) {
$mapping['mappedBy'] = (string)$manyToManyElement['mappedBy'];
$mapping['mappedBy'] = (string)$manyToManyElement['mapped-by'];
} else if (isset($manyToManyElement->{'join-table'})) {
$joinTableElement = $manyToManyElement->{'join-table'};
$joinTable = array(
@ -242,7 +263,7 @@ class XmlDriver extends AbstractFileDriver
{
$joinColumn = array(
'name' => (string)$joinColumnElement['name'],
'referencedColumnName' => (string)$joinColumnElement['referencedColumnName']
'referencedColumnName' => (string)$joinColumnElement['referenced-column-name']
);
if (isset($joinColumnElement['unique'])) {
$joinColumn['unique'] = (bool)$joinColumnElement['unique'];
@ -251,10 +272,10 @@ class XmlDriver extends AbstractFileDriver
$joinColumn['nullable'] = (bool)$joinColumnElement['nullable'];
}
if (isset($joinColumnElement['onDelete'])) {
$joinColumn['onDelete'] = (string)$joinColumnElement['onDelete'];
$joinColumn['onDelete'] = (string)$joinColumnElement['on-delete'];
}
if (isset($joinColumnElement['onUpdate'])) {
$joinColumn['onUpdate'] = (string)$joinColumnElement['onUpdate'];
$joinColumn['onUpdate'] = (string)$joinColumnElement['on-update'];
}
return $joinColumn;

View File

@ -28,8 +28,6 @@ use Doctrine\DBAL\Types\Type,
* The SchemaTool is a tool to create and/or drop database schemas based on
* <tt>ClassMetadata</tt> class descriptors.
*
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
* @author Roman Borschel <roman@code-factory.org>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
@ -70,10 +68,11 @@ class SchemaTool
}
/**
* Gets an array of DDL statements for the specified array of ClassMetadata instances.
* Gets the list of DDL statements that are required to create the database schema for
* the given list of ClassMetadata instances.
*
* @param array $classes
* @return array $sql
* @return array $sql The SQL statements needed to create the schema for the classes.
*/
public function getCreateSchemaSql(array $classes)
{
@ -140,11 +139,18 @@ class SchemaTool
$foreignKeyConstraints[] = $constraint;
}
} else if ($class->isInheritanceTypeTablePerClass()) {
//TODO
throw DoctrineException::notSupported();
} else {
$columns = $this->_gatherColumns($class, $options);
$this->_gatherRelationsSql($class, $sql, $columns, $foreignKeyConstraints);
}
if (isset($class->primaryTable['indexes'])) {
$options['indexes'] = $class->primaryTable['indexes'];
}
if (isset($class->primaryTable['uniqueConstraints'])) {
$options['uniqueConstraints'] = $class->primaryTable['uniqueConstraints'];
}
$sql = array_merge($sql, $this->_platform->getCreateTableSql(
$class->getQuotedTableName($this->_platform), $columns, $options));
@ -175,6 +181,14 @@ class SchemaTool
return $sql;
}
/**
* Gets a portable column definition as required by the DBAL for the discriminator
* column of a class.
*
* @param ClassMetadata $class
* @return array The portable column definition of the discriminator column as required by
* the DBAL.
*/
private function _getDiscriminatorColumnDefinition($class)
{
$discrColumn = $class->discriminatorColumn;
@ -187,11 +201,13 @@ class SchemaTool
}
/**
* Gathers the column definitions of all field mappings found in the given class.
* Gathers the column definitions as required by the DBAL of all field mappings
* found in the given class.
*
* @param ClassMetadata $class
* @param array $options
* @return array
* @param array $options The table options/constraints where any additional options/constraints
* that are required by columns should be appended.
* @return array The list of portable column definitions as required by the DBAL.
*/
private function _gatherColumns($class, array &$options)
{
@ -203,14 +219,23 @@ class SchemaTool
return $columns;
}
/**
* Creates a column definition as required by the DBAL from an ORM field mapping definition.
*
* @param ClassMetadata $class The class that owns the field mapping.
* @param array $mapping The field mapping.
* @param array $options The table options/constraints where any additional options/constraints
* required by the column should be appended.
* @return array The portable column definition as required by the DBAL.
*/
private function _gatherColumn($class, array $mapping, array &$options)
{
$column = array();
$column['name'] = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform);
$column['type'] = Type::getType($mapping['type']);
$column['length'] = $mapping['length'];
$column['notnull'] = ! $mapping['nullable'];
$column['length'] = isset($mapping['length']) ? $mapping['length'] : null;
$column['notnull'] = isset($mapping['nullable']) ? ! $mapping['nullable'] : false;
if (isset($mapping['default'])) {
$column['default'] = $mapping['default'];
}
@ -225,6 +250,18 @@ class SchemaTool
return $column;
}
/**
* Gathers the SQL for properly setting up the relations of the given class.
* This includes the SQL for foreign key constraints and join tables.
*
* @param ClassMetadata $class
* @param array $sql The sequence of SQL statements where any new statements should be appended.
* @param array $columns The list of columns in the class's primary table where any additional
* columns required by relations should be appended.
* @param array $constraints The constraints of the table where any additional constraints
* required by relations should be appended.
* @return void
*/
private function _gatherRelationsSql($class, array &$sql, array &$columns, array &$constraints)
{
foreach ($class->associationMappings as $fieldName => $mapping) {
@ -246,16 +283,24 @@ class SchemaTool
$columns[$column['name']] = $column;
$constraint['local'][] = $column['name'];
$constraint['foreign'][] = $joinColumn['referencedColumnName'];
if (isset($joinColumn['onUpdate'])) {
$constraint['onUpdate'] = $joinColumn['onUpdate'];
}
if (isset($joinColumn['onDelete'])) {
$constraint['onDelete'] = $joinColumn['onDelete'];
}
}
$constraints[] = $constraint;
} else if ($mapping->isOneToMany() && $mapping->isOwningSide) {
//... create join table, one-many through join table supported later
throw DoctrineException::updateMe("Not yet implemented.");
throw DoctrineException::notSupported();
} else if ($mapping->isManyToMany() && $mapping->isOwningSide) {
// create join table
$joinTableColumns = array();
$joinTableOptions = array();
$joinTable = $mapping->getJoinTable();
// Build first FK constraint (relation table => source table)
$constraint1 = array(
'tableName' => $mapping->getQuotedJoinTableName($this->_platform),
'foreignTable' => $class->getQuotedTableName($this->_platform),
@ -271,9 +316,16 @@ class SchemaTool
$joinTableColumns[$column['name']] = $column;
$constraint1['local'][] = $column['name'];
$constraint1['foreign'][] = $joinColumn['referencedColumnName'];
if (isset($joinColumn['onUpdate'])) {
$constraint1['onUpdate'] = $joinColumn['onUpdate'];
}
if (isset($joinColumn['onDelete'])) {
$constraint1['onDelete'] = $joinColumn['onDelete'];
}
}
$constraints[] = $constraint1;
// Build second FK constraint (relation table => target table)
$constraint2 = array();
$constraint2['tableName'] = $mapping->getQuotedJoinTableName($this->_platform);
$constraint2['foreignTable'] = $foreignClass->getQuotedTableName($this->_platform);
@ -289,33 +341,192 @@ class SchemaTool
$joinTableColumns[$inverseJoinColumn['name']] = $column;
$constraint2['local'][] = $inverseJoinColumn['name'];
$constraint2['foreign'][] = $inverseJoinColumn['referencedColumnName'];
if (isset($inverseJoinColumn['onUpdate'])) {
$constraint2['onUpdate'] = $inverseJoinColumn['onUpdate'];
}
if (isset($joinColumn['onDelete'])) {
$constraint2['onDelete'] = $inverseJoinColumn['onDelete'];
}
}
$constraints[] = $constraint2;
// Get the SQL for creating the join table and merge it with the others
$sql = array_merge($sql, $this->_platform->getCreateTableSql(
$mapping->getQuotedJoinTableName($this->_platform), $joinTableColumns, $joinTableOptions)
);
}
}
}
/**
* Drops the database schema for the given classes.
*
* @param array $classes
* @return void
*/
public function dropSchema(array $classes)
{
//TODO
$dropSchemaSql = $this->getDropSchemaSql($classes);
$conn = $this->_em->getConnection();
foreach ($dropSchemaSql as $sql) {
$conn->execute($sql);
}
}
/**
* Gets the SQL needed to drop the database schema for the given classes.
*
* @param array $classes
* @return array
*/
public function getDropSchemaSql(array $classes)
{
//TODO
$sql = array();
$commitOrder = $classes; //FIXME: get real commit order!!
// Drop tables in reverse commit order
for ($i = count($commitOrder) - 1; $i >= 0; --$i) {
$class = $commitOrder[$i];
if ($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName) {
continue;
}
$sql[] = $this->_platform->getDropTableSql($class->getTableName());
}
return $sql;
}
/**
* Updates the database schema of the given classes by comparing the ClassMetadata
* instances to the current database schema that is inspected.
*
* @param array $classes
* @return void
*/
public function updateSchema(array $classes)
{
//TODO
$updateSchemaSql = $this->getUpdateSchemaSql($classes);
$conn = $this->_em->getConnection();
foreach ($updateSchemaSql as $sql) {
$conn->execute($sql);
}
}
/**
* Gets the sequence of SQL statements that need to be performed in order
* to bring the given class mappings in-synch with the relational schema.
*
* @param array $classes The classes to consider.
* @return array The sequence of SQL statements.
*/
public function getUpdateSchemaSql(array $classes)
{
//TODO
$sql = array();
$conn = $this->_em->getConnection();
$sm = $conn->getSchemaManager();
$tables = $sm->listTables();
$newClasses = array();
foreach ($classes as $class) {
$tableName = $class->getTableName();
$tableExists = false;
foreach ($tables as $index => $table) {
if ($tableName == $table) {
$tableExists = true;
unset($tables[$index]);
break;
}
}
if ( ! $tableExists) {
$newClasses[] = $class;
} else {
$newFields = array();
$newJoinColumns = array();
$currentColumns = $sm->listTableColumns($tableName);
foreach ($class->fieldMappings as $fieldMapping) {
$exists = false;
foreach ($currentColumns as $index => $column) {
if ($column['name'] == $fieldMapping['columnName']) {
// Column exists, check for changes
// 1. check for nullability change
// 2. check for uniqueness change
// 3. check for length change if type string
// 4. check for type change
unset($currentColumns[$index]);
$exists = true;
break;
}
}
if ( ! $exists) {
$newFields[] = $fieldMapping;
}
}
foreach ($class->associationMappings as $assoc) {
if ($assoc->isOwningSide && $assoc->isOneToOne()) {
foreach ($assoc->targetToSourceKeyColumns as $targetColumn => $sourceColumn) {
$exists = false;
foreach ($currentColumns as $index => $column) {
if ($column['name'] == $sourceColumn) {
// Column exists, check for changes
// 1. check for nullability change
unset($currentColumns[$index]);
$exists = true;
break;
}
}
if ( ! $exists) {
$newJoinColumns[$sourceColumn] = array(
'name' => $sourceColumn,
'type' => 'integer' //FIXME!!!
);
}
}
}
}
if ($newFields || $newJoinColumns) {
$changes = array();
foreach ($newFields as $newField) {
$options = array();
$changes['add'][$newField['columnName']] = $this->_gatherColumn($class, $newField, $options);
}
foreach ($newJoinColumns as $name => $joinColumn) {
$changes['add'][$name] = $joinColumn;
}
$sql[] = $this->_platform->getAlterTableSql($tableName, $changes);
}
// Drop any remaining columns
if ($currentColumns) {
$changes = array();
foreach ($currentColumns as $column) {
$options = array();
$changes['remove'][$column['name']] = $column;
}
$sql[] = $this->_platform->getAlterTableSql($tableName, $changes);
}
}
}
if ($newClasses) {
$sql = array_merge($this->getCreateSchemaSql($newClasses), $sql);
}
// Drop any remaining tables (Probably not a good idea, because the given class list
// may not be complete!)
/*if ($tables) {
foreach ($tables as $table) {
$sql[] = $this->_platform->getDropTableSql($table);
}
}*/
return $sql;
}
}

View File

@ -25,9 +25,7 @@ use Doctrine\Common\Collections\ArrayCollection,
Doctrine\Common\Collections\Collection,
Doctrine\Common\DoctrineException,
Doctrine\Common\PropertyChangedListener,
Doctrine\ORM\Event\LifecycleEventArgs,
Doctrine\ORM\Internal\CommitOrderCalculator,
Doctrine\ORM\Internal\CommitOrderNode;
Doctrine\ORM\Event\LifecycleEventArgs;
/**
* The UnitOfWork is responsible for tracking changes to objects during an
@ -233,7 +231,7 @@ class UnitOfWork implements PropertyChangedListener
{
$this->_em = $em;
$this->_evm = $em->getEventManager();
$this->_commitOrderCalculator = new CommitOrderCalculator();
$this->_commitOrderCalculator = new Internal\CommitOrderCalculator();
$this->_useCExtension = $this->_em->getConfiguration()->getUseCExtension();
}

View File

@ -126,7 +126,7 @@ class MySqlPlatformTest extends \Doctrine\Tests\DbalTestCase
'Variable string declaration is not correct'
);
$this->assertEquals(
'TEXT',
'VARCHAR(255)',
$this->_platform->getVarcharTypeDeclarationSql(array()),
'Long string declaration is not correct'
);

View File

@ -26,7 +26,6 @@ class AllTests
$suite->addTest(Query\AllTests::suite());
$suite->addTest(Hydration\AllTests::suite());
$suite->addTest(Entity\AllTests::suite());
$suite->addTest(Tools\AllTests::suite());
$suite->addTest(Associations\AllTests::suite());
$suite->addTest(Mapping\AllTests::suite());
$suite->addTest(Functional\AllTests::suite());

View File

@ -42,6 +42,7 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\EntityRepositoryTest');
$suite->addTest(Locking\AllTests::suite());
$suite->addTest(SchemaTool\AllTests::suite());
return $suite;
}

View File

@ -0,0 +1,30 @@
<?php
namespace Doctrine\Tests\ORM\Functional\SchemaTool;
if (!defined('PHPUnit_MAIN_METHOD')) {
define('PHPUnit_MAIN_METHOD', 'Orm_Functional_Tools_AllTests::main');
}
require_once __DIR__ . '/../../../TestInit.php';
class AllTests
{
public static function main()
{
\PHPUnit_TextUI_TestRunner::run(self::suite());
}
public static function suite()
{
$suite = new \Doctrine\Tests\DoctrineTestSuite('Doctrine Orm Functional Tools');
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\SchemaTool\MySqlSchemaToolTest');
return $suite;
}
}
if (PHPUnit_MAIN_METHOD == 'Orm_Functional_Tools_AllTests::main') {
AllTests::main();
}

View File

@ -0,0 +1,98 @@
<?php
namespace Doctrine\Tests\ORM\Functional\SchemaTool;
use Doctrine\ORM\Tools\SchemaTool,
Doctrine\ORM\Mapping\ClassMetadata;
require_once __DIR__ . '/../../../TestInit.php';
class MySqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp() {
parent::setUp();
if ($this->_em->getConnection()->getDatabasePlatform()->getName() !== 'mysql') {
$this->markTestSkipped('The ' . __CLASS__ .' requires the use of mysql.');
}
}
public function testGetCreateSchemaSql()
{
$classes = array(
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'),
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'),
$this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'),
);
$tool = new SchemaTool($this->_em);
$sql = $tool->getCreateSchemaSql($classes);
$this->assertEquals(count($sql), 8);
$this->assertEquals("CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(id))", $sql[0]);
$this->assertEquals("CREATE TABLE cms_users_groups (user_id INT DEFAULT NULL, group_id INT DEFAULT NULL, PRIMARY KEY(user_id, group_id))", $sql[1]);
$this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, status VARCHAR(50) NOT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))", $sql[2]);
$this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY(phonenumber))", $sql[3]);
$this->assertEquals("ALTER TABLE cms_addresses ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[4]);
$this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[5]);
$this->assertEquals("ALTER TABLE cms_users_groups ADD FOREIGN KEY (group_id) REFERENCES cms_groups(id)", $sql[6]);
$this->assertEquals("ALTER TABLE cms_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES cms_users(id)", $sql[7]);
}
public function testGetUpdateSchemaSql()
{
$classes = array(
$this->_em->getClassMetadata(__NAMESPACE__ . '\SchemaToolEntityA')
);
$tool = new SchemaTool($this->_em);
$tool->createSchema($classes);
// Add field to SchemaToolEntityA
$classA = $classes[0];
$classA->mapField(array(
'fieldName' => 'newField',
'columnName' => 'new_field',
'type' => 'string',
'length' => 50,
'nullable' => false
));
// Introduce SchemaToolEntityB
$classB = new ClassMetadata(__NAMESPACE__ . '\SchemaToolEntityB');
$classB->setTableName('schematool_entity_b');
$classB->mapField(array(
'fieldName' => 'id',
'columnName' => 'id',
'type' => 'integer',
'nullable' => false,
'id' => true
));
$classB->mapField(array(
'fieldName' => 'field',
'columnName' => 'field',
'type' => 'string',
'nullable' => false
));
$classes[] = $classB;
$sql = $tool->getUpdateSchemaSql($classes);
$this->assertEquals(2, count($sql));
$this->assertEquals("CREATE TABLE schematool_entity_b (id INT NOT NULL, field VARCHAR(255) NOT NULL, PRIMARY KEY(id))", $sql[0]);
$this->assertEquals("ALTER TABLE schematool_entity_a ADD new_field VARCHAR(50) NOT NULL", $sql[1]);
}
}
/** @Entity @Table(name="schematool_entity_a") */
class SchemaToolEntityA {
/** @Id @Column(type="integer") */
private $id;
private $newField;
}
class SchemaToolEntityB {
private $id;
private $field;
}

View File

@ -17,23 +17,23 @@
<field name="name" column="name" type="string" length="50"/>
<one-to-one field="address" targetEntity="Address">
<join-column name="address_id" referencedColumnName="id"/>
<one-to-one field="address" target-entity="Address">
<join-column name="address_id" referenced-column-name="id"/>
</one-to-one>
<one-to-many field="phonenumbers" targetEntity="Phonenumber" mappedBy="user">
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user">
<cascade>
<cascade-persist/>
</cascade>
</one-to-many>
<many-to-many field="groups" targetEntity="Group">
<many-to-many field="groups" target-entity="Group">
<join-table name="cms_users_groups">
<join-columns>
<join-column name="user_id" referencedColumnName="id"/>
<join-column name="user_id" referenced-column-name="id"/>
</join-columns>
<inverse-join-columns>
<join-column name="group_id" referencedColumnName="id"/>
<join-column name="group_id" referenced-column-name="id"/>
</inverse-join-columns>
</join-table>
</many-to-many>

View File

@ -1,30 +0,0 @@
<?php
namespace Doctrine\Tests\ORM\Tools;
if (!defined('PHPUnit_MAIN_METHOD')) {
define('PHPUnit_MAIN_METHOD', 'Orm_Tools_AllTests::main');
}
require_once __DIR__ . '/../../TestInit.php';
class AllTests
{
public static function main()
{
\PHPUnit_TextUI_TestRunner::run(self::suite());
}
public static function suite()
{
$suite = new \Doctrine\Tests\DoctrineTestSuite('Doctrine Orm Tools');
$suite->addTestSuite('Doctrine\Tests\ORM\Tools\SchemaToolTest');
return $suite;
}
}
if (PHPUnit_MAIN_METHOD == 'Orm_Tools_AllTests::main') {
AllTests::main();
}

View File

@ -1,29 +0,0 @@
<?php
namespace Doctrine\Tests\ORM\Tools;
use Doctrine\ORM\Tools\SchemaTool;
require_once __DIR__ . '/../../TestInit.php';
class SchemaToolTest extends \Doctrine\Tests\OrmTestCase
{
public function testGetCreateSchemaSql()
{
$driver = new \Doctrine\Tests\Mocks\DriverMock;
$conn = new \Doctrine\Tests\Mocks\ConnectionMock(array(), $driver);
$conn->setDatabasePlatform(new \Doctrine\DBAL\Platforms\MySqlPlatform());
$em = $this->_getTestEntityManager($conn);
$classes = array(
$em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'),
$em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'),
$em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'),
);
$exporter = new SchemaTool($em);
$sql = $exporter->getCreateSchemaSql($classes);
$this->assertEquals(count($sql), 8);
}
}