vendor/doctrine-dbal/lib/Doctrine/DBAL/Schema/Comparator.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     1 <?php
       
     2 /*
       
     3  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
       
     4  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
       
     5  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
       
     6  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
       
     7  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
       
     8  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
       
     9  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
       
    10  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
       
    11  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    12  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
       
    13  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    14  *
       
    15  * This software consists of voluntary contributions made by many individuals
       
    16  * and is licensed under the LGPL. For more information, see
       
    17  * <http://www.doctrine-project.org>.
       
    18  */
       
    19 
       
    20 namespace Doctrine\DBAL\Schema;
       
    21 
       
    22 /**
       
    23  * Compare to Schemas and return an instance of SchemaDiff
       
    24  *
       
    25  * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
       
    26  * @license http://ez.no/licenses/new_bsd New BSD License
       
    27  * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
       
    28  * @link    www.doctrine-project.org
       
    29  * @since   2.0
       
    30  * @version $Revision$
       
    31  * @author  Benjamin Eberlei <kontakt@beberlei.de>
       
    32  */
       
    33 class Comparator
       
    34 {
       
    35     /**
       
    36      * @param Schema $fromSchema
       
    37      * @param Schema $toSchema
       
    38      * @return SchemaDiff
       
    39      */
       
    40     static public function compareSchemas( Schema $fromSchema, Schema $toSchema )
       
    41     {
       
    42         $c = new self();
       
    43         return $c->compare($fromSchema, $toSchema);
       
    44     }
       
    45 
       
    46     /**
       
    47      * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema.
       
    48      *
       
    49      * The returned diferences are returned in such a way that they contain the
       
    50      * operations to change the schema stored in $fromSchema to the schema that is
       
    51      * stored in $toSchema.
       
    52      *
       
    53      * @param Schema $fromSchema
       
    54      * @param Schema $toSchema
       
    55      *
       
    56      * @return SchemaDiff
       
    57      */
       
    58     public function compare(Schema $fromSchema, Schema $toSchema)
       
    59     {
       
    60         $diff = new SchemaDiff();
       
    61 
       
    62         $foreignKeysToTable = array();
       
    63 
       
    64         foreach ( $toSchema->getTables() AS $tableName => $table ) {
       
    65             if ( !$fromSchema->hasTable($tableName) ) {
       
    66                 $diff->newTables[$tableName] = $table;
       
    67             } else {
       
    68                 $tableDifferences = $this->diffTable( $fromSchema->getTable($tableName), $table );
       
    69                 if ( $tableDifferences !== false ) {
       
    70                     $diff->changedTables[$tableName] = $tableDifferences;
       
    71                 }
       
    72             }
       
    73         }
       
    74 
       
    75         /* Check if there are tables removed */
       
    76         foreach ( $fromSchema->getTables() AS $tableName => $table ) {
       
    77             if ( !$toSchema->hasTable($tableName) ) {
       
    78                 $diff->removedTables[$tableName] = $table;
       
    79             }
       
    80 
       
    81             // also remember all foreign keys that point to a specific table
       
    82             foreach ($table->getForeignKeys() AS $foreignKey) {
       
    83                 $foreignTable = strtolower($foreignKey->getForeignTableName());
       
    84                 if (!isset($foreignKeysToTable[$foreignTable])) {
       
    85                     $foreignKeysToTable[$foreignTable] = array();
       
    86                 }
       
    87                 $foreignKeysToTable[$foreignTable][] = $foreignKey;
       
    88             }
       
    89         }
       
    90 
       
    91         foreach ($diff->removedTables AS $tableName => $table) {
       
    92             if (isset($foreignKeysToTable[$tableName])) {
       
    93                 $diff->orphanedForeignKeys = array_merge($diff->orphanedForeignKeys, $foreignKeysToTable[$tableName]);
       
    94             }
       
    95         }
       
    96 
       
    97         foreach ( $toSchema->getSequences() AS $sequenceName => $sequence) {
       
    98             if (!$fromSchema->hasSequence($sequenceName)) {
       
    99                 $diff->newSequences[] = $sequence;
       
   100             } else {
       
   101                 if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) {
       
   102                     $diff->changedSequences[] = $toSchema->getSequence($sequenceName);
       
   103                 }
       
   104             }
       
   105         }
       
   106 
       
   107         foreach ($fromSchema->getSequences() AS $sequenceName => $sequence) {
       
   108             if (!$toSchema->hasSequence($sequenceName)) {
       
   109                 $diff->removedSequences[] = $sequence;
       
   110             }
       
   111         }
       
   112 
       
   113         return $diff;
       
   114     }
       
   115 
       
   116     /**
       
   117      *
       
   118      * @param Sequence $sequence1
       
   119      * @param Sequence $sequence2
       
   120      */
       
   121     public function diffSequence(Sequence $sequence1, Sequence $sequence2)
       
   122     {
       
   123         if($sequence1->getAllocationSize() != $sequence2->getAllocationSize()) {
       
   124             return true;
       
   125         }
       
   126 
       
   127         if($sequence1->getInitialValue() != $sequence2->getInitialValue()) {
       
   128             return true;
       
   129         }
       
   130 
       
   131         return false;
       
   132     }
       
   133 
       
   134     /**
       
   135      * Returns the difference between the tables $table1 and $table2.
       
   136      *
       
   137      * If there are no differences this method returns the boolean false.
       
   138      *
       
   139      * @param Table $table1
       
   140      * @param Table $table2
       
   141      *
       
   142      * @return bool|TableDiff
       
   143      */
       
   144     public function diffTable(Table $table1, Table $table2)
       
   145     {
       
   146         $changes = 0;
       
   147         $tableDifferences = new TableDiff($table1->getName());
       
   148 
       
   149         $table1Columns = $table1->getColumns();
       
   150         $table2Columns = $table2->getColumns();
       
   151 
       
   152         /* See if all the fields in table 1 exist in table 2 */
       
   153         foreach ( $table2Columns as $columnName => $column ) {
       
   154             if ( !$table1->hasColumn($columnName) ) {
       
   155                 $tableDifferences->addedColumns[$columnName] = $column;
       
   156                 $changes++;
       
   157             }
       
   158         }
       
   159         /* See if there are any removed fields in table 2 */
       
   160         foreach ( $table1Columns as $columnName => $column ) {
       
   161             if ( !$table2->hasColumn($columnName) ) {
       
   162                 $tableDifferences->removedColumns[$columnName] = $column;
       
   163                 $changes++;
       
   164             }
       
   165         }
       
   166         
       
   167         foreach ( $table1Columns as $columnName => $column ) {
       
   168             if ( $table2->hasColumn($columnName) ) {
       
   169                 $changedProperties = $this->diffColumn( $column, $table2->getColumn($columnName) );
       
   170                 if (count($changedProperties) ) {
       
   171                     $columnDiff = new ColumnDiff($column->getName(), $table2->getColumn($columnName), $changedProperties);
       
   172                     $tableDifferences->changedColumns[$column->getName()] = $columnDiff;
       
   173                     $changes++;
       
   174                 }
       
   175             }
       
   176         }
       
   177 
       
   178         $this->detectColumnRenamings($tableDifferences);
       
   179 
       
   180         $table1Indexes = $table1->getIndexes();
       
   181         $table2Indexes = $table2->getIndexes();
       
   182 
       
   183         foreach ($table2Indexes AS $index2Name => $index2Definition) {
       
   184             foreach ($table1Indexes AS $index1Name => $index1Definition) {
       
   185                 if ($this->diffIndex($index1Definition, $index2Definition) === false) {
       
   186                     unset($table1Indexes[$index1Name]);
       
   187                     unset($table2Indexes[$index2Name]);
       
   188                 } else {
       
   189                     if ($index1Name == $index2Name) {
       
   190                         $tableDifferences->changedIndexes[$index2Name] = $table2Indexes[$index2Name];
       
   191                         unset($table1Indexes[$index1Name]);
       
   192                         unset($table2Indexes[$index2Name]);
       
   193                         $changes++;
       
   194                     }
       
   195                 }
       
   196             }
       
   197         }
       
   198 
       
   199         foreach ($table1Indexes AS $index1Name => $index1Definition) {
       
   200             $tableDifferences->removedIndexes[$index1Name] = $index1Definition;
       
   201             $changes++;
       
   202         }
       
   203 
       
   204         foreach ($table2Indexes AS $index2Name => $index2Definition) {
       
   205             $tableDifferences->addedIndexes[$index2Name] = $index2Definition;
       
   206             $changes++;
       
   207         }
       
   208 
       
   209         $fromFkeys = $table1->getForeignKeys();
       
   210         $toFkeys = $table2->getForeignKeys();
       
   211 
       
   212         foreach ($fromFkeys AS $key1 => $constraint1) {
       
   213             foreach ($toFkeys AS $key2 => $constraint2) {
       
   214                 if($this->diffForeignKey($constraint1, $constraint2) === false) {
       
   215                     unset($fromFkeys[$key1]);
       
   216                     unset($toFkeys[$key2]);
       
   217                 } else {
       
   218                     if (strtolower($constraint1->getName()) == strtolower($constraint2->getName())) {
       
   219                         $tableDifferences->changedForeignKeys[] = $constraint2;
       
   220                         $changes++;
       
   221                         unset($fromFkeys[$key1]);
       
   222                         unset($toFkeys[$key2]);
       
   223                     }
       
   224                 }
       
   225             }
       
   226         }
       
   227 
       
   228         foreach ($fromFkeys AS $key1 => $constraint1) {
       
   229             $tableDifferences->removedForeignKeys[] = $constraint1;
       
   230             $changes++;
       
   231         }
       
   232 
       
   233         foreach ($toFkeys AS $key2 => $constraint2) {
       
   234             $tableDifferences->addedForeignKeys[] = $constraint2;
       
   235             $changes++;
       
   236         }
       
   237 
       
   238         return $changes ? $tableDifferences : false;
       
   239     }
       
   240 
       
   241     /**
       
   242      * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
       
   243      * however ambiguouties between different possibilites should not lead to renaming at all.
       
   244      * 
       
   245      * @param TableDiff $tableDifferences
       
   246      */
       
   247     private function detectColumnRenamings(TableDiff $tableDifferences)
       
   248     {
       
   249         $renameCandidates = array();
       
   250         foreach ($tableDifferences->addedColumns AS $addedColumnName => $addedColumn) {
       
   251             foreach ($tableDifferences->removedColumns AS $removedColumnName => $removedColumn) {
       
   252                 if (count($this->diffColumn($addedColumn, $removedColumn)) == 0) {
       
   253                     $renameCandidates[$addedColumn->getName()][] = array($removedColumn, $addedColumn, $addedColumnName);
       
   254                 }
       
   255             }
       
   256         }
       
   257 
       
   258         foreach ($renameCandidates AS $candidate => $candidateColumns) {
       
   259             if (count($candidateColumns) == 1) {
       
   260                 list($removedColumn, $addedColumn) = $candidateColumns[0];
       
   261                 $removedColumnName = strtolower($removedColumn->getName());
       
   262                 $addedColumnName = strtolower($addedColumn->getName());
       
   263 
       
   264                 $tableDifferences->renamedColumns[$removedColumnName] = $addedColumn;
       
   265                 unset($tableDifferences->addedColumns[$addedColumnName]);
       
   266                 unset($tableDifferences->removedColumns[$removedColumnName]);
       
   267             }
       
   268         }
       
   269     }
       
   270 
       
   271     /**
       
   272      * @param ForeignKeyConstraint $key1
       
   273      * @param ForeignKeyConstraint $key2
       
   274      * @return bool
       
   275      */
       
   276     public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2)
       
   277     {
       
   278         if (array_map('strtolower', $key1->getLocalColumns()) != array_map('strtolower', $key2->getLocalColumns())) {
       
   279             return true;
       
   280         }
       
   281         
       
   282         if (array_map('strtolower', $key1->getForeignColumns()) != array_map('strtolower', $key2->getForeignColumns())) {
       
   283             return true;
       
   284         }
       
   285 
       
   286         if ($key1->onUpdate() != $key2->onUpdate()) {
       
   287             return true;
       
   288         }
       
   289 
       
   290         if ($key1->onDelete() != $key2->onDelete()) {
       
   291             return true;
       
   292         }
       
   293 
       
   294         return false;
       
   295     }
       
   296 
       
   297     /**
       
   298      * Returns the difference between the fields $field1 and $field2.
       
   299      *
       
   300      * If there are differences this method returns $field2, otherwise the
       
   301      * boolean false.
       
   302      *
       
   303      * @param Column $column1
       
   304      * @param Column $column2
       
   305      *
       
   306      * @return array
       
   307      */
       
   308     public function diffColumn(Column $column1, Column $column2)
       
   309     {
       
   310         $changedProperties = array();
       
   311         if ( $column1->getType() != $column2->getType() ) {
       
   312             $changedProperties[] = 'type';
       
   313         }
       
   314 
       
   315         if ($column1->getNotnull() != $column2->getNotnull()) {
       
   316             $changedProperties[] = 'notnull';
       
   317         }
       
   318 
       
   319         if ($column1->getDefault() != $column2->getDefault()) {
       
   320             $changedProperties[] = 'default';
       
   321         }
       
   322 
       
   323         if ($column1->getUnsigned() != $column2->getUnsigned()) {
       
   324             $changedProperties[] = 'unsigned';
       
   325         }
       
   326 
       
   327         if ($column1->getType() instanceof \Doctrine\DBAL\Types\StringType) {
       
   328             // check if value of length is set at all, default value assumed otherwise.
       
   329             $length1 = $column1->getLength() ?: 255;
       
   330             $length2 = $column2->getLength() ?: 255;
       
   331             if ($length1 != $length2) {
       
   332                 $changedProperties[] = 'length';
       
   333             }
       
   334 
       
   335             if ($column1->getFixed() != $column2->getFixed()) {
       
   336                 $changedProperties[] = 'fixed';
       
   337             }
       
   338         }
       
   339 
       
   340         if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) {
       
   341             if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) {
       
   342                 $changedProperties[] = 'precision';
       
   343             }
       
   344             if ($column1->getScale() != $column2->getScale()) {
       
   345                 $changedProperties[] = 'scale';
       
   346             }
       
   347         }
       
   348 
       
   349         if ($column1->getAutoincrement() != $column2->getAutoincrement()) {
       
   350             $changedProperties[] = 'autoincrement';
       
   351         }
       
   352 
       
   353         // only allow to delete comment if its set to '' not to null.
       
   354         if ($column1->getComment() !== null && $column1->getComment() != $column2->getComment()) {
       
   355             $changedProperties[] = 'comment';
       
   356         }
       
   357 
       
   358         return $changedProperties;
       
   359     }
       
   360 
       
   361     /**
       
   362      * Finds the difference between the indexes $index1 and $index2.
       
   363      *
       
   364      * Compares $index1 with $index2 and returns $index2 if there are any
       
   365      * differences or false in case there are no differences.
       
   366      *
       
   367      * @param Index $index1
       
   368      * @param Index $index2
       
   369      * @return bool
       
   370      */
       
   371     public function diffIndex(Index $index1, Index $index2)
       
   372     {
       
   373         if ($index1->isFullfilledBy($index2) && $index2->isFullfilledBy($index1)) {
       
   374             return false;
       
   375         }
       
   376         return true;
       
   377     }
       
   378 }