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