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