Commit 9cd0379f authored by beberlei's avatar beberlei

[2.0] DDC-200 Implemented support for @columnDefinition - even with support to...

[2.0] DDC-200 Implemented support for @columnDefinition - even with support to pass the definition to join columns if necessary for relations.
parent 46ed63d1
......@@ -583,7 +583,7 @@ abstract class AbstractPlatform
$columnData['precision'] = $column->getPrecision();
$columnData['scale'] = $column->getScale();
$columnData['default'] = $column->getDefault();
// TODO: Fixed? Unsigned?
$columnData['columnDefinition'] = $column->getColumnDefinition();
if(in_array($column->getName(), $options['primary'])) {
$columnData['primary'] = true;
......@@ -903,30 +903,37 @@ abstract class AbstractPlatform
* unique constraint
* check
* column check constraint
* columnDefinition
* a string that defines the complete column
*
* @return string DBMS specific SQL code portion that should be used to declare the column.
*/
public function getColumnDeclarationSql($name, array $field)
{
$default = $this->getDefaultValueDeclarationSql($field);
if (isset($field['columnDefinition'])) {
$columnDef = $this->getCustomTypeDeclarationSql($field);
} else {
$default = $this->getDefaultValueDeclarationSql($field);
$charset = (isset($field['charset']) && $field['charset']) ?
' ' . $this->getColumnCharsetDeclarationSql($field['charset']) : '';
$charset = (isset($field['charset']) && $field['charset']) ?
' ' . $this->getColumnCharsetDeclarationSql($field['charset']) : '';
$collation = (isset($field['collation']) && $field['collation']) ?
' ' . $this->getColumnCollationDeclarationSql($field['collation']) : '';
$collation = (isset($field['collation']) && $field['collation']) ?
' ' . $this->getColumnCollationDeclarationSql($field['collation']) : '';
$notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
$notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
$unique = (isset($field['unique']) && $field['unique']) ?
' ' . $this->getUniqueFieldDeclarationSql() : '';
$unique = (isset($field['unique']) && $field['unique']) ?
' ' . $this->getUniqueFieldDeclarationSql() : '';
$check = (isset($field['check']) && $field['check']) ?
' ' . $field['check'] : '';
$check = (isset($field['check']) && $field['check']) ?
' ' . $field['check'] : '';
$typeDecl = $field['type']->getSqlDeclaration($field, $this);
$typeDecl = $field['type']->getSqlDeclaration($field, $this);
$columnDef = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation;
}
return $name . ' ' . $typeDecl . $charset . $default . $notnull . $unique . $check . $collation;
return $name . ' ' . $columnDef;
}
/**
......@@ -1081,6 +1088,19 @@ abstract class AbstractPlatform
. ')';
}
/**
* getCustomTypeDeclarationSql
* Obtail SQL code portion needed to create a custom column,
* e.g. when a field has the "columnDefinition" keyword.
* Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate.
*
* @return string
*/
public function getCustomTypeDeclarationSql(array $columnDef)
{
return $columnDef['columnDefinition'];
}
/**
* getIndexFieldDeclarationList
* Obtain DBMS specific SQL code portion needed to set an index
......
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Platforms;
use Doctrine\DBAL\DBALException,
Doctrine\DBAL\Connection,
Doctrine\DBAL\Types,
Doctrine\DBAL\Schema\Table,
Doctrine\DBAL\Schema\Index,
Doctrine\DBAL\Schema\ForeignKeyConstraint,
Doctrine\DBAL\Schema\TableDiff;
/**
* Base class for all DatabasePlatforms. The DatabasePlatforms are the central
* point of abstraction of platform-specific behaviors, features and SQL dialects.
* They are a passive source of information.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision: 3938 $
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
*/
abstract class AbstractPlatform
{
/**
* @var int
*/
const CREATE_INDEXES = 1;
/**
* @var int
*/
const CREATE_FOREIGNKEYS = 2;
/**
* Constructor.
*/
public function __construct() {}
/**
* Gets the character used for identifier quoting.
*
* @return string
*/
public function getIdentifierQuoteCharacter()
{
return '"';
}
/**
* Gets the string portion that starts an SQL comment.
*
* @return string
*/
public function getSqlCommentStartString()
{
return "--";
}
/**
* Gets the string portion that ends an SQL comment.
*
* @return string
*/
public function getSqlCommentEndString()
{
return "\n";
}
/**
* Gets the maximum length of a varchar field.
*
* @return integer
*/
public function getVarcharMaxLength()
{
return 255;
}
/**
* Gets all SQL wildcard characters of the platform.
*
* @return array
*/
public function getWildcards()
{
return array('%', '_');
}
/**
* Returns the regular expression operator.
*
* @return string
*/
public function getRegexpExpression()
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Returns the average value of a column
*
* @param string $column the column to use
* @return string generated sql including an AVG aggregate function
*/
public function getAvgExpression($column)
{
return 'AVG(' . $column . ')';
}
/**
* Returns the number of rows (without a NULL value) of a column
*
* If a '*' is used instead of a column the number of selected rows
* is returned.
*
* @param string|integer $column the column to use
* @return string generated sql including a COUNT aggregate function
*/
public function getCountExpression($column)
{
return 'COUNT(' . $column . ')';
}
/**
* Returns the highest value of a column
*
* @param string $column the column to use
* @return string generated sql including a MAX aggregate function
*/
public function getMaxExpression($column)
{
return 'MAX(' . $column . ')';
}
/**
* Returns the lowest value of a column
*
* @param string $column the column to use
* @return string
*/
public function getMinExpression($column)
{
return 'MIN(' . $column . ')';
}
/**
* Returns the total sum of a column
*
* @param string $column the column to use
* @return string
*/
public function getSumExpression($column)
{
return 'SUM(' . $column . ')';
}
// scalar functions
/**
* Returns the md5 sum of a field.
*
* Note: Not SQL92, but common functionality
*
* @return string
*/
public function getMd5Expression($column)
{
return 'MD5(' . $column . ')';
}
/**
* Returns the length of a text field.
*
* @param string $expression1
* @param string $expression2
* @return string
*/
public function getLengthExpression($column)
{
return 'LENGTH(' . $column . ')';
}
/**
* Rounds a numeric field to the number of decimals specified.
*
* @param string $expression1
* @param string $expression2
* @return string
*/
public function getRoundExpression($column, $decimals = 0)
{
return 'ROUND(' . $column . ', ' . $decimals . ')';
}
/**
* Returns the remainder of the division operation
* $expression1 / $expression2.
*
* @param string $expression1
* @param string $expression2
* @return string
*/
public function getModExpression($expression1, $expression2)
{
return 'MOD(' . $expression1 . ', ' . $expression2 . ')';
}
/**
* trim
* returns the string $str with leading and proceeding space characters removed
*
* @param string $str literal string or column name
* @return string
*/
public function getTrimExpression($str)
{
return 'TRIM(' . $str . ')';
}
/**
* rtrim
* returns the string $str with proceeding space characters removed
*
* @param string $str literal string or column name
* @return string
*/
public function getRtrimExpression($str)
{
return 'RTRIM(' . $str . ')';
}
/**
* ltrim
* returns the string $str with leading space characters removed
*
* @param string $str literal string or column name
* @return string
*/
public function getLtrimExpression($str)
{
return 'LTRIM(' . $str . ')';
}
/**
* upper
* Returns the string $str with all characters changed to
* uppercase according to the current character set mapping.
*
* @param string $str literal string or column name
* @return string
*/
public function getUpperExpression($str)
{
return 'UPPER(' . $str . ')';
}
/**
* lower
* Returns the string $str with all characters changed to
* lowercase according to the current character set mapping.
*
* @param string $str literal string or column name
* @return string
*/
public function getLowerExpression($str)
{
return 'LOWER(' . $str . ')';
}
/**
* locate
* returns the position of the first occurrence of substring $substr in string $str
*
* @param string $substr literal string to find
* @param string $str literal string
* @return integer
*/
public function getLocateExpression($str, $substr)
{
return 'LOCATE(' . $str . ', ' . $substr . ')';
}
/**
* Returns the current system date.
*
* @return string
*/
public function getNowExpression()
{
return 'NOW()';
}
/**
* return string to call a function to get a substring inside an SQL statement
*
* Note: Not SQL92, but common functionality.
*
* SQLite only supports the 2 parameter variant of this function
*
* @param string $value an sql string literal or column name/alias
* @param integer $position where to start the substring portion
* @param integer $length the substring portion length
* @return string SQL substring function with given parameters
*/
public function getSubstringExpression($value, $from, $len = null)
{
if ($len === null)
return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
else {
return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $len . ')';
}
}
/**
* Returns a series of strings concatinated
*
* concat() accepts an arbitrary number of parameters. Each parameter
* must contain an expression
*
* @param string $arg1, $arg2 ... $argN strings that will be concatinated.
* @return string
*/
public function getConcatExpression()
{
return join(' || ' , func_get_args());
}
/**
* Returns the SQL for a logical not.
*
* Example:
* <code>
* $q = new Doctrine_Query();
* $e = $q->expr;
* $q->select('*')->from('table')
* ->where($e->eq('id', $e->not('null'));
* </code>
*
* @return string a logical expression
*/
public function getNotExpression($expression)
{
return 'NOT(' . $expression . ')';
}
/**
* Returns the SQL to check if a value is one in a set of
* given values.
*
* in() accepts an arbitrary number of parameters. The first parameter
* must always specify the value that should be matched against. Successive
* must contain a logical expression or an array with logical expressions.
* These expressions will be matched against the first parameter.
*
* @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
*/
public function getInExpression($column, $values)
{
if ( ! is_array($values)) {
$values = array($values);
}
$values = $this->getIdentifiers($values);
if (count($values) == 0) {
throw \InvalidArgumentException('Values must not be empty.');
}
return $column . ' IN (' . implode(', ', $values) . ')';
}
/**
* Returns SQL that checks if a expression is null.
*
* @param string $expression the expression that should be compared to null
* @return string logical expression
*/
public function getIsNullExpression($expression)
{
return $expression . ' IS NULL';
}
/**
* Returns SQL that checks if a expression is not null.
*
* @param string $expression the expression that should be compared to null
* @return string logical expression
*/
public function getIsNotNullExpression($expression)
{
return $expression . ' IS NOT NULL';
}
/**
* Returns SQL that checks if an expression evaluates to a value between
* two values.
*
* The parameter $expression is checked if it is between $value1 and $value2.
*
* Note: There is a slight difference in the way BETWEEN works on some databases.
* http://www.w3schools.com/sql/sql_between.asp. If you want complete database
* independence you should avoid using between().
*
* @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
* @return string logical expression
*/
public function getBetweenExpression($expression, $value1, $value2)
{
return $expression . ' BETWEEN ' .$value1 . ' AND ' . $value2;
}
public function getAcosExpression($value)
{
return 'ACOS(' . $value . ')';
}
public function getSinExpression($value)
{
return 'SIN(' . $value . ')';
}
public function getPiExpression()
{
return 'PI()';
}
public function getCosExpression($value)
{
return 'COS(' . $value . ')';
}
public function getForUpdateSql()
{
return 'FOR UPDATE';
}
public function getDropDatabaseSql($database)
{
return 'DROP DATABASE ' . $database;
}
/**
* Drop a Table
*
* @param Table|string $table
* @return string
*/
public function getDropTableSql($table)
{
if ($table instanceof \Doctrine\DBAL\Schema\Table) {
$table = $table->getName();
}
return 'DROP TABLE ' . $table;
}
/**
* Drop index from a table
*
* @param Index|string $name
* @param string|Table $table
* @return string
*/
public function getDropIndexSql($index, $table=null)
{
if($index instanceof \Doctrine\DBAL\Schema\Index) {
$index = $index->getName();
} else if(!is_string($index)) {
throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSql() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
}
return 'DROP INDEX ' . $index;
}
/**
* Get drop constraint sql
*
* @param \Doctrine\DBAL\Schema\Constraint $constraint
* @param string|Table $table
* @return string
*/
public function getDropConstraintSql($constraint, $table)
{
if ($constraint instanceof \Doctrine\DBAL\Schema\Constraint) {
$constraint = $constraint->getName();
}
if ($table instanceof \Doctrine\DBAL\Schema\Table) {
$table = $table->getName();
}
return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint;
}
/**
* @param ForeignKeyConstraint|string $foreignKey
* @param Table|string $table
* @return string
*/
public function getDropForeignKeySql($foreignKey, $table)
{
if ($foreignKey instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) {
$foreignKey = $foreignKey->getName();
}
if ($table instanceof \Doctrine\DBAL\Schema\Table) {
$table = $table->getName();
}
return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey;
}
/**
* Gets the SQL statement(s) to create a table with the specified name, columns and constraints
* on this platform.
*
* @param string $table The name of the table.
* @param int $createFlags
* @return array The sequence of SQL statements.
*/
public function getCreateTableSql(Table $table, $createFlags=self::CREATE_INDEXES)
{
if (!is_int($createFlags)) {
throw new \InvalidArgumentException("Second argument of AbstractPlatform::getCreateTableSql() has to be integer.");
}
$tableName = $table->getName();
$options = $table->getOptions();
$options['uniqueConstraints'] = array();
$options['indexes'] = array();
$options['primary'] = array();
if (($createFlags&self::CREATE_INDEXES) > 0) {
foreach ($table->getIndexes() AS $index) {
/* @var $index Index */
if ($index->isPrimary()) {
$options['primary'] = $index->getColumns();
} else {
$options['indexes'][$index->getName()] = $index;
}
}
}
$columns = array();
foreach ($table->getColumns() AS $column) {
/* @var \Doctrine\DBAL\Schema\Column $column */
$columnData = array();
$columnData['name'] = $column->getName();
$columnData['type'] = $column->getType();
$columnData['length'] = $column->getLength();
$columnData['notnull'] = $column->getNotNull();
$columnData['unique'] = ($column->hasPlatformOption("unique"))?$column->getPlatformOption('unique'):false;
$columnData['version'] = ($column->hasPlatformOption("version"))?$column->getPlatformOption('version'):false;
if(strtolower($columnData['type']) == "string" && $columnData['length'] === null) {
$columnData['length'] = 255;
}
$columnData['precision'] = $column->getPrecision();
$columnData['scale'] = $column->getScale();
$columnData['default'] = $column->getDefault();
// TODO: Fixed? Unsigned?
if(in_array($column->getName(), $options['primary'])) {
$columnData['primary'] = true;
if($table->isIdGeneratorIdentity()) {
$columnData['autoincrement'] = true;
}
}
$columns[$columnData['name']] = $columnData;
}
if (($createFlags&self::CREATE_FOREIGNKEYS) > 0) {
$options['foreignKeys'] = array();
foreach ($table->getForeignKeys() AS $fkConstraint) {
$options['foreignKeys'][] = $fkConstraint;
}
}
return $this->_getCreateTableSql($tableName, $columns, $options);
}
/**
* @param string $tableName
* @param array $columns
* @param array $options
* @return array
*/
protected function _getCreateTableSql($tableName, array $columns, array $options = array())
{
$columnListSql = $this->getColumnDeclarationListSql($columns);
if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
foreach ($options['uniqueConstraints'] as $name => $definition) {
$columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSql($name, $definition);
}
}
if (isset($options['primary']) && ! empty($options['primary'])) {
$columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')';
}
if (isset($options['indexes']) && ! empty($options['indexes'])) {
foreach($options['indexes'] as $index => $definition) {
$columnListSql .= ', ' . $this->getIndexDeclarationSql($index, $definition);
}
}
$query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;
$check = $this->getCheckDeclarationSql($columns);
if ( ! empty($check)) {
$query .= ', ' . $check;
}
$query .= ')';
$sql[] = $query;
if (isset($options['foreignKeys'])) {
foreach ((array) $options['foreignKeys'] AS $definition) {
$sql[] = $this->getCreateForeignKeySql($definition, $tableName);
}
}
return $sql;
}
public function getCreateTemporaryTableSnippetSql()
{
return "CREATE TEMPORARY TABLE";
}
/**
* Gets the SQL to create a sequence on this platform.
*
* @param \Doctrine\DBAL\Schema\Sequence $sequence
* @throws DoctrineException
*/
public function getCreateSequenceSql(\Doctrine\DBAL\Schema\Sequence $sequence)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Gets the SQL to create a constraint on a table on this platform.
*
* @param Constraint $constraint
* @param string|Table $table
* @return string
*/
public function getCreateConstraintSql(\Doctrine\DBAL\Schema\Constraint $constraint, $table)
{
if ($table instanceof \Doctrine\DBAL\Schema\Table) {
$table = $table->getName();
}
$query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getName();
$columns = array();
foreach ($constraint->getColumns() as $column) {
$columns[] = $column;
}
$columnList = '('. implode(', ', $columns) . ')';
$referencesClause = '';
if ($constraint instanceof \Doctrine\DBAL\Schema\Index) {
if($constraint->isPrimary()) {
$query .= ' PRIMARY KEY';
} elseif ($constraint->isUnique()) {
$query .= ' UNIQUE';
} else {
throw new \InvalidArgumentException(
'Can only create primary or unique constraints, no common indexes with getCreateConstraintSql().'
);
}
} else if ($constraint instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) {
$query .= ' FOREIGN KEY';
$foreignColumns = array();
foreach ($constraint->getForeignColumns() AS $column) {
$foreignColumns[] = $column;
}
$referencesClause = ' REFERENCES '.$constraint->getForeignTableName(). ' ('.implode(', ', $foreignColumns).')';
}
$query .= ' '.$columnList.$referencesClause;
return $query;
}
/**
* Gets the SQL to create an index on a table on this platform.
*
* @param Index $index
* @param string|Table $table name of the table on which the index is to be created
* @return string
*/
public function getCreateIndexSql(Index $index, $table)
{
if ($table instanceof Table) {
$table = $table->getName();
}
$name = $index->getName();
$columns = $index->getColumns();
if (count($columns) == 0) {
throw new \InvalidArgumentException("Incomplete definition. 'columns' required.");
}
$type = '';
if ($index->isUnique()) {
$type = 'UNIQUE ';
}
$query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
$query .= ' (' . $this->getIndexFieldDeclarationListSql($columns) . ')';
return $query;
}
/**
* Quotes a string so that it can be safely used as a table or column name,
* even if it is a reserved word of the platform.
*
* NOTE: Just because you CAN use quoted identifiers doesn't mean
* you SHOULD use them. In general, they end up causing way more
* problems than they solve.
*
* @param string $str identifier name to be quoted
* @return string quoted identifier string
*/
public function quoteIdentifier($str)
{
$c = $this->getIdentifierQuoteCharacter();
return $c . $str . $c;
}
/**
* Create a new foreign key
*
* @param ForeignKeyConstraint $foreignKey ForeignKey instance
* @param string|Table $table name of the table on which the foreign key is to be created
* @return string
*/
public function getCreateForeignKeySql(ForeignKeyConstraint $foreignKey, $table)
{
if ($table instanceof \Doctrine\DBAL\Schema\Table) {
$table = $table->getName();
}
$query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSql($foreignKey);
return $query;
}
/**
* Gets the sql statements for altering an existing table.
*
* The method returns an array of sql statements, since some platforms need several statements.
*
* @param TableDiff $diff
* @return array
*/
public function getAlterTableSql(TableDiff $diff)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Common code for alter table statement generation that updates the changed Index and Foreign Key definitions.
*
* @param TableDiff $diff
* @return array
*/
protected function _getAlterTableIndexForeignKeySql(TableDiff $diff)
{
if ($diff->newName !== false) {
$tableName = $diff->newName;
} else {
$tableName = $diff->name;
}
$sql = array();
if ($this->supportsForeignKeyConstraints()) {
foreach ($diff->addedForeignKeys AS $foreignKey) {
$sql[] = $this->getCreateForeignKeySql($foreignKey, $tableName);
}
foreach ($diff->removedForeignKeys AS $foreignKey) {
$sql[] = $this->getDropForeignKeySql($foreignKey, $tableName);
}
foreach ($diff->changedForeignKeys AS $foreignKey) {
$sql[] = $this->getDropForeignKeySql($foreignKey, $tableName);
$sql[] = $this->getCreateForeignKeySql($foreignKey, $tableName);
}
}
foreach ($diff->addedIndexes AS $index) {
$sql[] = $this->getCreateIndexSql($index, $tableName);
}
foreach ($diff->removedIndexes AS $index) {
$sql[] = $this->getDropIndexSql($index, $tableName);
}
foreach ($diff->changedIndexes AS $index) {
$sql[] = $this->getDropIndexSql($index, $tableName);
$sql[] = $this->getCreateIndexSql($index, $tableName);
}
return $sql;
}
/**
* Get declaration of a number of fields in bulk
*
* @param array $fields a multidimensional associative array.
* The first dimension determines the field name, while the second
* dimension is keyed with the name of the properties
* of the field being declared as array indexes. Currently, the types
* of supported field properties are as follows:
*
* length
* Integer value that determines the maximum length of the text
* field. If this argument is missing the field should be
* declared to have the longest length allowed by the DBMS.
*
* default
* Text value to be used as default for this field.
*
* notnull
* Boolean flag that indicates whether this field is constrained
* to not be set to null.
* charset
* Text value with the default CHARACTER SET for this field.
* collation
* Text value with the default COLLATION for this field.
* unique
* unique constraint
*
* @return string
*/
public function getColumnDeclarationListSql(array $fields)
{
$queryFields = array();
foreach ($fields as $fieldName => $field) {
$query = $this->getColumnDeclarationSql($fieldName, $field);
$queryFields[] = $query;
}
return implode(', ', $queryFields);
}
/**
* Obtain DBMS specific SQL code portion needed to declare a generic type
* field to be used in statements like CREATE TABLE.
*
* @param string $name name the field to be declared.
* @param array $field associative array with the name of the properties
* of the field being declared as array indexes. Currently, the types
* of supported field properties are as follows:
*
* length
* Integer value that determines the maximum length of the text
* field. If this argument is missing the field should be
* declared to have the longest length allowed by the DBMS.
*
* default
* Text value to be used as default for this field.
*
* notnull
* Boolean flag that indicates whether this field is constrained
* to not be set to null.
* charset
* Text value with the default CHARACTER SET for this field.
* collation
* Text value with the default COLLATION for this field.
* unique
* unique constraint
* check
* column check constraint
*
* @return string DBMS specific SQL code portion that should be used to declare the column.
*/
public function getColumnDeclarationSql($name, array $field)
{
$default = $this->getDefaultValueDeclarationSql($field);
$charset = (isset($field['charset']) && $field['charset']) ?
' ' . $this->getColumnCharsetDeclarationSql($field['charset']) : '';
$collation = (isset($field['collation']) && $field['collation']) ?
' ' . $this->getColumnCollationDeclarationSql($field['collation']) : '';
$notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
$unique = (isset($field['unique']) && $field['unique']) ?
' ' . $this->getUniqueFieldDeclarationSql() : '';
$check = (isset($field['check']) && $field['check']) ?
' ' . $field['check'] : '';
$typeDecl = $field['type']->getSqlDeclaration($field, $this);
return $name . ' ' . $typeDecl . $charset . $default . $notnull . $unique . $check . $collation;
}
/**
* Gets the SQL snippet that declares a floating point column of arbitrary precision.
*
* @param array $columnDef
* @return string
*/
public function getDecimalTypeDeclarationSql(array $columnDef)
{
$columnDef['precision'] = ( ! isset($columnDef['precision']) || empty($columnDef['precision']))
? 10 : $columnDef['precision'];
$columnDef['scale'] = ( ! isset($columnDef['scale']) || empty($columnDef['scale']))
? 0 : $columnDef['scale'];
return 'NUMERIC(' . $columnDef['precision'] . ', ' . $columnDef['scale'] . ')';
}
/**
* Gets the SQL snippet that declares a boolean column.
*
* @param array $columnDef
* @return string
*/
abstract public function getBooleanTypeDeclarationSql(array $columnDef);
/**
* Gets the SQL snippet that declares a 4 byte integer column.
*
* @param array $columnDef
* @return string
*/
abstract public function getIntegerTypeDeclarationSql(array $columnDef);
/**
* Gets the SQL snippet that declares an 8 byte integer column.
*
* @param array $columnDef
* @return string
*/
abstract public function getBigIntTypeDeclarationSql(array $columnDef);
/**
* Gets the SQL snippet that declares a 2 byte integer column.
*
* @param array $columnDef
* @return string
*/
abstract public function getSmallIntTypeDeclarationSql(array $columnDef);
/**
* Gets the SQL snippet that declares common properties of an integer column.
*
* @param array $columnDef
* @return string
*/
abstract protected function _getCommonIntegerTypeDeclarationSql(array $columnDef);
/**
* Obtain DBMS specific SQL code portion needed to set a default value
* declaration to be used in statements like CREATE TABLE.
*
* @param array $field field definition array
* @return string DBMS specific SQL code portion needed to set a default value
*/
public function getDefaultValueDeclarationSql($field)
{
$default = empty($field['notnull']) ? ' DEFAULT NULL' : '';
if (isset($field['default'])) {
$default = " DEFAULT '".$field['default']."'";
if (isset($field['type'])) {
if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) {
$default = " DEFAULT ".$field['default'];
} else if ((string)$field['type'] == 'DateTime' && $field['default'] == $this->getCurrentTimestampSql()) {
$default = " DEFAULT ".$this->getCurrentTimestampSql();
}
}
}
return $default;
}
/**
* Obtain DBMS specific SQL code portion needed to set a CHECK constraint
* declaration to be used in statements like CREATE TABLE.
*
* @param array $definition check definition
* @return string DBMS specific SQL code portion needed to set a CHECK constraint
*/
public function getCheckDeclarationSql(array $definition)
{
$constraints = array();
foreach ($definition as $field => $def) {
if (is_string($def)) {
$constraints[] = 'CHECK (' . $def . ')';
} else {
if (isset($def['min'])) {
$constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')';
}
if (isset($def['max'])) {
$constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')';
}
}
}
return implode(', ', $constraints);
}
/**
* Obtain DBMS specific SQL code portion needed to set a unique
* constraint declaration to be used in statements like CREATE TABLE.
*
* @param string $name name of the unique constraint
* @param Index $index index definition
* @return string DBMS specific SQL code portion needed
* to set a constraint
*/
public function getUniqueConstraintDeclarationSql($name, Index $index)
{
if (count($index->getColumns()) == 0) {
throw \InvalidArgumentException("Incomplete definition. 'columns' required.");
}
return 'CONSTRAINT' . $name . ' UNIQUE ('
. $this->getIndexFieldDeclarationListSql($index->getColumns())
. ')';
}
/**
* Obtain DBMS specific SQL code portion needed to set an index
* declaration to be used in statements like CREATE TABLE.
*
* @param string $name name of the index
* @param Index $index index definition
* @return string DBMS specific SQL code portion needed to set an index
*/
public function getIndexDeclarationSql($name, Index $index)
{
$type = '';
if($index->isUnique()) {
$type = 'UNIQUE ';
}
if (count($index->getColumns()) == 0) {
throw \InvalidArgumentException("Incomplete definition. 'columns' required.");
}
return $type . 'INDEX ' . $name . ' ('
. $this->getIndexFieldDeclarationListSql($index->getColumns())
. ')';
}
/**
* getIndexFieldDeclarationList
* Obtain DBMS specific SQL code portion needed to set an index
* declaration to be used in statements like CREATE TABLE.
*
* @return string
*/
public function getIndexFieldDeclarationListSql(array $fields)
{
$ret = array();
foreach ($fields as $field => $definition) {
if (is_array($definition)) {
$ret[] = $field;
} else {
$ret[] = $definition;
}
}
return implode(', ', $ret);
}
/**
* A method to return the required SQL string that fits between CREATE ... TABLE
* to create the table as a temporary table.
*
* Should be overridden in driver classes to return the correct string for the
* specific database type.
*
* The default is to return the string "TEMPORARY" - this will result in a
* SQL error for any database that does not support temporary tables, or that
* requires a different SQL command from "CREATE TEMPORARY TABLE".
*
* @return string The string required to be placed between "CREATE" and "TABLE"
* to generate a temporary table, if possible.
*/
public function getTemporaryTableSql()
{
return 'TEMPORARY';
}
/**
* Get sql query to show a list of database
*
* @return unknown
*/
public function getShowDatabasesSql()
{
throw DBALException::notSupported(__METHOD__);
}
/**
* getForeignKeyDeclaration
* Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
* of a field declaration to be used in statements like CREATE TABLE.
*
* @param array $definition an associative array with the following structure:
* name optional constraint name
*
* local the local field(s)
*
* foreign the foreign reference field(s)
*
* foreignTable the name of the foreign table
*
* onDelete referential delete action
*
* onUpdate referential update action
*
* deferred deferred constraint checking
*
* The onDelete and onUpdate keys accept the following values:
*
* CASCADE: Delete or update the row from the parent table and automatically delete or
* update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported.
* Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column
* in the parent table or in the child table.
*
* SET NULL: Delete or update the row from the parent table and set the foreign key column or columns in the
* child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier
* specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported.
*
* NO ACTION: In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary
* key value is not allowed to proceed if there is a related foreign key value in the referenced table.
*
* RESTRICT: Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as
* omitting the ON DELETE or ON UPDATE clause.
*
* SET DEFAULT
*
* @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
* of a field declaration.
*/
public function getForeignKeyDeclarationSql(ForeignKeyConstraint $foreignKey)
{
$sql = $this->getForeignKeyBaseDeclarationSql($foreignKey);
$sql .= $this->getAdvancedForeignKeyOptionsSql($foreignKey);
return $sql;
}
/**
* Return the FOREIGN KEY query section dealing with non-standard options
* as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
*
* @param ForeignKeyConstraint $foreignKey foreign key definition
* @return string
*/
public function getAdvancedForeignKeyOptionsSql(ForeignKeyConstraint $foreignKey)
{
$query = '';
if ($this->supportsForeignKeyOnUpdate() && $foreignKey->hasOption('onUpdate')) {
$query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSql($foreignKey->getOption('onUpdate'));
}
if ($foreignKey->hasOption('onDelete')) {
$query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSql($foreignKey->getOption('onDelete'));
}
return $query;
}
/**
* returns given referential action in uppercase if valid, otherwise throws
* an exception
*
* @throws Doctrine_Exception_Exception if unknown referential action given
* @param string $action foreign key referential action
* @param string foreign key referential action in uppercase
*/
public function getForeignKeyReferentialActionSql($action)
{
$upper = strtoupper($action);
switch ($upper) {
case 'CASCADE':
case 'SET NULL':
case 'NO ACTION':
case 'RESTRICT':
case 'SET DEFAULT':
return $upper;
break;
default:
throw \InvalidArgumentException('Invalid foreign key action: ' . $upper);
}
}
/**
* Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
* of a field declaration to be used in statements like CREATE TABLE.
*
* @param ForeignKeyConstraint $foreignKey
* @return string
*/
public function getForeignKeyBaseDeclarationSql(ForeignKeyConstraint $foreignKey)
{
$sql = '';
if (strlen($foreignKey->getName())) {
$sql .= 'CONSTRAINT ' . $foreignKey->getName() . ' ';
}
$sql .= 'FOREIGN KEY (';
if (count($foreignKey->getLocalColumns()) == 0) {
throw new \InvalidArgumentException("Incomplete definition. 'local' required.");
}
if (count($foreignKey->getForeignColumns()) == 0) {
throw new \InvalidArgumentException("Incomplete definition. 'foreign' required.");
}
if (strlen($foreignKey->getForeignTableName()) == 0) {
throw new \InvalidArgumentException("Incomplete definition. 'foreignTable' required.");
}
$sql .= implode(', ', $foreignKey->getLocalColumns())
. ') REFERENCES '
. $foreignKey->getForeignTableName() . '('
. implode(', ', $foreignKey->getForeignColumns()) . ')';
return $sql;
}
/**
* Obtain DBMS specific SQL code portion needed to set the UNIQUE constraint
* of a field declaration to be used in statements like CREATE TABLE.
*
* @return string DBMS specific SQL code portion needed to set the UNIQUE constraint
* of a field declaration.
*/
public function getUniqueFieldDeclarationSql()
{
return 'UNIQUE';
}
/**
* Obtain DBMS specific SQL code portion needed to set the CHARACTER SET
* of a field declaration to be used in statements like CREATE TABLE.
*
* @param string $charset name of the charset
* @return string DBMS specific SQL code portion needed to set the CHARACTER SET
* of a field declaration.
*/
public function getColumnCharsetDeclarationSql($charset)
{
return '';
}
/**
* Obtain DBMS specific SQL code portion needed to set the COLLATION
* of a field declaration to be used in statements like CREATE TABLE.
*
* @param string $collation name of the collation
* @return string DBMS specific SQL code portion needed to set the COLLATION
* of a field declaration.
*/
public function getColumnCollationDeclarationSql($collation)
{
return '';
}
/**
* build a pattern matching string
*
* EXPERIMENTAL
*
* WARNING: this function is experimental and may change signature at
* any time until labelled as non-experimental
*
* @access public
*
* @param array $pattern even keys are strings, odd are patterns (% and _)
* @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future)
* @param string $field optional field name that is being matched against
* (might be required when emulating ILIKE)
*
* @return string SQL pattern
*/
public function getMatchPatternExpression($pattern, $operator = null, $field = null)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Whether the platform prefers sequences for ID generation.
* Subclasses should override this method to return TRUE if they prefer sequences.
*
* @return boolean
*/
public function prefersSequences()
{
return false;
}
/**
* Whether the platform prefers identity columns (eg. autoincrement) for ID generation.
* Subclasses should override this method to return TRUE if they prefer identity columns.
*
* @return boolean
*/
public function prefersIdentityColumns()
{
return false;
}
/**
* Some platforms need the boolean values to be converted.
*
* The default conversion in this implementation converts to integers (false => 0, true => 1).
*
* @param mixed $item
*/
public function convertBooleans($item)
{
if (is_array($item)) {
foreach ($item as $k => $value) {
if (is_bool($value)) {
$item[$k] = (int) $value;
}
}
} else if (is_bool($item)) {
$item = (int) $item;
}
return $item;
}
/**
* Gets the SQL statement specific for the platform to set the charset.
*
* This function is MySQL specific and required by
* {@see \Doctrine\DBAL\Connection::setCharset($charset)}
*
* @param string $charset
* @return string
*/
public function getSetCharsetSql($charset)
{
return "SET NAMES '".$charset."'";
}
/**
* Gets the SQL specific for the platform to get the current date.
*
* @return string
*/
public function getCurrentDateSql()
{
return 'CURRENT_DATE';
}
/**
* Gets the SQL specific for the platform to get the current time.
*
* @return string
*/
public function getCurrentTimeSql()
{
return 'CURRENT_TIME';
}
/**
* Gets the SQL specific for the platform to get the current timestamp
*
* @return string
*/
public function getCurrentTimestampSql()
{
return 'CURRENT_TIMESTAMP';
}
/**
* Get sql for transaction isolation level Connection constant
*
* @param integer $level
*/
protected function _getTransactionIsolationLevelSql($level)
{
switch ($level) {
case Connection::TRANSACTION_READ_UNCOMMITTED:
return 'READ UNCOMMITTED';
case Connection::TRANSACTION_READ_COMMITTED:
return 'READ COMMITTED';
case Connection::TRANSACTION_REPEATABLE_READ:
return 'REPEATABLE READ';
case Connection::TRANSACTION_SERIALIZABLE:
return 'SERIALIZABLE';
default:
throw new \InvalidArgumentException('Invalid isolation level:' . $level);
}
}
public function getListDatabasesSql()
{
throw DBALException::notSupported(__METHOD__);
}
public function getListFunctionsSql()
{
throw DBALException::notSupported(__METHOD__);
}
public function getListTriggersSql($table = null)
{
throw DBALException::notSupported(__METHOD__);
}
public function getListSequencesSql($database)
{
throw DBALException::notSupported(__METHOD__);
}
public function getListTableConstraintsSql($table)
{
throw DBALException::notSupported(__METHOD__);
}
public function getListTableColumnsSql($table)
{
throw DBALException::notSupported(__METHOD__);
}
public function getListTablesSql()
{
throw DBALException::notSupported(__METHOD__);
}
public function getListUsersSql()
{
throw DBALException::notSupported(__METHOD__);
}
public function getListViewsSql()
{
throw DBALException::notSupported(__METHOD__);
}
public function getListTableIndexesSql($table)
{
throw DBALException::notSupported(__METHOD__);
}
public function getListTableForeignKeysSql($table)
{
throw DBALException::notSupported(__METHOD__);
}
public function getCreateViewSql($name, $sql)
{
throw DBALException::notSupported(__METHOD__);
}
public function getDropViewSql($name)
{
throw DBALException::notSupported(__METHOD__);
}
public function getDropSequenceSql($sequence)
{
throw DBALException::notSupported(__METHOD__);
}
public function getSequenceNextValSql($sequenceName)
{
throw DBALException::notSupported(__METHOD__);
}
public function getCreateDatabaseSql($database)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Get sql to set the transaction isolation level
*
* @param integer $level
*/
public function getSetTransactionIsolationSql($level)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Obtain DBMS specific SQL code portion needed to set the CHARACTER SET
* of a field declaration to be used in statements like CREATE TABLE.
*
* @param string $charset name of the charset
* @return string DBMS specific SQL code portion needed to set the CHARACTER SET
* of a field declaration.
*/
public function getCharsetFieldDeclaration($charset)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Obtain DBMS specific SQL to be used to create datetime fields in
* statements like CREATE TABLE
*
* @param array $fieldDeclaration
* @return string
*/
public function getDateTimeTypeDeclarationSql(array $fieldDeclaration)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Obtain DBMS specific SQL to be used to create date fields in statements
* like CREATE TABLE.
*
* @param array $fieldDeclaration
* @return string
*/
public function getDateTypeDeclarationSql(array $fieldDeclaration)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Obtain DBMS specific SQL to be used to create time fields in statements
* like CREATE TABLE.
*
* @param array $fieldDeclaration
* @return string
*/
public function getTimeTypeDeclarationSql(array $fieldDeclaration)
{
throw DoctrineException::getTimeTypeDeclarationNotSupported($this);
}
/**
* Gets the default transaction isolation level of the platform.
*
* @return integer The default isolation level.
* @see Doctrine\DBAL\Connection\TRANSACTION_* constants.
*/
public function getDefaultTransactionIsolationLevel()
{
return Connection::TRANSACTION_READ_COMMITTED;
}
/* supports*() metods */
/**
* Whether the platform supports sequences.
*
* @return boolean
*/
public function supportsSequences()
{
return false;
}
/**
* Whether the platform supports identity columns.
* Identity columns are columns that recieve an auto-generated value from the
* database on insert of a row.
*
* @return boolean
*/
public function supportsIdentityColumns()
{
return false;
}
/**
* Whether the platform supports indexes.
*
* @return boolean
*/
public function supportsIndexes()
{
return true;
}
public function supportsAlterTable()
{
return true;
}
/**
* Whether the platform supports transactions.
*
* @return boolean
*/
public function supportsTransactions()
{
return true;
}
/**
* Whether the platform supports savepoints.
*
* @return boolean
*/
public function supportsSavepoints()
{
return true;
}
/**
* Whether the platform supports primary key constraints.
*
* @return boolean
*/
public function supportsPrimaryConstraints()
{
return true;
}
/**
* Does the platform supports foreign key constraints?
*
* @return boolean
*/
public function supportsForeignKeyConstraints()
{
return true;
}
/**
* Does this platform supports onUpdate in foreign key constraints?
*
* @return bool
*/
public function supportsForeignKeyOnUpdate()
{
return ($this->supportsForeignKeyConstraints() && true);
}
/**
* Whether the platform supports database schemas.
*
* @return boolean
*/
public function supportsSchemas()
{
return false;
}
/**
* @return bool
*/
public function createsExplicitIndexForForeignKeys()
{
return false;
}
/**
* Whether the platform supports getting the affected rows of a recent
* update/delete type query.
*
* @return boolean
*/
public function supportsGettingAffectedRows()
{
return true;
}
public function getIdentityColumnNullInsertSql()
{
return "";
}
/**
* Gets the format string, as accepted by the date() function, that describes
* the format of a stored datetime value of this platform.
*
* @return string The format string.
*
* @todo We need to get the specific format for each dbms and override this
* function for each platform
*/
public function getDateTimeFormatString()
{
return 'Y-m-d H:i:s';
}
/**
* Gets the format string, as accepted by the date() function, that describes
* the format of a stored date value of this platform.
*
* @return string The format string.
*/
public function getDateFormatString()
{
return 'Y-m-d';
}
/**
* Gets the format string, as accepted by the date() function, that describes
* the format of a stored time value of this platform.
*
* @return string The format string.
*/
public function getTimeFormatString()
{
return 'H:i:s';
}
public function modifyLimitQuery($query, $limit, $offset = null)
{
if ( ! is_null($limit)) {
$query .= ' LIMIT ' . $limit;
}
if ( ! is_null($offset)) {
$query .= ' OFFSET ' . $offset;
}
return $query;
}
/**
* Gets the SQL snippet used to declare a VARCHAR column type.
*
* @param array $field
*/
abstract public function getVarcharTypeDeclarationSql(array $field);
/**
* Gets the SQL snippet used to declare a CLOB column type.
*
* @param array $field
*/
abstract public function getClobTypeDeclarationSql(array $field);
/**
* Gets the name of the platform.
*
* @return string
*/
abstract public function getName();
/**
* Gets the character casing of a column in an SQL result set of this platform.
*
* @param string $column The column name for which to get the correct character casing.
* @return string The column name in the character casing used in SQL result sets.
*/
public function getSqlResultCasing($column)
{
return $column;
}
/**
* Makes any fixes to a name of a schema element (table, sequence, ...) that are required
* by restrictions of the platform, like a maximum length.
*
* @param string $schemaName
* @return string
*/
public function fixSchemaElementName($schemaElementName)
{
return $schemaElementName;
}
/**
* Maximum length of any given databse identifier, like tables or column names.
*
* @return int
*/
public function getMaxIdentifierLength()
{
return 63;
}
/**
* Get the insert sql for an empty insert statement
*
* @param string $tableName
* @param string $identifierColumnName
* @return string $sql
*/
public function getEmptyIdentityInsertSql($tableName, $identifierColumnName)
{
return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (null)';
}
}
......@@ -80,6 +80,11 @@ class Column extends AbstractAsset
*/
protected $_platformOptions = array();
/**
* @var string
*/
protected $_columnDefinition = null;
/**
* Create a new Column
*
......@@ -226,6 +231,17 @@ class Column extends AbstractAsset
return $this;
}
/**
*
* @param string
* @return Column
*/
public function setColumnDefinition($value)
{
$this->_columnDefinition = $value;
return $this;
}
public function getType()
{
return $this->_type;
......@@ -281,6 +297,11 @@ class Column extends AbstractAsset
return $this->_platformOptions[$name];
}
public function getColumnDefinition()
{
return $this->_columnDefinition;
}
/**
* @param Visitor $visitor
*/
......@@ -304,6 +325,7 @@ class Column extends AbstractAsset
'scale' => $this->_scale,
'fixed' => $this->_fixed,
'unsigned' => $this->_unsigned,
'columnDefinition' => $this->_columnDefinition,
), $this->_platformOptions);
}
}
\ No newline at end of file
......@@ -200,6 +200,10 @@ class AnnotationDriver implements Driver
$mapping['columnName'] = $columnAnnot->name;
}
if (isset($columnAnnot->columnDefinition)) {
$mapping['columnDefinition'] = $columnAnnot->columnDefinition;
}
if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) {
$mapping['id'] = true;
}
......
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Common\DoctrineException,
Doctrine\Common\Cache\ArrayCache,
Doctrine\Common\Annotations\AnnotationReader,
Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\MappingException;
require __DIR__ . '/DoctrineAnnotations.php';
/**
* The AnnotationDriver reads the mapping metadata from docblock annotations.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class AnnotationDriver implements Driver
{
/** The AnnotationReader. */
private $_reader;
private $_classDirectory;
/**
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
* docblock annotations.
*
* @param AnnotationReader $reader The AnnotationReader to use.
*/
public function __construct(AnnotationReader $reader, $classDirectory = null)
{
$this->_reader = $reader;
$this->_classDirectory = $classDirectory;
}
public function setClassDirectory($classDirectory)
{
$this->_classDirectory = $classDirectory;
}
/**
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
{
$class = $metadata->getReflectionClass();
$classAnnotations = $this->_reader->getClassAnnotations($class);
// Evaluate Entity annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) {
$entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity'];
$metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
} else if (isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'])) {
$metadata->isMappedSuperclass = true;
} else {
throw DoctrineException::classIsNotAValidEntityOrMappedSuperClass($className);
}
// Evaluate DoctrineTable annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\Table'])) {
$tableAnnot = $classAnnotations['Doctrine\ORM\Mapping\Table'];
$primaryTable = array(
'name' => $tableAnnot->name,
'schema' => $tableAnnot->schema
);
if ($tableAnnot->indexes !== null) {
foreach ($tableAnnot->indexes as $indexAnnot) {
$primaryTable['indexes'][$indexAnnot->name] = array(
'columns' => $indexAnnot->columns
);
}
}
if ($tableAnnot->uniqueConstraints !== null) {
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraint) {
$primaryTable['uniqueConstraints'][$uniqueConstraint->name] = array(
'columns' => $uniqueConstraint->columns
);
}
}
$metadata->setPrimaryTable($primaryTable);
}
// Evaluate InheritanceType annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\InheritanceType'])) {
$inheritanceTypeAnnot = $classAnnotations['Doctrine\ORM\Mapping\InheritanceType'];
$metadata->setInheritanceType(constant('\Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value));
}
// Evaluate DiscriminatorColumn annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn'])) {
$discrColumnAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn'];
$metadata->setDiscriminatorColumn(array(
'name' => $discrColumnAnnot->name,
'type' => $discrColumnAnnot->type,
'length' => $discrColumnAnnot->length
));
}
// Evaluate DiscriminatorMap annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'])) {
$discrMapAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'];
$metadata->setDiscriminatorMap($discrMapAnnot->value);
}
// Evaluate DoctrineChangeTrackingPolicy annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\ChangeTrackingPolicy'])) {
$changeTrackingAnnot = $classAnnotations['Doctrine\ORM\Mapping\ChangeTrackingPolicy'];
$metadata->setChangeTrackingPolicy($changeTrackingAnnot->value);
}
// Evaluate annotations on properties/fields
foreach ($class->getProperties() as $property) {
if ($metadata->isMappedSuperclass && ! $property->isPrivate()
||
$metadata->isInheritedField($property->name)
||
$metadata->isInheritedAssociation($property->name)) {
continue;
}
$mapping = array();
$mapping['fieldName'] = $property->getName();
// Check for JoinColummn/JoinColumns annotations
$joinColumns = array();
if ($joinColumnAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumn')) {
$joinColumns[] = array(
'name' => $joinColumnAnnot->name,
'referencedColumnName' => $joinColumnAnnot->referencedColumnName,
'unique' => $joinColumnAnnot->unique,
'nullable' => $joinColumnAnnot->nullable,
'onDelete' => $joinColumnAnnot->onDelete,
'onUpdate' => $joinColumnAnnot->onUpdate
);
} else if ($joinColumnsAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinColumns')) {
foreach ($joinColumnsAnnot->value as $joinColumn) {
$joinColumns[] = array(
'name' => $joinColumn->name,
'referencedColumnName' => $joinColumn->referencedColumnName,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'onUpdate' => $joinColumn->onUpdate
);
}
}
// Field can only be annotated with one of:
// @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany
if ($columnAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Column')) {
if ($columnAnnot->type == null) {
throw DoctrineException::propertyTypeIsRequired($property->getName());
}
$mapping['type'] = $columnAnnot->type;
$mapping['length'] = $columnAnnot->length;
$mapping['precision'] = $columnAnnot->precision;
$mapping['scale'] = $columnAnnot->scale;
$mapping['nullable'] = $columnAnnot->nullable;
$mapping['unique'] = $columnAnnot->unique;
if ($columnAnnot->options) {
$mapping['options'] = $columnAnnot->options;
}
if (isset($columnAnnot->default)) {
$mapping['default'] = $columnAnnot->default;
}
if (isset($columnAnnot->name)) {
$mapping['columnName'] = $columnAnnot->name;
}
if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) {
$mapping['id'] = true;
}
if ($generatedValueAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\GeneratedValue')) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAnnot->strategy));
}
if ($versionAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Version')) {
$metadata->setVersionMapping($mapping);
}
$metadata->mapField($mapping);
// Check for SequenceGenerator/TableGenerator definition
if ($seqGeneratorAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\SequenceGenerator')) {
$metadata->setSequenceGeneratorDefinition(array(
'sequenceName' => $seqGeneratorAnnot->sequenceName,
'allocationSize' => $seqGeneratorAnnot->allocationSize,
'initialValue' => $seqGeneratorAnnot->initialValue
));
} else if ($tblGeneratorAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\TableGenerator')) {
throw DoctrineException::tableIdGeneratorNotImplemented();
}
} else if ($oneToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) {
$mapping['targetEntity'] = $oneToOneAnnot->targetEntity;
$mapping['joinColumns'] = $joinColumns;
$mapping['mappedBy'] = $oneToOneAnnot->mappedBy;
$mapping['cascade'] = $oneToOneAnnot->cascade;
$mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $oneToOneAnnot->fetch);
$metadata->mapOneToOne($mapping);
} else if ($oneToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToMany')) {
$mapping['mappedBy'] = $oneToManyAnnot->mappedBy;
$mapping['targetEntity'] = $oneToManyAnnot->targetEntity;
$mapping['cascade'] = $oneToManyAnnot->cascade;
$mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $oneToManyAnnot->fetch);
$metadata->mapOneToMany($mapping);
} else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) {
$mapping['joinColumns'] = $joinColumns;
$mapping['cascade'] = $manyToOneAnnot->cascade;
$mapping['targetEntity'] = $manyToOneAnnot->targetEntity;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $manyToOneAnnot->fetch);
$metadata->mapManyToOne($mapping);
} else if ($manyToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToMany')) {
$joinTable = array();
if ($joinTableAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\JoinTable')) {
$joinTable = array(
'name' => $joinTableAnnot->name,
'schema' => $joinTableAnnot->schema
);
foreach ($joinTableAnnot->joinColumns as $joinColumn) {
$joinTable['joinColumns'][] = array(
'name' => $joinColumn->name,
'referencedColumnName' => $joinColumn->referencedColumnName,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'onUpdate' => $joinColumn->onUpdate
);
}
foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
$joinTable['inverseJoinColumns'][] = array(
'name' => $joinColumn->name,
'referencedColumnName' => $joinColumn->referencedColumnName,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'onUpdate' => $joinColumn->onUpdate
);
}
}
$mapping['joinTable'] = $joinTable;
$mapping['targetEntity'] = $manyToManyAnnot->targetEntity;
$mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
$mapping['cascade'] = $manyToManyAnnot->cascade;
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $manyToManyAnnot->fetch);
$metadata->mapManyToMany($mapping);
}
}
// Evaluate HasLifecycleCallbacks annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\HasLifecycleCallbacks'])) {
foreach ($class->getMethods() as $method) {
if ($method->isPublic()) {
$annotations = $this->_reader->getMethodAnnotations($method);
if (isset($annotations['Doctrine\ORM\Mapping\PrePersist'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::prePersist);
}
if (isset($annotations['Doctrine\ORM\Mapping\PostPersist'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postPersist);
}
if (isset($annotations['Doctrine\ORM\Mapping\PreUpdate'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preUpdate);
}
if (isset($annotations['Doctrine\ORM\Mapping\PostUpdate'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postUpdate);
}
if (isset($annotations['Doctrine\ORM\Mapping\PreRemove'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preRemove);
}
if (isset($annotations['Doctrine\ORM\Mapping\PostRemove'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postRemove);
}
if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad);
}
}
}
}
}
/**
* Whether the class with the specified name should have its metadata loaded.
* This is only the case if it is annotated with either @Entity or
* @MappedSuperclass in the class doc block.
*
* @param string $className
* @return boolean
*/
public function isTransient($className)
{
$classAnnotations = $this->_reader->getClassAnnotations(new \ReflectionClass($className));
return ! isset($classAnnotations['Doctrine\ORM\Mapping\Entity']) &&
! isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass']);
}
/**
* {@inheritDoc}
*/
public function getAllClassNames()
{
if ($this->_classDirectory) {
$iter = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->_classDirectory),
\RecursiveIteratorIterator::LEAVES_ONLY);
$declared = get_declared_classes();
foreach ($iter as $item) {
$info = pathinfo($item->getPathName());
if ( ! isset($info['extension']) || $info['extension'] != 'php') {
continue;
}
require_once $item->getPathName();
}
$declared = array_diff(get_declared_classes(), $declared);
$classes = array();
foreach ($declared as $className) {
if ( ! $this->isTransient($className)) {
$classes[] = $className;
}
}
return $classes;
} else {
return array();
}
}
}
\ No newline at end of file
......@@ -63,6 +63,7 @@ final class Column extends Annotation {
public $default; //TODO: remove?
public $name;
public $options = array();
public $columnDefinition;
}
final class OneToOne extends Annotation {
public $targetEntity;
......
......@@ -159,6 +159,10 @@ class XmlDriver extends AbstractFileDriver
$metadata->setVersionMapping($mapping);
}
if (isset($fieldMapping['columnDefinition'])) {
$mapping['columnDefinition'] = (string)$fieldMapping['columnDefinition'];
}
$metadata->mapField($mapping);
}
}
......
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\MappingException;
/**
* XmlDriver is a metadata driver that enables mapping through XML files.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class XmlDriver extends AbstractFileDriver
{
protected $_fileExtension = '.dcm.xml';
/**
* Loads the metadata for the specified class into the provided container.
*
* @param string $className
* @param ClassMetadata $metadata
*/
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
{
$xmlRoot = $this->getElement($className);
if ($xmlRoot->getName() == 'entity') {
$metadata->setCustomRepositoryClass(
isset($xmlRoot['repository-class']) ? (string)$xmlRoot['repository-class'] : null
);
} else if ($xmlRoot->getName() == 'mapped-superclass') {
$metadata->isMappedSuperclass = true;
} else {
throw DoctrineException::classIsNotAValidEntityOrMapperSuperClass($className);
}
// Evaluate <entity...> attributes
if (isset($xmlRoot['table'])) {
$metadata->primaryTable['name'] = (string)$xmlRoot['table'];
}
if (isset($xmlRoot['schema'])) {
$metadata->primaryTable['schema'] = (string)$xmlRoot['schema'];
}
if (isset($xmlRoot['inheritance-type'])) {
$metadata->setInheritanceType((string)$xmlRoot['inheritance-type']);
}
// Evaluate <discriminator-column...>
if (isset($xmlRoot->{'discriminator-column'})) {
$discrColumn = $xmlRoot->{'discriminator-column'};
$metadata->setDiscriminatorColumn(array(
'name' => (string)$discrColumn['name'],
'type' => (string)$discrColumn['type'],
'length' => (string)$discrColumn['length']
));
}
// Evaluate <discriminator-map...>
if (isset($xmlRoot->{'discriminator-map'})) {
$metadata->setDiscriminatorMap((array)$xmlRoot->{'discriminator-map'});
}
// Evaluate <change-tracking-policy...>
if (isset($xmlRoot->{'change-tracking-policy'})) {
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_'
. strtoupper((string)$xmlRoot->{'change-tracking-policy'})));
}
// Evaluate <indexes...>
if (isset($xmlRoot->indexes)) {
foreach ($xmlRoot->indexes->index as $index) {
if (is_string($index['columns'])) {
$columns = explode(',', $index['columns']);
} else {
$columns = $index['columns'];
}
$metadata->primaryTable['indexes'][$index['name']] = array(
'columns' => $columns
);
}
}
// Evaluate <unique-constraints..>
if (isset($xmlRoot->{'unique-constraints'})) {
foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $unique) {
if (is_string($unique['columns'])) {
$columns = explode(',', $unique['columns']);
} else {
$columns = $unique['columns'];
}
$metadata->primaryTable['uniqueConstraints'][$unique['name']] = array(
'columns' => $columns
);
}
}
// Evaluate <field ...> mappings
if (isset($xmlRoot->field)) {
foreach ($xmlRoot->field as $fieldMapping) {
$mapping = array(
'fieldName' => (string)$fieldMapping['name'],
'type' => (string)$fieldMapping['type']
);
if (isset($fieldMapping['column'])) {
$mapping['columnName'] = (string)$fieldMapping['column'];
}
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'];
}
if (isset($fieldMapping['unique'])) {
$mapping['unique'] = (bool)$fieldMapping['unique'];
}
if (isset($fieldMapping['options'])) {
$mapping['options'] = (array)$fieldMapping['options'];
}
if (isset($fieldMapping['version']) && $fieldMapping['version']) {
$metadata->setVersionMapping($mapping);
}
$metadata->mapField($mapping);
}
}
// Evaluate <id ...> mappings
foreach ($xmlRoot->id as $idElement) {
$mapping = array(
'id' => true,
'fieldName' => (string)$idElement['name'],
'type' => (string)$idElement['type']
);
if (isset($idElement['column'])) {
$mapping['columnName'] = (string)$idElement['column'];
}
$metadata->mapField($mapping);
if (isset($idElement->generator)) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_'
. strtoupper((string)$idElement->generator['strategy'])));
}
// Check for SequenceGenerator/TableGenerator definition
if (isset($idElement->{'sequence-generator'})) {
$seqGenerator = $idElement->{'sequence-generator'};
$metadata->setSequenceGeneratorDefinition(array(
'sequenceName' => $seqGenerator->{'sequence-name'},
'allocationSize' => $seqGenerator->{'allocation-size'},
'initialValue' => $seqGeneratorAnnot->{'initial-value'}
));
} else if (isset($idElement->{'table-generator'})) {
throw DoctrineException::tableIdGeneratorNotImplemented();
}
}
// Evaluate <one-to-one ...> mappings
if (isset($xmlRoot->{'one-to-one'})) {
foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
$mapping = array(
'fieldName' => (string)$oneToOneElement['field'],
'targetEntity' => (string)$oneToOneElement['target-entity']
);
if (isset($oneToOneElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . (string)$oneToOneElement['fetch']);
}
if (isset($oneToOneElement['mapped-by'])) {
$mapping['mappedBy'] = (string)$oneToOneElement['mapped-by'];
} else {
$joinColumns = array();
if (isset($oneToOneElement->{'join-column'})) {
$joinColumns[] = $this->_getJoinColumnMapping($oneToOneElement->{'join-column'});
} else if (isset($oneToOneElement->{'join-columns'})) {
foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
$joinColumns[] = $this->_getJoinColumnMapping($joinColumnElement);
}
} else {
throw MappingException::invalidMapping($mapping['fieldName']);
}
$mapping['joinColumns'] = $joinColumns;
}
if (isset($oneToOneElement->cascade)) {
$mapping['cascade'] = $this->_getCascadeMappings($oneToOneElement->cascade);
}
if (isset($oneToOneElement->{'orphan-removal'})) {
$mapping['orphanRemoval'] = (bool)$oneToOneElement->{'orphan-removal'};
}
$metadata->mapOneToOne($mapping);
}
}
// Evaluate <one-to-many ...> mappings
if (isset($xmlRoot->{'one-to-many'})) {
foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
$mapping = array(
'fieldName' => (string)$oneToManyElement['field'],
'targetEntity' => (string)$oneToManyElement['target-entity'],
'mappedBy' => (string)$oneToManyElement['mapped-by']
);
if (isset($oneToManyElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . (string)$oneToManyElement['fetch']);
}
if (isset($oneToManyElement->cascade)) {
$mapping['cascade'] = $this->_getCascadeMappings($oneToManyElement->cascade);
}
if (isset($oneToManyElement->{'orphan-removal'})) {
$mapping['orphanRemoval'] = (bool)$oneToManyElement->{'orphan-removal'};
}
$metadata->mapOneToMany($mapping);
}
}
// Evaluate <many-to-one ...> mappings
if (isset($xmlRoot->{'many-to-one'})) {
foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
$mapping = array(
'fieldName' => (string)$manyToOneElement['field'],
'targetEntity' => (string)$manyToOneElement['target-entity']
);
if (isset($manyToOneElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . (string)$manyToOneElement['fetch']);
}
$joinColumns = array();
if (isset($manyToOneElement->{'join-column'})) {
$joinColumns[] = $this->_getJoinColumnMapping($manyToOneElement->{'join-column'});
} else if (isset($manyToOneElement->{'join-columns'})) {
foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
if (!isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $name;
}
$joinColumns[] = $this->_getJoinColumnMapping($joinColumnElement);
}
} else {
throw MappingException::invalidMapping($mapping['fieldName']);
}
$mapping['joinColumns'] = $joinColumns;
if (isset($manyToOneElement->cascade)) {
$mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade);
}
if (isset($manyToOneElement->{'orphan-removal'})) {
$mapping['orphanRemoval'] = (bool)$manyToOneElement->{'orphan-removal'};
}
$metadata->mapManyToOne($mapping);
}
}
// Evaluate <many-to-many ...> mappings
if (isset($xmlRoot->{'many-to-many'})) {
foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
$mapping = array(
'fieldName' => (string)$manyToManyElement['field'],
'targetEntity' => (string)$manyToManyElement['target-entity']
);
if (isset($manyToManyElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . (string)$manyToManyElement['fetch']);
}
if (isset($manyToManyElement['mapped-by'])) {
$mapping['mappedBy'] = (string)$manyToManyElement['mapped-by'];
} else if (isset($manyToManyElement->{'join-table'})) {
$joinTableElement = $manyToManyElement->{'join-table'};
$joinTable = array(
'name' => (string)$joinTableElement['name']
);
if (isset($joinTableElement['schema'])) {
$joinTable['schema'] = (string)$joinTableElement['schema'];
}
foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
$joinTable['joinColumns'][] = $this->_getJoinColumnMapping($joinColumnElement);
}
foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
$joinTable['inverseJoinColumns'][] = $this->_getJoinColumnMapping($joinColumnElement);
}
$mapping['joinTable'] = $joinTable;
} else {
throw MappingException::invalidMapping($mapping['fieldName']);
}
if (isset($manyToManyElement->cascade)) {
$mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement->cascade);
}
if (isset($manyToManyElement->{'orphan-removal'})) {
$mapping['orphanRemoval'] = (bool)$manyToManyElement->{'orphan-removal'};
}
$metadata->mapManyToMany($mapping);
}
}
// Evaluate <lifecycle-callbacks...>
if (isset($xmlRoot->{'lifecycle-callbacks'})) {
foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
$metadata->addLifecycleCallback((string)$lifecycleCallback['method'], constant('\Doctrine\ORM\Events::' . (string)$lifecycleCallback['type']));
}
}
}
/**
* Loads a mapping file with the given name and returns a map
* from class/entity names to their corresponding SimpleXMLElement nodes.
*
* @param string $file The mapping file to load.
* @return array
*/
protected function _loadMappingFile($file)
{
$result = array();
$xmlElement = simplexml_load_file($file);
if (isset($xmlElement->entity)) {
foreach ($xmlElement->entity as $entityElement) {
$entityName = (string)$entityElement['name'];
$result[$entityName] = $entityElement;
}
} else if (isset($xmlElement->{'mapped-superclass'})) {
foreach ($xmlElement->{'mapped-superclass'} as $mapperSuperClass) {
$className = (string)$mappedSuperClass['name'];
$result[$className] = $mappedSuperClass;
}
}
return $result;
}
/**
* Constructs a joinColumn mapping array based on the information
* found in the given SimpleXMLElement.
*
* @param $joinColumnElement The XML element.
* @return array The mapping array.
*/
private function _getJoinColumnMapping(\SimpleXMLElement $joinColumnElement)
{
$joinColumn = array(
'name' => (string)$joinColumnElement['name'],
'referencedColumnName' => (string)$joinColumnElement['referenced-column-name']
);
if (isset($joinColumnElement['unique'])) {
$joinColumn['unique'] = (bool)$joinColumnElement['unique'];
}
if (isset($joinColumnElement['nullable'])) {
$joinColumn['nullable'] = (bool)$joinColumnElement['nullable'];
}
if (isset($joinColumnElement['onDelete'])) {
$joinColumn['onDelete'] = (string)$joinColumnElement['on-delete'];
}
if (isset($joinColumnElement['onUpdate'])) {
$joinColumn['onUpdate'] = (string)$joinColumnElement['on-update'];
}
return $joinColumn;
}
/**
* Gathers a list of cascade options found in the given cascade element.
*
* @param $cascadeElement The cascade element.
* @return array The list of cascade options.
*/
private function _getCascadeMappings($cascadeElement)
{
$cascades = array();
if (isset($cascadeElement->{'cascade-persist'})) {
$cascades[] = 'persist';
}
if (isset($cascadeElement->{'cascade-remove'})) {
$cascades[] = 'remove';
}
if (isset($cascadeElement->{'cascade-merge'})) {
$cascades[] = 'merge';
}
if (isset($cascadeElement->{'cascade-refresh'})) {
$cascades[] = 'refresh';
}
return $cascades;
}
}
\ No newline at end of file
......@@ -204,6 +204,9 @@ class YamlDriver extends AbstractFileDriver
if (isset($fieldMapping['version']) && $fieldMapping['version']) {
$metadata->setVersionMapping($mapping);
}
if (isset($fieldMapping['columnDefinition'])) {
$mapping['columnDefinition'] = $fieldMapping['columnDefinition'];
}
$metadata->mapField($mapping);
}
......
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\Common\DoctrineException,
Doctrine\ORM\Mapping\MappingException;
if ( ! class_exists('sfYaml', false)) {
require_once __DIR__ . '/../../../../vendor/sfYaml/sfYaml.class.php';
require_once __DIR__ . '/../../../../vendor/sfYaml/sfYamlDumper.class.php';
require_once __DIR__ . '/../../../../vendor/sfYaml/sfYamlInline.class.php';
require_once __DIR__ . '/../../../../vendor/sfYaml/sfYamlParser.class.php';
}
/**
* The YamlDriver reads the mapping metadata from yaml schema files.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class YamlDriver extends AbstractFileDriver
{
protected $_fileExtension = '.dcm.yml';
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
{
$element = $this->getElement($className);
if ($element['type'] == 'entity') {
$metadata->setCustomRepositoryClass(
isset($element['repositoryClass']) ? $element['repositoryClass'] : null
);
} else if ($element['type'] == 'mappedSuperclass') {
$metadata->isMappedSuperclass = true;
} else {
throw DoctrineException::classIsNotAValidEntityOrMapperSuperClass($className);
}
// Evaluate root level properties
if (isset($element['table'])) {
$metadata->primaryTable['name'] = $element['table'];
}
if (isset($element['schema'])) {
$metadata->primaryTable['schema'] = $element['schema'];
}
if (isset($element['inheritanceType'])) {
$metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType'])));
}
// Evaluate discriminatorColumn
if (isset($element['discriminatorColumn'])) {
$discrColumn = $element['discriminatorColumn'];
$metadata->setDiscriminatorColumn(array(
'name' => $discrColumn['name'],
'type' => $discrColumn['type'],
'length' => $discrColumn['length']
));
}
// Evaluate discriminatorMap
if (isset($element['discriminatorMap'])) {
$metadata->setDiscriminatorMap($element['discriminatorMap']);
}
// Evaluate changeTrackingPolicy
if (isset($element['changeTrackingPolicy'])) {
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_'
. strtoupper($element['changeTrackingPolicy'])));
}
// Evaluate indexes
if (isset($element['indexes'])) {
foreach ($element['indexes'] as $name => $index) {
if ( ! isset($index['name'])) {
$index['name'] = $name;
}
if (is_string($index['columns'])) {
$columns = explode(',', $index['columns']);
} else {
$columns = $index['columns'];
}
$metadata->primaryTable['indexes'][$index['name']] = array(
'columns' => $columns
);
}
}
// Evaluate uniqueConstraints
if (isset($element['uniqueConstraints'])) {
foreach ($element['uniqueConstraints'] as $name => $unique) {
if ( ! isset($unique['name'])) {
$unique['name'] = $name;
}
if (is_string($unique['columns'])) {
$columns = explode(',', $unique['columns']);
} else {
$columns = $unique['columns'];
}
$metadata->primaryTable['uniqueConstraints'][$unique['name']] = array(
'columns' => $columns
);
}
}
if (isset($element['id'])) {
// Evaluate identifier settings
foreach ($element['id'] as $name => $idElement) {
$mapping = array(
'id' => true,
'fieldName' => $name,
'type' => $idElement['type']
);
if (isset($idElement['column'])) {
$mapping['columnName'] = $idElement['column'];
}
$metadata->mapField($mapping);
if (isset($idElement['generator'])) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_'
. strtoupper($idElement['generator']['strategy'])));
}
}
}
// Evaluate fields
if (isset($element['fields'])) {
foreach ($element['fields'] as $name => $fieldMapping) {
$e = explode('(', $fieldMapping['type']);
$fieldMapping['type'] = $e[0];
if (isset($e[1])) {
$fieldMapping['length'] = substr($e[1], 0, strlen($e[1]) - 1);
}
$mapping = array(
'fieldName' => $name,
'type' => $fieldMapping['type']
);
if (isset($fieldMapping['id'])) {
$mapping['id'] = true;
if (isset($fieldMapping['generator']['strategy'])) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_'
. strtoupper($fieldMapping['generator']['strategy'])));
}
}
// Check for SequenceGenerator/TableGenerator definition
if (isset($fieldMapping['sequenceGenerator'])) {
$metadata->setSequenceGeneratorDefinition($fieldMapping['sequenceGenerator']);
} else if (isset($fieldMapping['tableGenerator'])) {
throw DoctrineException::tableIdGeneratorNotImplemented();
}
if (isset($fieldMapping['column'])) {
$mapping['columnName'] = $fieldMapping['column'];
}
if (isset($fieldMapping['length'])) {
$mapping['length'] = $fieldMapping['length'];
}
if (isset($fieldMapping['precision'])) {
$mapping['precision'] = $fieldMapping['precision'];
}
if (isset($fieldMapping['scale'])) {
$mapping['scale'] = $fieldMapping['scale'];
}
if (isset($fieldMapping['unique'])) {
$mapping['unique'] = (bool)$fieldMapping['unique'];
}
if (isset($fieldMapping['options'])) {
$mapping['options'] = $fieldMapping['options'];
}
if (isset($fieldMapping['nullable'])) {
$mapping['nullable'] = $fieldMapping['nullable'];
}
if (isset($fieldMapping['version']) && $fieldMapping['version']) {
$metadata->setVersionMapping($mapping);
}
$metadata->mapField($mapping);
}
}
// Evaluate oneToOne relationships
if (isset($element['oneToOne'])) {
foreach ($element['oneToOne'] as $name => $oneToOneElement) {
$mapping = array(
'fieldName' => $name,
'targetEntity' => $oneToOneElement['targetEntity']
);
if (isset($oneToOneElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $oneToOneElement['fetch']);
}
if (isset($oneToOneElement['mappedBy'])) {
$mapping['mappedBy'] = $oneToOneElement['mappedBy'];
} else {
$joinColumns = array();
if (isset($oneToOneElement['joinColumn'])) {
$joinColumns[] = $this->_getJoinColumnMapping($oneToOneElement['joinColumn']);
} else if (isset($oneToOneElement['joinColumns'])) {
foreach ($oneToOneElement['joinColumns'] as $name => $joinColumnElement) {
if (!isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $name;
}
$joinColumns[] = $this->_getJoinColumnMapping($joinColumnElement);
}
} else {
throw MappingException::invalidMapping($mapping['fieldName']);
}
$mapping['joinColumns'] = $joinColumns;
}
if (isset($oneToOneElement['cascade'])) {
$mapping['cascade'] = $this->_getCascadeMappings($oneToOneElement['cascade']);
}
$metadata->mapOneToOne($mapping);
}
}
// Evaluate oneToMany relationships
if (isset($element['oneToMany'])) {
foreach ($element['oneToMany'] as $name => $oneToManyElement) {
$mapping = array(
'fieldName' => $name,
'targetEntity' => $oneToManyElement['targetEntity'],
'mappedBy' => $oneToManyElement['mappedBy']
);
if (isset($oneToManyElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $oneToManyElement['fetch']);
}
if (isset($oneToManyElement['cascade'])) {
$mapping['cascade'] = $this->_getCascadeMappings($oneToManyElement['cascade']);
}
$metadata->mapOneToMany($mapping);
}
}
// Evaluate manyToOne relationships
if (isset($element['manyToOne'])) {
foreach ($element['manyToOne'] as $name => $manyToOneElement) {
$mapping = array(
'fieldName' => $name,
'targetEntity' => $manyToOneElement['targetEntity']
);
if (isset($manyToOneElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $manyToOneElement['fetch']);
}
$joinColumns = array();
if (isset($manyToOneElement['joinColumn'])) {
$joinColumns[] = $this->_getJoinColumnMapping($manyToOneElement['joinColumn']);
} else if (isset($manyToOneElement['joinColumns'])) {
foreach ($manyToOneElement['joinColumns'] as $name => $joinColumnElement) {
if (!isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $name;
}
$joinColumns[] = $this->_getJoinColumnMapping($joinColumnElement);
}
} else {
throw MappingException::invalidMapping($mapping['fieldName']);
}
$mapping['joinColumns'] = $joinColumns;
if (isset($manyToOneElement['cascade'])) {
$mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement['cascade']);
}
$metadata->mapManyToOne($mapping);
}
}
// Evaluate manyToMany relationships
if (isset($element['manyToMany'])) {
foreach ($element['manyToMany'] as $name => $manyToManyElement) {
$mapping = array(
'fieldName' => $name,
'targetEntity' => $manyToManyElement['targetEntity']
);
if (isset($manyToManyElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $manyToManyElement['fetch']);
}
if (isset($manyToManyElement['mappedBy'])) {
$mapping['mappedBy'] = $manyToManyElement['mappedBy'];
} else if (isset($manyToManyElement['joinTable'])) {
$joinTableElement = $manyToManyElement['joinTable'];
$joinTable = array(
'name' => $joinTableElement['name']
);
if (isset($joinTableElement['schema'])) {
$joinTable['schema'] = $joinTableElement['schema'];
}
foreach ($joinTableElement['joinColumns'] as $name => $joinColumnElement) {
if (!isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $name;
}
$joinTable['joinColumns'][] = $this->_getJoinColumnMapping($joinColumnElement);
}
foreach ($joinTableElement['inverseJoinColumns'] as $name => $joinColumnElement) {
if (!isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $name;
}
$joinTable['inverseJoinColumns'][] = $this->_getJoinColumnMapping($joinColumnElement);
}
$mapping['joinTable'] = $joinTable;
} else {
throw MappingException::invalidMapping($mapping['fieldName']);
}
if (isset($manyToManyElement['cascade'])) {
$mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement['cascade']);
}
$metadata->mapManyToMany($mapping);
}
}
// Evaluate lifeCycleCallbacks
if (isset($element['lifecycleCallbacks'])) {
foreach ($element['lifecycleCallbacks'] as $method => $type) {
$metadata->addLifecycleCallback($method, constant('\Doctrine\ORM\Events::' . $type));
}
}
}
/**
* Constructs a joinColumn mapping array based on the information
* found in the given join column element.
*
* @param $joinColumnElement The array join column element
* @return array The mapping array.
*/
private function _getJoinColumnMapping($joinColumnElement)
{
$joinColumn = array(
'name' => $joinColumnElement['name'],
'referencedColumnName' => $joinColumnElement['referencedColumnName']
);
if (isset($joinColumnElement['fieldName'])) {
$joinColumn['fieldName'] = (string) $joinColumnElement['fieldName'];
}
if (isset($joinColumnElement['unique'])) {
$joinColumn['unique'] = (bool) $joinColumnElement['unique'];
}
if (isset($joinColumnElement['nullable'])) {
$joinColumn['nullable'] = (bool) $joinColumnElement['nullable'];
}
if (isset($joinColumnElement['onDelete'])) {
$joinColumn['onDelete'] = $joinColumnElement['onDelete'];
}
if (isset($joinColumnElement['onUpdate'])) {
$joinColumn['onUpdate'] = $joinColumnElement['onUpdate'];
}
return $joinColumn;
}
/**
* Gathers a list of cascade options found in the given cascade element.
*
* @param $cascadeElement The cascade element.
* @return array The list of cascade options.
*/
private function _getCascadeMappings($cascadeElement)
{
$cascades = array();
if (isset($cascadeElement['cascadePersist'])) {
$cascades[] = 'persist';
}
if (isset($cascadeElement['cascadeRemove'])) {
$cascades[] = 'remove';
}
if (isset($cascadeElement['cascadeMerge'])) {
$cascades[] = 'merge';
}
if (isset($cascadeElement['cascadeRefresh'])) {
$cascades[] = 'refresh';
}
return $cascades;
}
/**
* Loads a mapping file with the given name and returns a map
* from class/entity names to their corresponding elements.
*
* @param string $file The mapping file to load.
* @return array
*/
protected function _loadMappingFile($file)
{
return \sfYaml::load($file);
}
}
\ No newline at end of file
......@@ -306,6 +306,10 @@ class SchemaTool
if (isset($mapping['default'])) {
$options['default'] = $mapping['default'];
}
if (isset($mapping['columnDefinition'])) {
$options['columnDefinition'] = $mapping['columnDefinition'];
}
if ($table->hasColumn($columnName)) {
// required in some inheritence scenarios
......@@ -392,8 +396,9 @@ class SchemaTool
foreach ($joinColumns as $joinColumn) {
// Note that this thing might be quoted, i.e. `foo`, [foo], ...
$columnName = $mapping->getQuotedJoinColumnName($joinColumn['name'], $this->_platform);
$referencedFieldName = $class->getFieldName($joinColumn['referencedColumnName']);
if (!$class->hasField($class->getFieldName($joinColumn['referencedColumnName']))) {
if (!$class->hasField($referencedFieldName)) {
throw new \Doctrine\Common\DoctrineException(
"Column name `".$joinColumn['referencedColumnName']."` referenced for relation from ".
"$mapping->sourceEntityName towards $mapping->targetEntityName does not exist."
......@@ -408,8 +413,13 @@ class SchemaTool
// Only add the column to the table if it does not exist already.
// It might exist already if the foreign key is mapped into a regular
// property as well.
$fieldMapping = $class->getFieldMapping($referencedFieldName);
$columnDef = isset($fieldMapping['columnDefinition']) ? $fieldMapping['columnDefinition'] : null;
$columnOptions = array('notnull' => false, 'columnDefinition' => $columnDef);
$theJoinTable->createColumn(
$columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), array('notnull' => false)
$columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), $columnOptions
);
}
......
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Tools;
use Doctrine\DBAL\Types\Type,
Doctrine\ORM\EntityManager,
Doctrine\ORM\Internal\CommitOrderCalculator;
/**
* The SchemaTool is a tool to create/drop/update database schemas based on
* <tt>ClassMetadata</tt> class descriptors.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @version $Revision$
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class SchemaTool
{
/**
* @var string
*/
const DROP_METADATA = "metadata";
/**
* @var string
*/
const DROP_DATABASE = "database";
/**
* @var \Doctrine\ORM\EntityManager
*/
private $_em;
/**
* @var \Doctrine\DBAL\Platforms\AbstractPlatform
*/
private $_platform;
/**
* Initializes a new SchemaTool instance that uses the connection of the
* provided EntityManager.
*
* @param Doctrine\ORM\EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->_em = $em;
$this->_platform = $em->getConnection()->getDatabasePlatform();
}
/**
* Creates the database schema for the given array of ClassMetadata instances.
*
* @param array $classes
*/
public function createSchema(array $classes)
{
$createSchemaSql = $this->getCreateSchemaSql($classes);
$conn = $this->_em->getConnection();
foreach ($createSchemaSql as $sql) {
$conn->execute($sql);
}
}
/**
* 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 The SQL statements needed to create the schema for the classes.
*/
public function getCreateSchemaSql(array $classes)
{
$schema = $this->getSchemaFromMetadata($classes);
return $schema->toSql($this->_platform);
}
/**
* From a given set of metadata classes this method creates a Schema instance.
*
* @param array $classes
* @return Schema
*/
public function getSchemaFromMetadata(array $classes)
{
$processedClasses = array(); // Reminder for processed classes, used for hierarchies
$sm = $this->_em->getConnection()->getSchemaManager();
$schema = new \Doctrine\DBAL\Schema\Schema(array(), array(), $sm->createSchemaConfig());
foreach ($classes as $class) {
if (isset($processedClasses[$class->name]) || $class->isMappedSuperclass) {
continue;
}
$table = $schema->createTable($class->getQuotedTableName($this->_platform));
if ($class->isIdGeneratorIdentity()) {
$table->setIdGeneratorType(\Doctrine\DBAL\Schema\Table::ID_IDENTITY);
} else if ($class->isIdGeneratorSequence()) {
$table->setIdGeneratorType(\Doctrine\DBAL\Schema\Table::ID_SEQUENCE);
}
$columns = array(); // table columns
if ($class->isInheritanceTypeSingleTable()) {
$columns = $this->_gatherColumns($class, $table);
$this->_gatherRelationsSql($class, $table, $schema);
// Add the discriminator column
$discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table);
// Aggregate all the information from all classes in the hierarchy
foreach ($class->parentClasses as $parentClassName) {
// Parent class information is already contained in this class
$processedClasses[$parentClassName] = true;
}
foreach ($class->subClasses as $subClassName) {
$subClass = $this->_em->getClassMetadata($subClassName);
$this->_gatherColumns($subClass, $table);
$this->_gatherRelationsSql($subClass, $table, $schema);
$processedClasses[$subClassName] = true;
}
} else if ($class->isInheritanceTypeJoined()) {
// Add all non-inherited fields as columns
$pkColumns = array();
foreach ($class->fieldMappings as $fieldName => $mapping) {
if ( ! isset($mapping['inherited'])) {
$columnName = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform);
$this->_gatherColumn($class, $mapping, $table);
if ($class->isIdentifier($fieldName)) {
$pkColumns[] = $columnName;
}
}
}
$this->_gatherRelationsSql($class, $table, $schema);
// Add the discriminator column only to the root table
if ($class->name == $class->rootEntityName) {
$discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table);
} else {
// Add an ID FK column to child tables
/* @var Doctrine\ORM\Mapping\ClassMetadata $class */
$idMapping = $class->fieldMappings[$class->identifier[0]];
$this->_gatherColumn($class, $idMapping, $table);
$columnName = $class->getQuotedColumnName($class->identifier[0], $this->_platform);
$pkColumns[] = $columnName;
if ($table->isIdGeneratorIdentity()) {
$table->setIdGeneratorType(\Doctrine\DBAL\Schema\Table::ID_NONE);
}
// Add a FK constraint on the ID column
$table->addUnnamedForeignKeyConstraint(
$this->_em->getClassMetadata($class->rootEntityName)->getQuotedTableName($this->_platform),
array($columnName), array($columnName), array('onDelete' => 'CASCADE')
);
}
$table->setPrimaryKey($pkColumns);
} else if ($class->isInheritanceTypeTablePerClass()) {
throw DoctrineException::notSupported();
} else {
$this->_gatherColumns($class, $table);
$this->_gatherRelationsSql($class, $table, $schema);
}
if (isset($class->primaryTable['indexes'])) {
foreach ($class->primaryTable['indexes'] AS $indexName => $indexData) {
$table->addIndex($indexData['columns'], $indexName);
}
}
if (isset($class->primaryTable['uniqueConstraints'])) {
foreach ($class->primaryTable['uniqueConstraints'] AS $indexName => $indexData) {
$table->addUniqueIndex($indexData['columns'], $indexName);
}
}
$processedClasses[$class->name] = true;
if ($class->isIdGeneratorSequence() && $class->name == $class->rootEntityName) {
$seqDef = $class->getSequenceGeneratorDefinition();
if (!$schema->hasSequence($seqDef['sequenceName'])) {
$schema->createSequence(
$seqDef['sequenceName'],
$seqDef['allocationSize'],
$seqDef['initialValue']
);
}
}
}
return $schema;
}
/**
* 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, $table)
{
$discrColumn = $class->discriminatorColumn;
$table->createColumn(
$class->getQuotedDiscriminatorColumnName($this->_platform),
$discrColumn['type'],
array('length' => $discrColumn['length'], 'notnull' => true)
);
}
/**
* Gathers the column definitions as required by the DBAL of all field mappings
* found in the given class.
*
* @param ClassMetadata $class
* @param Table $table
* @return array The list of portable column definitions as required by the DBAL.
*/
private function _gatherColumns($class, $table)
{
$columns = array();
$pkColumns = array();
foreach ($class->fieldMappings as $fieldName => $mapping) {
$column = $this->_gatherColumn($class, $mapping, $table);
if ($class->isIdentifier($mapping['fieldName'])) {
$pkColumns[] = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform);
}
}
// For now, this is a hack required for single table inheritence, since this method is called
// twice by single table inheritence relations
if(!$table->hasIndex('primary')) {
$table->setPrimaryKey($pkColumns);
}
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 Table $table
* @return array The portable column definition as required by the DBAL.
*/
private function _gatherColumn($class, array $mapping, $table)
{
$columnName = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform);
$columnType = $mapping['type'];
$options = array();
$options['length'] = isset($mapping['length']) ? $mapping['length'] : null;
$options['notnull'] = isset($mapping['nullable']) ? ! $mapping['nullable'] : true;
$options['platformOptions'] = array();
$options['platformOptions']['version'] = $class->isVersioned && $class->versionField == $mapping['fieldName'] ? true : false;
if(strtolower($columnType) == 'string' && $options['length'] === null) {
$options['length'] = 255;
}
if (isset($mapping['precision'])) {
$options['precision'] = $mapping['precision'];
}
if (isset($mapping['scale'])) {
$options['scale'] = $mapping['scale'];
}
if (isset($mapping['default'])) {
$options['default'] = $mapping['default'];
}
if ($table->hasColumn($columnName)) {
// required in some inheritence scenarios
$table->changeColumn($columnName, $options);
} else {
$table->createColumn($columnName, $columnType, $options);
}
$isUnique = isset($mapping['unique']) ? $mapping['unique'] : false;
if ($isUnique) {
$table->addUniqueIndex(array($columnName));
}
}
/**
* 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, $table, $schema)
{
foreach ($class->associationMappings as $fieldName => $mapping) {
if (isset($class->inheritedAssociationFields[$fieldName])) {
continue;
}
$foreignClass = $this->_em->getClassMetadata($mapping->targetEntityName);
if ($mapping->isOneToOne() && $mapping->isOwningSide) {
$primaryKeyColumns = $uniqueConstraints = array(); // unnecessary for this relation-type
$this->_gatherRelationJoinColumns($mapping->getJoinColumns(), $table, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints);
} else if ($mapping->isOneToMany() && $mapping->isOwningSide) {
//... create join table, one-many through join table supported later
throw DoctrineException::notSupported();
} else if ($mapping->isManyToMany() && $mapping->isOwningSide) {
// create join table
$joinTable = $mapping->getJoinTable();
$theJoinTable = $schema->createTable($mapping->getQuotedJoinTableName($this->_platform));
$primaryKeyColumns = $uniqueConstraints = array();
// Build first FK constraint (relation table => source table)
$this->_gatherRelationJoinColumns($joinTable['joinColumns'], $theJoinTable, $class, $mapping, $primaryKeyColumns, $uniqueConstraints);
// Build second FK constraint (relation table => target table)
$this->_gatherRelationJoinColumns($joinTable['inverseJoinColumns'], $theJoinTable, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints);
foreach($uniqueConstraints AS $indexName => $unique) {
$theJoinTable->addUniqueIndex(
$unique['columns'], is_numeric($indexName) ? null : $indexName
);
}
$theJoinTable->setPrimaryKey($primaryKeyColumns);
}
}
}
/**
* Gather columns and fk constraints that are required for one part of relationship.
*
* @param array $joinColumns
* @param \Doctrine\DBAL\Schema\Table $theJoinTable
* @param ClassMetadata $class
* @param \Doctrine\ORM\Mapping\AssociationMapping $mapping
* @param array $primaryKeyColumns
* @param array $uniqueConstraints
*/
private function _gatherRelationJoinColumns($joinColumns, $theJoinTable, $class, $mapping, &$primaryKeyColumns, &$uniqueConstraints)
{
$localColumns = array();
$foreignColumns = array();
$fkOptions = array();
foreach ($joinColumns as $joinColumn) {
// Note that this thing might be quoted, i.e. `foo`, [foo], ...
$columnName = $mapping->getQuotedJoinColumnName($joinColumn['name'], $this->_platform);
if (!$class->hasField($class->getFieldName($joinColumn['referencedColumnName']))) {
throw new \Doctrine\Common\DoctrineException(
"Column name `".$joinColumn['referencedColumnName']."` referenced for relation from ".
"$mapping->sourceEntityName towards $mapping->targetEntityName does not exist."
);
}
$primaryKeyColumns[] = $columnName;
$localColumns[] = $columnName;
$foreignColumns[] = $joinColumn['referencedColumnName'];
if ( ! $theJoinTable->hasColumn($joinColumn['name'])) {
// Only add the column to the table if it does not exist already.
// It might exist already if the foreign key is mapped into a regular
// property as well.
$theJoinTable->createColumn(
$columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), array('notnull' => false)
);
}
if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) {
$uniqueConstraints[] = array('columns' => $columnName);
}
if (isset($joinColumn['onUpdate'])) {
$fkOptions['onUpdate'] = $joinColumn['onUpdate'];
}
if (isset($joinColumn['onDelete'])) {
$fkOptions['onDelete'] = $joinColumn['onDelete'];
}
}
$theJoinTable->addUnnamedForeignKeyConstraint(
$class->getQuotedTableName($this->_platform), $localColumns, $foreignColumns, $fkOptions
);
}
/**
* Drops the database schema for the given classes.
*
* In any way when an exception is thrown it is supressed since drop was
* issued for all classes of the schema and some probably just don't exist.
*
* @param array $classes
* @param string $mode
* @return void
*/
public function dropSchema(array $classes, $mode=self::DROP_METADATA)
{
$dropSchemaSql = $this->getDropSchemaSql($classes, $mode);
$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
* @param string $mode
* @return array
*/
public function getDropSchemaSql(array $classes)
{
$sm = $this->_em->getConnection()->getSchemaManager();
$schema = $sm->createSchema();
$visitor = new \Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector($this->_platform);
/* @var $schema \Doctrine\DBAL\Schema\Schema */
$schema->visit($visitor);
return $visitor->getQueries();
}
/**
* Drop all tables of the database connection.
*
* @return array
*/
private function _getDropSchemaTablesDatabaseMode($classes)
{
$conn = $this->_em->getConnection();
$sm = $conn->getSchemaManager();
/* @var $sm \Doctrine\DBAL\Schema\AbstractSchemaManager */
$allTables = $sm->listTables();
$orderedTables = $this->_getDropSchemaTablesMetadataMode($classes);
foreach($allTables AS $tableName) {
if(!in_array($tableName, $orderedTables)) {
$orderedTables[] = $tableName;
}
}
return $orderedTables;
}
private function _getDropSchemaTablesMetadataMode(array $classes)
{
$orderedTables = array();
$commitOrder = $this->_getCommitOrder($classes);
$associationTables = $this->_getAssociationTables($commitOrder);
// Drop association tables first
foreach ($associationTables as $associationTable) {
$orderedTables[] = $associationTable;
}
// Drop tables in reverse commit order
for ($i = count($commitOrder) - 1; $i >= 0; --$i) {
$class = $commitOrder[$i];
if (($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName)
|| $class->isMappedSuperclass) {
continue;
}
$orderedTables[] = $class->getTableName();
}
//TODO: Drop other schema elements, like sequences etc.
return $orderedTables;
}
/**
* 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, $saveMode=false)
{
$updateSchemaSql = $this->getUpdateSchemaSql($classes, $saveMode);
$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, $saveMode=false)
{
$sm = $this->_em->getConnection()->getSchemaManager();
$fromSchema = $sm->createSchema();
$toSchema = $this->getSchemaFromMetadata($classes);
$comparator = new \Doctrine\DBAL\Schema\Comparator();
$schemaDiff = $comparator->compare($fromSchema, $toSchema);
if ($saveMode) {
return $schemaDiff->toSaveSql($this->_platform);
} else {
return $schemaDiff->toSql($this->_platform);
}
}
private function _getCommitOrder(array $classes)
{
$calc = new CommitOrderCalculator;
// Calculate dependencies
foreach ($classes as $class) {
$calc->addClass($class);
foreach ($class->associationMappings as $assoc) {
if ($assoc->isOwningSide) {
$targetClass = $this->_em->getClassMetadata($assoc->targetEntityName);
if ( ! $calc->hasClass($targetClass->name)) {
$calc->addClass($targetClass);
}
// add dependency ($targetClass before $class)
$calc->addDependency($targetClass, $class);
}
}
}
return $calc->getCommitOrder();
}
private function _getAssociationTables(array $classes)
{
$associationTables = array();
foreach ($classes as $class) {
foreach ($class->associationMappings as $assoc) {
if ($assoc->isOwningSide && $assoc->isManyToMany()) {
$associationTables[] = $assoc->joinTable['name'];
}
}
}
return $associationTables;
}
}
......@@ -4,6 +4,9 @@ namespace Doctrine\Tests\DBAL\Platforms;
abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase
{
/**
* @var Doctrine\DBAL\Platforms\AbstractPlatform
*/
protected $_platform;
abstract public function createPlatform();
......@@ -125,4 +128,10 @@ abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase
$this->assertEquals($expectedSql, $sql);
}
public function testGetCustomColumnDeclarationSql()
{
$field = array('columnDefinition' => 'MEDIUMINT(6) UNSIGNED');
$this->assertEquals('foo MEDIUMINT(6) UNSIGNED', $this->_platform->getColumnDeclarationSql('foo', $field));
}
}
......@@ -44,6 +44,7 @@ class ColumnTest extends \PHPUnit_Framework_TestCase
'scale' => 2,
'fixed' => true,
'unsigned' => true,
'columnDefinition' => null,
'foo' => 'bar',
);
......
......@@ -18,10 +18,11 @@ class AllTests
public static function suite()
{
$suite = new \Doctrine\Tests\DoctrineTestSuite('Doctrine Orm Hydration');
$suite = new \Doctrine\Tests\DoctrineTestSuite('Doctrine Orm Tools');
$suite->addTestSuite('Doctrine\Tests\ORM\Tools\Export\ClassMetadataExporterTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Tools\ConvertDoctrine1SchemaTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Tools\SchemaToolTest');
return $suite;
}
......
......@@ -32,4 +32,28 @@ class SchemaToolTest extends \Doctrine\Tests\OrmTestCase
array_map('strtolower', $schema->getTable('cms_users')->getIndex('cms_users_username_uniq')->getColumns())
);
}
/**
* @group DDC-200
*/
public function testPassColumnDefinitionToJoinColumn()
{
$customColumnDef = "MEDIUMINT(6) UNSIGNED NOT NULL";
$em = $this->_getTestEntityManager();
$schemaTool = new SchemaTool($em);
$avatar = $em->getClassMetadata('Doctrine\Tests\Models\Forum\ForumAvatar');
$avatar->fieldMappings['id']['columnDefinition'] = $customColumnDef;
$user = $em->getClassMetadata('Doctrine\Tests\Models\Forum\ForumUser');
$classes = array($avatar, $user);
$schema = $schemaTool->getSchemaFromMetadata($classes);
$this->assertTrue($schema->hasTable('forum_users'));
$table = $schema->getTable("forum_users");
$this->assertTrue($table->hasColumn('avatar_id'));
$this->assertEquals($customColumnDef, $table->getColumn('avatar_id')->getColumnDefinition());
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment