Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / dbal / lib / Doctrine / DBAL / Schema / Comparator.php
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 MIT license. 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  * 
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 $table ) {
65             $tableName = $table->getShortestName($toSchema->getName());
66             if ( ! $fromSchema->hasTable($tableName)) {
67                 $diff->newTables[$tableName] = $toSchema->getTable($tableName);
68             } else {
69                 $tableDifferences = $this->diffTable($fromSchema->getTable($tableName), $toSchema->getTable($tableName));
70                 if ($tableDifferences !== false) {
71                     $diff->changedTables[$tableName] = $tableDifferences;
72                 }
73             }
74         }
75
76         /* Check if there are tables removed */
77         foreach ($fromSchema->getTables() as $table) {
78             $tableName = $table->getShortestName($fromSchema->getName());
79
80             $table = $fromSchema->getTable($tableName);
81             if ( ! $toSchema->hasTable($tableName) ) {
82                 $diff->removedTables[$tableName] = $table;
83             }
84
85             // also remember all foreign keys that point to a specific table
86             foreach ($table->getForeignKeys() as $foreignKey) {
87                 $foreignTable = strtolower($foreignKey->getForeignTableName());
88                 if (!isset($foreignKeysToTable[$foreignTable])) {
89                     $foreignKeysToTable[$foreignTable] = array();
90                 }
91                 $foreignKeysToTable[$foreignTable][] = $foreignKey;
92             }
93         }
94
95         foreach ($diff->removedTables as $tableName => $table) {
96             if (isset($foreignKeysToTable[$tableName])) {
97                 $diff->orphanedForeignKeys = array_merge($diff->orphanedForeignKeys, $foreignKeysToTable[$tableName]);
98             }
99         }
100
101         foreach ($toSchema->getSequences() as $sequence) {
102             $sequenceName = $sequence->getShortestName($toSchema->getName());
103             if ( ! $fromSchema->hasSequence($sequenceName)) {
104                 $diff->newSequences[] = $sequence;
105             } else {
106                 if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) {
107                     $diff->changedSequences[] = $toSchema->getSequence($sequenceName);
108                 }
109             }
110         }
111
112         foreach ($fromSchema->getSequences() as $sequence) {
113             if ($this->isAutoIncrementSequenceInSchema($toSchema, $sequence)) {
114                 continue;
115             }
116
117             $sequenceName = $sequence->getShortestName($fromSchema->getName());
118
119             if ( ! $toSchema->hasSequence($sequenceName)) {
120                 $diff->removedSequences[] = $sequence;
121             }
122         }
123
124         return $diff;
125     }
126
127     private function isAutoIncrementSequenceInSchema($schema, $sequence)
128     {
129         foreach ($schema->getTables() as $table) {
130             if ($sequence->isAutoIncrementsFor($table)) {
131                 return true;
132             }
133         }
134
135         return false;
136     }
137
138     /**
139      *
140      * @param Sequence $sequence1
141      * @param Sequence $sequence2
142      */
143     public function diffSequence(Sequence $sequence1, Sequence $sequence2)
144     {
145         if($sequence1->getAllocationSize() != $sequence2->getAllocationSize()) {
146             return true;
147         }
148
149         if($sequence1->getInitialValue() != $sequence2->getInitialValue()) {
150             return true;
151         }
152
153         return false;
154     }
155
156     /**
157      * Returns the difference between the tables $table1 and $table2.
158      *
159      * If there are no differences this method returns the boolean false.
160      *
161      * @param Table $table1
162      * @param Table $table2
163      *
164      * @return bool|TableDiff
165      */
166     public function diffTable(Table $table1, Table $table2)
167     {
168         $changes = 0;
169         $tableDifferences = new TableDiff($table1->getName());
170
171         $table1Columns = $table1->getColumns();
172         $table2Columns = $table2->getColumns();
173
174         /* See if all the fields in table 1 exist in table 2 */
175         foreach ( $table2Columns as $columnName => $column ) {
176             if ( !$table1->hasColumn($columnName) ) {
177                 $tableDifferences->addedColumns[$columnName] = $column;
178                 $changes++;
179             }
180         }
181         /* See if there are any removed fields in table 2 */
182         foreach ( $table1Columns as $columnName => $column ) {
183             if ( !$table2->hasColumn($columnName) ) {
184                 $tableDifferences->removedColumns[$columnName] = $column;
185                 $changes++;
186             }
187         }
188
189         foreach ( $table1Columns as $columnName => $column ) {
190             if ( $table2->hasColumn($columnName) ) {
191                 $changedProperties = $this->diffColumn( $column, $table2->getColumn($columnName) );
192                 if (count($changedProperties) ) {
193                     $columnDiff = new ColumnDiff($column->getName(), $table2->getColumn($columnName), $changedProperties);
194                     $tableDifferences->changedColumns[$column->getName()] = $columnDiff;
195                     $changes++;
196                 }
197             }
198         }
199
200         $this->detectColumnRenamings($tableDifferences);
201
202         $table1Indexes = $table1->getIndexes();
203         $table2Indexes = $table2->getIndexes();
204
205         foreach ($table2Indexes as $index2Name => $index2Definition) {
206             foreach ($table1Indexes as $index1Name => $index1Definition) {
207                 if ($this->diffIndex($index1Definition, $index2Definition) === false) {
208                     unset($table1Indexes[$index1Name]);
209                     unset($table2Indexes[$index2Name]);
210                 } else {
211                     if ($index1Name == $index2Name) {
212                         $tableDifferences->changedIndexes[$index2Name] = $table2Indexes[$index2Name];
213                         unset($table1Indexes[$index1Name]);
214                         unset($table2Indexes[$index2Name]);
215                         $changes++;
216                     }
217                 }
218             }
219         }
220
221         foreach ($table1Indexes as $index1Name => $index1Definition) {
222             $tableDifferences->removedIndexes[$index1Name] = $index1Definition;
223             $changes++;
224         }
225
226         foreach ($table2Indexes as $index2Name => $index2Definition) {
227             $tableDifferences->addedIndexes[$index2Name] = $index2Definition;
228             $changes++;
229         }
230
231         $fromFkeys = $table1->getForeignKeys();
232         $toFkeys = $table2->getForeignKeys();
233
234         foreach ($fromFkeys as $key1 => $constraint1) {
235             foreach ($toFkeys as $key2 => $constraint2) {
236                 if($this->diffForeignKey($constraint1, $constraint2) === false) {
237                     unset($fromFkeys[$key1]);
238                     unset($toFkeys[$key2]);
239                 } else {
240                     if (strtolower($constraint1->getName()) == strtolower($constraint2->getName())) {
241                         $tableDifferences->changedForeignKeys[] = $constraint2;
242                         $changes++;
243                         unset($fromFkeys[$key1]);
244                         unset($toFkeys[$key2]);
245                     }
246                 }
247             }
248         }
249
250         foreach ($fromFkeys as $key1 => $constraint1) {
251             $tableDifferences->removedForeignKeys[] = $constraint1;
252             $changes++;
253         }
254
255         foreach ($toFkeys as $key2 => $constraint2) {
256             $tableDifferences->addedForeignKeys[] = $constraint2;
257             $changes++;
258         }
259
260         return $changes ? $tableDifferences : false;
261     }
262
263     /**
264      * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
265      * however ambiguouties between different possibilites should not lead to renaming at all.
266      *
267      * @param TableDiff $tableDifferences
268      */
269     private function detectColumnRenamings(TableDiff $tableDifferences)
270     {
271         $renameCandidates = array();
272         foreach ($tableDifferences->addedColumns as $addedColumnName => $addedColumn) {
273             foreach ($tableDifferences->removedColumns as $removedColumnName => $removedColumn) {
274                 if (count($this->diffColumn($addedColumn, $removedColumn)) == 0) {
275                     $renameCandidates[$addedColumn->getName()][] = array($removedColumn, $addedColumn, $addedColumnName);
276                 }
277             }
278         }
279
280         foreach ($renameCandidates as $candidateColumns) {
281             if (count($candidateColumns) == 1) {
282                 list($removedColumn, $addedColumn) = $candidateColumns[0];
283                 $removedColumnName = strtolower($removedColumn->getName());
284                 $addedColumnName = strtolower($addedColumn->getName());
285
286                 $tableDifferences->renamedColumns[$removedColumnName] = $addedColumn;
287                 unset($tableDifferences->addedColumns[$addedColumnName]);
288                 unset($tableDifferences->removedColumns[$removedColumnName]);
289             }
290         }
291     }
292
293     /**
294      * @param ForeignKeyConstraint $key1
295      * @param ForeignKeyConstraint $key2
296      * @return bool
297      */
298     public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2)
299     {
300         if (array_map('strtolower', $key1->getLocalColumns()) != array_map('strtolower', $key2->getLocalColumns())) {
301             return true;
302         }
303
304         if (array_map('strtolower', $key1->getForeignColumns()) != array_map('strtolower', $key2->getForeignColumns())) {
305             return true;
306         }
307
308         if ($key1->onUpdate() != $key2->onUpdate()) {
309             return true;
310         }
311
312         if ($key1->onDelete() != $key2->onDelete()) {
313             return true;
314         }
315
316         return false;
317     }
318
319     /**
320      * Returns the difference between the fields $field1 and $field2.
321      *
322      * If there are differences this method returns $field2, otherwise the
323      * boolean false.
324      *
325      * @param Column $column1
326      * @param Column $column2
327      *
328      * @return array
329      */
330     public function diffColumn(Column $column1, Column $column2)
331     {
332         $changedProperties = array();
333         if ( $column1->getType() != $column2->getType() ) {
334             $changedProperties[] = 'type';
335         }
336
337         if ($column1->getNotnull() != $column2->getNotnull()) {
338             $changedProperties[] = 'notnull';
339         }
340
341         if ($column1->getDefault() != $column2->getDefault()) {
342             $changedProperties[] = 'default';
343         }
344
345         if ($column1->getUnsigned() != $column2->getUnsigned()) {
346             $changedProperties[] = 'unsigned';
347         }
348
349         if ($column1->getType() instanceof \Doctrine\DBAL\Types\StringType) {
350             // check if value of length is set at all, default value assumed otherwise.
351             $length1 = $column1->getLength() ?: 255;
352             $length2 = $column2->getLength() ?: 255;
353             if ($length1 != $length2) {
354                 $changedProperties[] = 'length';
355             }
356
357             if ($column1->getFixed() != $column2->getFixed()) {
358                 $changedProperties[] = 'fixed';
359             }
360         }
361
362         if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) {
363             if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) {
364                 $changedProperties[] = 'precision';
365             }
366             if ($column1->getScale() != $column2->getScale()) {
367                 $changedProperties[] = 'scale';
368             }
369         }
370
371         if ($column1->getAutoincrement() != $column2->getAutoincrement()) {
372             $changedProperties[] = 'autoincrement';
373         }
374
375         // only allow to delete comment if its set to '' not to null.
376         if ($column1->getComment() !== null && $column1->getComment() != $column2->getComment()) {
377             $changedProperties[] = 'comment';
378         }
379
380         $options1 = $column1->getCustomSchemaOptions();
381         $options2 = $column2->getCustomSchemaOptions();
382
383         $commonKeys = array_keys(array_intersect_key($options1, $options2));
384
385         foreach ($commonKeys as $key) {
386             if ($options1[$key] !== $options2[$key]) {
387                 $changedProperties[] = $key;
388             }
389         }
390
391         $diffKeys = array_keys(array_diff_key($options1, $options2) + array_diff_key($options2, $options1));
392
393         $changedProperties = array_merge($changedProperties, $diffKeys);
394
395         return $changedProperties;
396     }
397
398     /**
399      * Finds the difference between the indexes $index1 and $index2.
400      *
401      * Compares $index1 with $index2 and returns $index2 if there are any
402      * differences or false in case there are no differences.
403      *
404      * @param Index $index1
405      * @param Index $index2
406      * @return bool
407      */
408     public function diffIndex(Index $index1, Index $index2)
409     {
410         if ($index1->isFullfilledBy($index2) && $index2->isFullfilledBy($index1)) {
411             return false;
412         }
413         return true;
414     }
415 }