Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/*
3
 *  $Id: Builder.php 2939 2007-10-19 14:23:42Z Jonathan.Wage $
4
 *
5
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16
 *
17
 * This software consists of voluntary contributions made by many individuals
18
 * and is licensed under the LGPL. For more information, see
19
 * <http://www.doctrine-project.org>.
20
 */
21
 
22
/**
23
 * Doctrine_Migration_Builder
24
 *
25
 * @package     Doctrine
26
 * @subpackage  Migration
27
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
28
 * @author      Jonathan H. Wage <jwage@mac.com>
29
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
30
 * @link        www.doctrine-project.org
31
 * @since       1.0
32
 * @version     $Revision: 2939 $
33
 */
34
class Doctrine_Migration_Builder extends Doctrine_Builder
35
{
36
    /**
37
     * The path to your migration classes directory
38
     *
39
     * @var string
40
     */
41
    private $migrationsPath = '';
42
 
43
    /**
44
     * File suffix to use when writing class definitions
45
     *
46
     * @var string $suffix
47
     */
48
    private $suffix = '.php';
49
 
50
    /**
51
     * Instance of the migration class for the migration classes directory
52
     *
53
     * @var Doctrine_Migration $migration
54
     */
55
    private $migration;
56
 
57
    /**
58
     * Class template used for writing classes
59
     *
60
     * @var $tpl
61
     */
62
    private static $tpl;
63
 
64
    /**
65
     * Instantiate new instance of the Doctrine_Migration_Builder class
66
     *
67
     * <code>
68
     * $builder = new Doctrine_Migration_Builder('/path/to/migrations');
69
     * </code>
70
     *
71
     * @return void
72
     */
73
    public function __construct($migrationsPath = null)
74
    {
75
        if ($migrationsPath instanceof Doctrine_Migration) {
76
            $this->setMigrationsPath($migrationsPath->getMigrationClassesDirectory());
77
            $this->migration = $migrationsPath;
78
        } else if (is_dir($migrationsPath)) {
79
            $this->setMigrationsPath($migrationsPath);
80
            $this->migration = new Doctrine_Migration($migrationsPath);
81
        }
82
 
83
        $this->loadTemplate();
84
    }
85
 
86
    /**
87
     * Set the path to write the generated migration classes
88
     *
89
     * @param string path   the path where migration classes are stored and being generated
90
     * @return void
91
     */
92
    public function setMigrationsPath($path)
93
    {
94
        Doctrine_Lib::makeDirectories($path);
95
 
96
        $this->migrationsPath = $path;
97
    }
98
 
99
    /**
100
     * Get the path where generated migration classes are written to
101
     *
102
     * @return string       the path where migration classes are stored and being generated
103
     */
104
    public function getMigrationsPath()
105
    {
106
        return $this->migrationsPath;
107
    }
108
 
109
    /**
110
     * Loads the class template used for generating classes
111
     *
112
     * @return void
113
     */
114
    protected function loadTemplate()
115
    {
116
        if (isset(self::$tpl)) {
117
            return;
118
        }
119
 
120
        self::$tpl =<<<END
121
/**
122
 * This class has been auto-generated by the Doctrine ORM Framework
123
 */
124
class %s extends %s
125
{
126
    public function up()
127
    {
128
%s
129
    }
130
 
131
    public function down()
132
    {
133
%s
134
    }
135
}
136
END;
137
    }
138
 
139
    /**
140
     * Generate migrations from a Doctrine_Migration_Diff instance
141
     *
142
     * @param  Doctrine_Migration_Diff $diff Instance to generate changes from
143
     * @return array $changes  Array of changes produced from the diff
144
     */
145
    public function generateMigrationsFromDiff(Doctrine_Migration_Diff $diff)
146
    {
147
        $changes = $diff->generateChanges();
148
 
149
        $up = array();
150
        $down = array();
151
 
152
        if ( ! empty($changes['dropped_tables'])) {
153
            foreach ($changes['dropped_tables'] as $tableName => $table) {
154
                $up[] = $this->buildDropTable($table);
155
                $down[] = $this->buildCreateTable($table);
156
            }
157
        }
158
 
159
        if ( ! empty($changes['created_tables'])) {
160
            foreach ($changes['created_tables'] as $tableName => $table) {
161
                $up[] = $this->buildCreateTable($table);
162
                $down[] = $this->buildDropTable($table);
163
            }
164
        }
165
 
166
        if ( ! empty($changes['dropped_columns'])) {
167
            foreach ($changes['dropped_columns'] as $tableName => $removedColumns) {
168
                foreach ($removedColumns as $name => $column) {
169
                    $up[] = $this->buildRemoveColumn($tableName, $name, $column);
170
                    $down[] = $this->buildAddColumn($tableName, $name, $column);
171
                }
172
            }
173
        }
174
 
175
        if ( ! empty($changes['created_columns'])) {
176
            foreach ($changes['created_columns'] as $tableName => $addedColumns) {
177
                foreach ($addedColumns as $name => $column) {
178
                    $up[] = $this->buildAddColumn($tableName, $name, $column);
179
                    $down[] = $this->buildRemoveColumn($tableName, $name, $column);
180
                }
181
            }
182
        }
183
 
184
        if ( ! empty($changes['changed_columns'])) {
185
            foreach ($changes['changed_columns'] as $tableName => $changedColumns) {
186
                foreach ($changedColumns as $name => $column) {
187
                    $up[] = $this->buildChangeColumn($tableName, $name, $column);
188
                }
189
            }
190
        }
191
 
192
        if ( ! empty($up) || ! empty($down)) {
193
            $up = implode("\n", $up);
194
            $down = implode("\n", $down);
195
            $className = 'Version' . $this->migration->getNextMigrationClassVersion();
196
            $this->generateMigrationClass($className, array(), $up, $down);
197
        }
198
 
199
        $up = array();
200
        $down = array();
201
        if ( ! empty($changes['dropped_foreign_keys'])) {
202
            foreach ($changes['dropped_foreign_keys'] as $tableName => $droppedFks) {
203
                if ( ! empty($changes['dropped_tables']) && isset($changes['dropped_tables'][$tableName])) {
204
                    continue;
205
                }
206
 
207
                foreach ($droppedFks as $name => $foreignKey) {
208
                    $up[] = $this->buildDropForeignKey($tableName, $foreignKey);
209
                    $down[] = $this->buildCreateForeignKey($tableName, $foreignKey);
210
                }
211
            }
212
        }
213
 
214
        if ( ! empty($changes['dropped_indexes'])) {
215
            foreach ($changes['dropped_indexes'] as $tableName => $removedIndexes) {
216
                if ( ! empty($changes['dropped_tables']) && isset($changes['dropped_tables'][$tableName])) {
217
                    continue;
218
                }
219
 
220
                foreach ($removedIndexes as $name => $index) {
221
                    $up[] = $this->buildRemoveIndex($tableName, $name, $index);
222
                    $down[] = $this->buildAddIndex($tableName, $name, $index);
223
                }
224
            }
225
        }
226
 
227
        if ( ! empty($changes['created_foreign_keys'])) {
228
            foreach ($changes['created_foreign_keys'] as $tableName => $createdFks) {
229
                if ( ! empty($changes['dropped_tables']) && isset($changes['dropped_tables'][$tableName])) {
230
                    continue;
231
                }
232
 
233
                foreach ($createdFks as $name => $foreignKey) {
234
                    $up[] = $this->buildCreateForeignKey($tableName, $foreignKey);
235
                    $down[] = $this->buildDropForeignKey($tableName, $foreignKey);
236
                }
237
            }
238
        }
239
 
240
        if ( ! empty($changes['created_indexes'])) {
241
            foreach ($changes['created_indexes'] as $tableName => $addedIndexes) {
242
                if ( ! empty($changes['dropped_tables']) && isset($changes['dropped_tables'][$tableName])) {
243
                    continue;
244
                }
245
 
246
                foreach ($addedIndexes as $name => $index) {
247
                    if (isset($changes['created_tables'][$tableName]['options']['indexes'][$name])) {
248
                        continue;
249
                    }
250
                    $up[] = $this->buildAddIndex($tableName, $name, $index);
251
                    $down[] = $this->buildRemoveIndex($tableName, $name, $index);
252
                }
253
            }
254
        }
255
 
256
        if ( ! empty($up) || ! empty($down)) {
257
            $up = implode("\n", $up);
258
            $down = implode("\n", $down);
259
            $className = 'Version' . $this->migration->getNextMigrationClassVersion();
260
            $this->generateMigrationClass($className, array(), $up, $down);
261
        }
262
        return $changes;
263
    }
264
 
265
    /**
266
     * Generate a set of migration classes from the existing databases
267
     *
268
     * @return void
269
     */
270
    public function generateMigrationsFromDb()
271
    {
272
        $directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'tmp_doctrine_models';
273
 
274
        Doctrine_Core::generateModelsFromDb($directory);
275
 
276
        $result = $this->generateMigrationsFromModels($directory, Doctrine_Core::MODEL_LOADING_CONSERVATIVE);
277
 
278
        Doctrine_Lib::removeDirectories($directory);
279
 
280
        return $result;
281
    }
282
 
283
    /**
284
     * Generate a set of migrations from a set of models
285
     *
286
     * @param  string $modelsPath    Path to models
287
     * @param  string $modelLoading  What type of model loading to use when loading the models
288
     * @return boolean
289
     */
290
    public function generateMigrationsFromModels($modelsPath = null, $modelLoading = null)
291
    {
292
        if ($modelsPath !== null) {
293
            $models = Doctrine_Core::filterInvalidModels(Doctrine_Core::loadModels($modelsPath, $modelLoading));
294
        } else {
295
            $models = Doctrine_Core::getLoadedModels();
296
        }
297
 
298
        $models = Doctrine_Core::initializeModels($models);
299
 
300
        $foreignKeys = array();
301
 
302
        foreach ($models as $model) {
303
            $table = Doctrine_Core::getTable($model);
304
            if ($table->getTableName() !== $this->migration->getTableName()) {
305
                $export = $table->getExportableFormat();
306
 
307
                $foreignKeys[$export['tableName']] = $export['options']['foreignKeys'];
308
 
309
                $up = $this->buildCreateTable($export);
310
                $down = $this->buildDropTable($export);
311
 
312
                $className = 'Add' . Doctrine_Inflector::classify($export['tableName']);
313
 
314
                $this->generateMigrationClass($className, array(), $up, $down);
315
            }
316
        }
317
 
318
        if ( ! empty($foreignKeys)) {
319
            $className = 'AddFks';
320
 
321
            $up = array();
322
            $down = array();
323
            foreach ($foreignKeys as $tableName => $definitions)    {
324
                $tableForeignKeyNames[$tableName] = array();
325
 
326
                foreach ($definitions as $definition) {
327
                    $up[] = $this->buildCreateForeignKey($tableName, $definition);
328
                    $down[] = $this->buildDropForeignKey($tableName, $definition);
329
                }
330
            }
331
 
332
            $up = implode("\n", $up);
333
            $down = implode("\n", $down);
334
            if ($up || $down) {
335
                $this->generateMigrationClass($className, array(), $up, $down);
336
            }
337
        }
338
 
339
        return true;
340
    }
341
 
342
    /**
343
     * Build the code for creating foreign keys
344
     *
345
     * @param  string $tableName
346
     * @param  array  $definition
347
     * @return string $code
348
     */
349
    public function buildCreateForeignKey($tableName, $definition)
350
    {
351
        return "        \$this->createForeignKey('" . $tableName . "', '" . $definition['name'] . "', " . $this->varExport($definition, true) . ");";
352
    }
353
 
354
    /**
355
     * Build the code for dropping foreign keys
356
     *
357
     * @param  string $tableName
358
     * @param  array  $definition
359
     * @return string $code
360
     */
361
    public function buildDropForeignKey($tableName, $definition)
362
    {
363
        return "        \$this->dropForeignKey('" . $tableName . "', '" . $definition['name'] . "');";
364
    }
365
 
366
    /**
367
     * Build the code for creating tables
368
     *
369
     * @param  string $tableData
370
     * @return string $code
371
     */
372
    public function buildCreateTable($tableData)
373
    {
374
        $code  = "        \$this->createTable('" . $tableData['tableName'] . "', ";
375
 
376
        $code .= $this->varExport($tableData['columns'], true) . ", ";
377
 
378
        $optionsWeNeed = array('type', 'indexes', 'primary', 'collate', 'charset');
379
 
380
        $options = array();
381
        foreach ($optionsWeNeed as $option) {
382
            if (isset($tableData['options'][$option])) {
383
                $options[$option] = $tableData['options'][$option];
384
            }
385
        }
386
 
387
        $code .= $this->varExport($options, true);
388
 
389
        $code .= ");";
390
 
391
        return $code;
392
    }
393
 
394
    /**
395
     * Build the code for dropping tables
396
     *
397
     * @param  string $tableData
398
     * @return string $code
399
     */
400
    public function buildDropTable($tableData)
401
    {
402
        return "        \$this->dropTable('" . $tableData['tableName'] . "');";
403
    }
404
 
405
    /**
406
     * Build the code for adding columns
407
     *
408
     * @param string $tableName
409
     * @param string $columnName
410
     * @param string $column
411
     * @return string $code
412
     */
413
    public function buildAddColumn($tableName, $columnName, $column)
414
    {
415
        $length = $column['length'];
416
        $type = $column['type'];
417
        unset($column['length'], $column['type']);
418
        return "        \$this->addColumn('" . $tableName . "', '" . $columnName. "', '" . $type . "', '" . $length . "', " . $this->varExport($column) . ");";
419
    }
420
 
421
    /**
422
     * Build the code for removing columns
423
     *
424
     * @param string $tableName
425
     * @param string $columnName
426
     * @param string $column
427
     * @return string $code
428
     */
429
    public function buildRemoveColumn($tableName, $columnName, $column)
430
    {
431
        return "        \$this->removeColumn('" . $tableName . "', '" . $columnName. "');";
432
    }
433
 
434
    /**
435
     * Build the code for changing columns
436
     *
437
     * @param string $tableName
438
     * @param string $columnName
439
     * @param string $column
440
     * @return string $code
441
     */
442
    public function buildChangeColumn($tableName, $columnName, $column)
443
    {
444
        $length = $column['length'];
445
        $type = $column['type'];
446
        unset($column['length'], $column['type']);
447
        return "        \$this->changeColumn('" . $tableName . "', '" . $columnName. "', '" . $type . "', '" . $length . "', " . $this->varExport($column) . ");";
448
    }
449
 
450
    /**
451
     * Build the code for adding indexes
452
     *
453
     * @param string $tableName
454
     * @param string $indexName
455
     * @param string $index
456
     * @return sgtring $code
457
     */
458
    public function buildAddIndex($tableName, $indexName, $index)
459
    {
460
        return "        \$this->addIndex('$tableName', '$indexName', " . $this->varExport($index) . ");";
461
    }
462
 
463
    /**
464
     * Build the code for removing indexes
465
     *
466
     * @param string $tableName
467
     * @param string $indexName
468
     * @param string $index
469
     * @return string $code
470
     */
471
    public function buildRemoveIndex($tableName, $indexName, $index)
472
    {
473
        return "        \$this->removeIndex('$tableName', '$indexName', " . $this->varExport($index) . ");";
474
    }
475
 
476
    /**
477
     * Generate a migration class
478
     *
479
     * @param string  $className   Class name to generate
480
     * @param array   $options     Options for the migration class
481
     * @param string  $up          The code for the up function
482
     * @param string  $down        The code for the down function
483
     * @param boolean $return      Whether or not to return the code.
484
     *                             If true return and false it writes the class to disk.
485
     * @return mixed
486
     */
487
    public function generateMigrationClass($className, $options = array(), $up = null, $down = null, $return = false)
488
    {
489
        $className = Doctrine_Inflector::urlize($className);
490
        $className = str_replace('-', '_', $className);
491
        $className = Doctrine_Inflector::classify($className);
492
 
493
        if ($return || ! $this->getMigrationsPath()) {
494
            return $this->buildMigrationClass($className, null, $options, $up, $down);
495
        } else {
496
            if ( ! $this->getMigrationsPath()) {
497
                throw new Doctrine_Migration_Exception('You must specify the path to your migrations.');
498
            }
499
 
500
            $next = time() + $this->migration->getNextMigrationClassVersion();
501
            $fileName = $next . '_' . Doctrine_Inflector::tableize($className) . $this->suffix;
502
 
503
            $class = $this->buildMigrationClass($className, $fileName, $options, $up, $down);
504
 
505
            $path = $this->getMigrationsPath() . DIRECTORY_SEPARATOR . $fileName;
506
            if (class_exists($className) || file_exists($path)) {
507
                $this->migration->loadMigrationClass($className);
508
                return false;
509
            }
510
 
511
            file_put_contents($path, $class);
512
            require_once($path);
513
            $this->migration->loadMigrationClass($className);
514
 
515
            return true;
516
        }
517
    }
518
 
519
    /**
520
     * Build the code for a migration class
521
     *
522
     * @param string  $className   Class name to generate
523
     * @param string  $fileName    File name to write the class to
524
     * @param array   $options     Options for the migration class
525
     * @param string  $up          The code for the up function
526
     * @param string  $down        The code for the down function
527
     * @return string $content     The code for the generated class
528
     */
529
    public function buildMigrationClass($className, $fileName = null, $options = array(), $up = null, $down = null)
530
    {
531
        $extends = isset($options['extends']) ? $options['extends']:'Doctrine_Migration_Base';
532
 
533
        $content  = '<?php' . PHP_EOL;
534
 
535
        $content .= sprintf(self::$tpl, $className,
536
                                       $extends,
537
                                       $up,
538
                                       $down);
539
 
540
        return $content;
541
    }
542
}