blob: 73c55c346870e160748024ffcebd48f6e90b9952 [file] [log] [blame]
Phil Sturgeon9758d842011-02-07 20:39:00 +00001<?php defined('BASEPATH') OR exit('No direct script access allowed');
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP 5.1.6 or newer
6 *
7 * @package CodeIgniter
8 * @author EllisLab Dev Team
9 * @copyright Copyright (c) 2006 - 2011, EllisLab, Inc.
10 * @license http://codeigniter.com/user_guide/license.html
11 * @link http://codeigniter.com
12 * @since Version 1.0
13 * @filesource
14 */
15
16// ------------------------------------------------------------------------
17
18/**
19 * Migration Class
20 *
21 * All migrations should implement this, forces up() and down() and gives
22 * access to the CI super-global.
23 *
24 * @package CodeIgniter
25 * @subpackage Libraries
26 * @category Libraries
27 * @author Reactor Engineers
28 * @link
29 */
30class CI_Migration {
31
32 private $_migration_enabled = FALSE;
33 private $_migration_path = NULL;
34 private $_migration_version = 0;
35
36 public $error = '';
37
38 function __construct($config = array())
39 {
40 # Only run this constructor on main library load
41 if (get_parent_class($this) !== FALSE)
42 {
43 return;
44 }
45
46 foreach ($config as $key => $val)
47 {
48 $this->{'_' . $key} = $val;
49 }
50
51 log_message('debug', 'Migrations class initialized');
52
53 // Are they trying to use migrations while it is disabled?
54 if ($this->_migration_enabled !== TRUE)
55 {
56 show_error('Migrations has been loaded but is disabled or set up incorrectly.');
57 }
58
59 // If not set, set it
60 $this->_migration_path == '' OR $this->_migration_path = APPPATH . 'migrations/';
61
62 // Add trailing slash if not set
63 $this->_migration_path = rtrim($this->_migration_path, '/').'/';
64
65 // They'll probably be using dbforge
66 $this->load->dbforge();
67
68 // If the migrations table is missing, make it
69 if ( ! $this->db->table_exists('migrations'))
70 {
71 $this->dbforge->add_field(array(
72 'version' => array('type' => 'INT', 'constraint' => 3),
73 ));
74
75 $this->dbforge->create_table('migrations', TRUE);
76
77 $this->db->insert('migrations', array('version' => 0));
78 }
79 }
80
81 // --------------------------------------------------------------------
82
83 /**
84 * Migrate to a schema version
85 *
86 * Calls each migration step required to get to the schema version of
87 * choice
88 *
89 * @access public
90 * @param $version integer Target schema version
91 * @return mixed TRUE if already latest, FALSE if failed, int if upgraded
92 */
93 function version($target_version)
94 {
95 $start = $current_version = $this->_get_version();
96 $stop = $target_version;
97
98 if ($target_version > $current_version)
99 {
100 // Moving Up
101 ++$start;
102 ++$stop;
103 $step = 1;
104 }
105
106 else
107 {
108 // Moving Down
109 $step = -1;
110 }
111
112 $method = $step === 1 ? 'up' : 'down';
113 $migrations = array();
114
115 // We now prepare to actually DO the migrations
116 // But first let's make sure that everything is the way it should be
117 for ($i = $start; $i != $stop; $i += $step)
118 {
119 $f = glob(sprintf($this->_migration_path . '%03d_*.php', $i));
120
121 // Only one migration per step is permitted
122 if (count($f) > 1)
123 {
124 $this->error = sprintf($this->lang->line('multiple_migration_version'), $i);
125 return FALSE;
126 }
127
128 // Migration step not found
129 if (count($f) == 0)
130 {
131 // If trying to migrate up to a version greater than the last
132 // existing one, migrate to the last one.
133 if ($step == 1)
134 {
135 break;
136 }
137
138 // If trying to migrate down but we're missing a step,
139 // something must definitely be wrong.
140 $this->error = sprintf($this->lang->line('migration_not_found'), $i);
141 return FALSE;
142 }
143
144 $file = basename($f[0]);
145 $name = basename($f[0], '.php');
146
147 // Filename validations
148 if (preg_match('/^\d{3}_(\w+)$/', $name, $match))
149 {
150 $match[1] = strtolower($match[1]);
151
152 // Cannot repeat a migration at different steps
153 if (in_array($match[1], $migrations))
154 {
155 $this->error = sprintf($this->lang->line('multiple_migrations_name'), $match[1]);
156 return FALSE;
157 }
158
159 include $f[0];
160 $class = 'Migration_' . ucfirst($match[1]);
161
162 if ( ! class_exists($class))
163 {
164 $this->error = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
165 return FALSE;
166 }
167
168 if ( ! is_callable(array($class, 'up')) || ! is_callable(array($class, 'down')))
169 {
170 $this->error = sprintf($this->lang->line('wrong_migration_interface'), $class);
171 return FALSE;
172 }
173
174 $migrations[] = $match[1];
175 }
176 else
177 {
178 $this->error = sprintf($this->lang->line('invalid_migration_filename'), $file);
179 return FALSE;
180 }
181 }
182
183 $this->log('Current schema version: ' . $current_version);
184
185 $version = $i + ($step == 1 ? -1 : 0);
186
187 // If there is nothing to do so quit
188 if ($migrations === array())
189 {
190 return TRUE;
191 }
192
193 $this->log('Moving ' . $method . ' to version ' . $version);
194
195 // Loop through the migrations
196 foreach ($migrations AS $migration)
197 {
198 // Run the migration class
199 $class = 'Migration_' . ucfirst(strtolower($migration));
200 call_user_func(array(new $class, $method));
201
202 $current_version += $step;
203 $this->_update_version($current_version);
204 }
205
206 $this->log('All done. Schema is at version '.$current_version);
207
208 return $current_version;
209 }
210
211 // --------------------------------------------------------------------
212
213 /**
214 * Set's the schema to the latest migration
215 *
216 * @access public
217 * @return mixed true if already latest, false if failed, int if upgraded
218 */
219 public function latest()
220 {
221 if ( ! $migrations = $this->find_migrations())
222 {
223 throw new Exception('no_migrations_found');
224 return false;
225 }
226
227 $last_migration = basename(end($migrations));
228
229 // Calculate the last migration step from existing migration
230 // filenames and procceed to the standard version migration
231 $last_version = intval(substr($last_migration, 0, 3));
232 return $this->version($last_version);
233 }
234
235 // --------------------------------------------------------------------
236
237 /**
238 * Set's the schema to the migration version set in config
239 *
240 * @access public
241 * @return mixed true if already current, false if failed, int if upgraded
242 */
243 public function current()
244 {
245 $version = $this->_migration_version;
246 return $this->version($version);
247 }
248
249 // --------------------------------------------------------------------
250
251 /**
252 * Set's the schema to the latest migration
253 *
254 * @access public
255 * @return mixed true if already latest, false if failed, int if upgraded
256 */
257
258 protected static function find_migrations()
259 {
260 // Load all *_*.php files in the migrations path
261 $files = glob($this->_migration_path . '*_*.php');
262 $file_count = count($files);
263
264 for ($i = 0; $i < $file_count; $i++)
265 {
266 // Mark wrongly formatted files as false for later filtering
267 $name = basename($files[$i], '.php');
268 if ( ! preg_match('/^\d{3}_(\w+)$/', $name))
269 {
270 $files[$i] = FALSE;
271 }
272 }
273
274 sort($files);
275
276 return $files;
277 }
278
279 // --------------------------------------------------------------------
280
281 /**
282 * Retrieves current schema version
283 *
284 * @access private
285 * @return integer Current Schema version
286 */
287 private function _get_version()
288 {
289 $row = $this->db->get('migrations')->row();
290 return $row ? $row->version : 0;
291 }
292
293 // --------------------------------------------------------------------
294
295 /**
296 * Stores the current schema version
297 *
298 * @access private
299 * @param $migrations integer Schema version reached
300 * @return void Outputs a report of the migration
301 */
302 private function _update_version($migrations)
303 {
304 return $this->db->update('migrations', array(
305 'version' => $migrations
306 ));
307 }
308
309 // --------------------------------------------------------------------
310
311 /**
312 * Stores the current schema version
313 *
314 * @access private
315 * @param $migrations integer Schema version reached
316 * @return void Outputs a report of the migration
317 */
318 private function log($text)
319 {
320 echo $text.'<br/>';
321 }
322
323 // --------------------------------------------------------------------
324
325 /**
326 * Enable the use of CI super-global
327 *
328 * @access public
329 * @param $var
330 * @return mixed
331 */
332 public function __get($var)
333 {
334 return get_instance()->$var;
335 }
336}