Coverage for Doctrine_Migration

Back to coverage report

1 <?php
2 /*
3  *  $Id: Migration.php 1080 2007-02-10 18:17:08Z jwage $
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.phpdoctrine.com>.
20  */
21
22 /**
23  * Doctrine_Migration
24  *
25  * this class represents a database view
26  *
27  * @package     Doctrine
28  * @subpackage  Migration
29  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
30  * @link        www.phpdoctrine.com
31  * @since       1.0
32  * @version     $Revision: 1080 $
33  * @author      Jonathan H. Wage <jwage@mac.com>
34  */
35 class Doctrine_Migration
36 {
37     protected $changes = array('created_tables'      =>  array(),
38                                'renamed_tables'      =>  array(),
39                                'created_constraints' =>  array(),
40                                'dropped_fks'         =>  array(),
41                                'created_fks'         =>  array(),
42                                'dropped_constraints' =>  array(),
43                                'removed_indexes'     =>  array(),
44                                'dropped_tables'      =>  array(),
45                                'added_columns'       =>  array(),
46                                'renamed_columns'     =>  array(),
47                                'changed_columns'     =>  array(),
48                                'removed_columns'     =>  array(),
49                                'added_indexes'       =>  array(),
50                                ),
51               $migrationTableName = 'migration_version',
52               $migrationClassesDirectory = array(),
53               $migrationClasses = array(),
54               $loadedMigrations = array();
55
56     /**
57      * construct
58      *
59      * Specify the path to the directory with the migration classes.
60      * The classes will be loaded and the migration table will be created if it does not already exist
61      *
62      * @param string $directory 
63      * @return void
64      */
65     public function __construct($directory = null)
66     {
67         if ($directory != null) {
68             $this->migrationClassesDirectory = $directory;
69             
70             $this->loadMigrationClasses();
71             
72             $this->createMigrationTable();
73         }
74     }
75
76     /**
77      * createMigrationTable
78      * 
79      * Creates the migration table used to store the current version
80      *
81      * @return void
82      */
83     protected function createMigrationTable()
84     {
85         $conn = Doctrine_Manager::connection();
86         
87         try {
88             $conn->export->createTable($this->migrationTableName, array('version' => array('type' => 'integer', 'size' => 11)));
89             
90             return true;
91         } catch(Exception $e) {
92             return false;
93         }
94     }
95
96     /**
97      * loadMigrationClasses
98      *
99      * Loads the migration classes for the directory specified by the constructor
100      *
101      * @return void
102      */
103     protected function loadMigrationClasses()
104     {
105         if ($this->migrationClasses) {
106             return $this->migrationClasses;
107         }
108         
109         $classes = get_declared_classes();
110         
111         if ($this->migrationClassesDirectory !== null) {
112             foreach ((array) $this->migrationClassesDirectory as $dir) {
113                 $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir),
114                                                         RecursiveIteratorIterator::LEAVES_ONLY);
115
116                 foreach ($it as $file) {
117                     $e = explode('.', $file->getFileName());
118                     if (end($e) === 'php' && strpos($file->getFileName(), '.inc') === false) {
119                         if ( ! in_array($file->getFileName(), $this->loadedMigrations)) {
120                             require_once($file->getPathName());
121                             
122                             $requiredClass = array_diff(get_declared_classes(), $classes);
123                             $requiredClass = end($requiredClass);
124                             
125                             if ($requiredClass) {
126                                 $this->loadedMigrations[$requiredClass] = $file->getFileName();
127                             }
128                         }
129                     }
130                 }
131             }
132         }
133         
134         $parent = new ReflectionClass('Doctrine_Migration');
135         
136         foreach ($this->loadedMigrations as $name => $fileName) {
137             $class = new ReflectionClass($name);
138             
139             while ($class->isSubclassOf($parent)) {
140
141                 $class = $class->getParentClass();
142                 if ($class === false) {
143                     break;
144                 }
145             }
146             
147             if ($class === false) {
148                 continue;
149             }
150             
151             $e = explode('_', $fileName);
152             $classMigrationNum = (int) $e[0];
153             
154             $this->migrationClasses[$classMigrationNum] = array('className' => $name, 'fileName' => $fileName);
155         }
156         
157         return $this->migrationClasses;
158     }
159
160     /**
161      * getMigrationClasses
162      *
163      * @return void
164      */
165     public function getMigrationClasses()
166     {
167         return $this->migrationClasses;
168     }
169
170     /**
171      * setCurrentVersion
172      *
173      * Sets the current version in the migration table
174      *
175      * @param string $number 
176      * @return void
177      */
178     protected function setCurrentVersion($number)
179     {
180         $conn = Doctrine_Manager::connection();
181         
182         if ($this->hasMigrated()) {
183             $conn->exec("UPDATE " . $this->migrationTableName . " SET version = $number");
184         } else {
185             $conn->exec("INSERT INTO " . $this->migrationTableName . " (version) VALUES ($number)");
186         }
187     }
188
189     /**
190      * getCurrentVersion
191      *
192      * Get the current version of the database
193      *
194      * @return void
195      */
196     public function getCurrentVersion()
197     {
198         $conn = Doctrine_Manager::connection();
199         
200         $result = $conn->fetchColumn("SELECT version FROM " . $this->migrationTableName);
201         
202         return isset($result[0]) ? $result[0]:0;
203     }
204
205     /**
206      * hasMigrated
207      *
208      * Returns true/false for whether or not this database has been migrated in the past
209      *
210      * @return void
211      */
212     public function hasMigrated()
213     {
214         $conn = Doctrine_Manager::connection();
215         
216         $result = $conn->fetchColumn("SELECT version FROM " . $this->migrationTableName);
217         
218         return isset($result[0]) ? true:false; 
219     }
220
221     /**
222      * getLatestVersion
223      *
224      * Gets the latest possible version from the loaded migration classes
225      *
226      * @return void
227      */
228     public function getLatestVersion()
229     {
230         $this->loadMigrationClasses();
231         
232         $versions = array();
233         foreach (array_keys($this->migrationClasses) as $classMigrationNum) {
234             $versions[$classMigrationNum] = $classMigrationNum;
235         }
236         
237         rsort($versions);
238         
239         return isset($versions[0]) ? $versions[0]:0;
240     }
241
242     /**
243      * getNextVersion
244      *
245      * @return integer $nextVersion
246      */
247     public function getNextVersion()
248     {
249         return $this->getLatestVersion() + 1;
250     }
251
252     /**
253      * getMigrationClass
254      *
255      * Get instance of migration class for $num
256      *
257      * @param string $num 
258      * @return void
259      */
260     protected function getMigrationClass($num)
261     {
262         foreach ($this->migrationClasses as $classMigrationNum => $info) {
263             $className = $info['className'];
264             
265             if ($classMigrationNum == $num) {
266                 return new $className();
267             }
268         }
269         
270         throw new Doctrine_Migration_Exception('Could not find migration class for migration step: '.$num);
271     }
272
273     /**
274      * doMigrateStep
275      *
276      * Perform migration directory for the specified version. Loads migration classes and performs the migration then processes the changes
277      *
278      * @param string $direction 
279      * @param string $num 
280      * @return void
281      */
282     protected function doMigrateStep($direction, $num)
283     {
284         $migrate = $this->getMigrationClass($num);
285         
286         $migrate->doMigrate($direction);
287     }
288
289     /**
290      * doMigrate
291      * 
292      * Perform migration for a migration class. Executes the up or down method then processes the changes
293      *
294      * @param string $direction 
295      * @return void
296      */
297     protected function doMigrate($direction)
298     {
299         if (method_exists($this, $direction)) {
300             $this->$direction();
301
302             foreach ($this->changes as $type => $changes) {
303                 $process = new Doctrine_Migration_Process();
304                 $funcName = 'process' . Doctrine::classify($type);
305
306                 if ( ! empty($changes)) {
307                     $process->$funcName($changes); 
308                 }
309             }
310         }
311     }
312
313     /**
314      * migrate
315      *
316      * Perform a migration chain by specifying the $from and $to.
317      * If you do not specify a $from or $to then it will attempt to migrate from the current version to the latest version
318      *
319      * @param string $from 
320      * @param string $to 
321      * @return void
322      */
323     public function migrate($to = null)
324     {
325         $from = $this->getCurrentVersion();
326         
327         // If nothing specified then lets assume we are migrating from the current version to the latest version
328         if ($to === null) {
329             $to = $this->getLatestVersion();
330         }
331         
332         if ($from == $to) {
333             throw new Doctrine_Migration_Exception('Already at version # ' . $to);
334         }
335         
336         $direction = $from > $to ? 'down':'up';
337         
338         if ($direction === 'up') {
339             for ($i = $from + 1; $i <= $to; $i++) {
340                 $this->doMigrateStep($direction, $i);
341             }
342         } else {
343             for ($i = $from; $i > $to; $i--) {
344                 $this->doMigrateStep($direction, $i);
345             }
346         }
347         
348         $this->setCurrentVersion($to);
349         
350         return $to;
351     }
352
353     /**
354      * addChange
355      *
356      * @param string $type 
357      * @param string $array 
358      * @return void
359      */
360     protected function addChange($type, array $change = array())
361     {
362         $this->changes[$type][] = $change;
363     }
364
365     /**
366      * createTable
367      *
368      * @param string $tableName 
369      * @param string $array 
370      * @param string $array 
371      * @return void
372      */
373     public function createTable($tableName, array $fields = array(), array $options = array())
374     {
375         $options = get_defined_vars();
376         
377         $this->addChange('created_tables', $options);
378     }
379
380     /**
381      * dropTable
382      *
383      * @param string $tableName 
384      * @return void
385      */
386     public function dropTable($tableName)
387     {
388         $options = get_defined_vars();
389         
390         $this->addChange('dropped_tables', $options);
391     }
392
393     /**
394      * renameTable
395      *
396      * @param string $oldTableName 
397      * @param string $newTableName 
398      * @return void
399      */
400     public function renameTable($oldTableName, $newTableName)
401     {
402         $options = get_defined_vars();
403         
404         $this->addChange('renamed_tables', $options);
405     }
406
407     /**
408      * createConstraint
409      *
410      * @param string $tableName
411      * @param string $constraintName
412      * @return void
413      */
414     public function createConstraint($tableName, $constraintName, array $definition)
415     {
416         $options = get_defined_vars();
417         
418         $this->addChange('created_constraints', $options);
419     }
420
421     /**
422      * dropConstraint
423      *
424      * @param string $tableName
425      * @param string $constraintName
426      * @return void
427      */
428     public function dropConstraint($tableName, $constraintName, $primary = false)
429     {
430         $options = get_defined_vars();
431         
432         $this->addChange('dropped_constraints', $options);
433     }
434
435     /**
436      * createForeignKey
437      *
438      * @param string $tableName
439      * @param string $constraintName
440      * @return void
441      */
442     public function createForeignKey($tableName, array $definition)
443     {
444         $options = get_defined_vars();
445         
446         $this->addChange('created_fks', $options);
447     }
448
449     /**
450      * dropForeignKey
451      *
452      * @param string $tableName
453      * @param string $constraintName
454      * @return void
455      */
456     public function dropForeignKey($tableName, $fkName)
457     {
458         $options = get_defined_vars();
459         
460         $this->addChange('dropped_fks', $options);
461     }
462
463     /**
464      * addColumn
465      *
466      * @param string $tableName 
467      * @param string $columnName 
468      * @param string $type 
469      * @param string $array 
470      * @return void
471      */
472     public function addColumn($tableName, $columnName, $type, array $options = array())
473     {
474         $options = get_defined_vars();
475         
476         $this->addChange('added_columns', $options);
477     }
478
479     /**
480      * renameColumn
481      *
482      * @param string $tableName 
483      * @param string $oldColumnName 
484      * @param string $newColumnName 
485      * @return void
486      */
487     public function renameColumn($tableName, $oldColumnName, $newColumnName)
488     {
489         $options = get_defined_vars();
490         
491         $this->addChange('renamed_columns', $options);
492     }
493
494     /**
495      * renameColumn
496      *
497      * @param string $tableName 
498      * @param string $columnName 
499      * @param string $type 
500      * @param string $array 
501      * @return void
502      */
503     public function changeColumn($tableName, $columnName, $type, array $options = array())
504     {
505         $options = get_defined_vars();
506         
507         $this->addChange('changed_columns', $options);
508     }
509
510     /**
511      * removeColumn
512      *
513      * @param string $tableName 
514      * @param string $columnName 
515      * @return void
516      */
517     public function removeColumn($tableName, $columnName)
518     {
519         $options = get_defined_vars();
520         
521         $this->addChange('removed_columns', $options);
522     }
523
524     /**
525      * addIndex
526      *
527      * @param string $tableName 
528      * @param string $indexName 
529      * @param string $array 
530      * @return void
531      */
532     public function addIndex($tableName, $indexName, array $definition)
533     {
534         $options = get_defined_vars();
535         
536         $this->addChange('added_indexes', $options);
537     }
538
539     /**
540      * removeIndex
541      *
542      * @param string $tableName 
543      * @param string $indexName 
544      * @return void
545      */
546     public function removeIndex($tableName, $indexName)
547     {
548         $options = get_defined_vars();
549         
550         $this->addChange('removed_indexes', $options);
551     }
552 }