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