blob: 3943ec1304bd31c354783e81987f839d003aff6e [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
Phil Sturgeon96bd33b2011-05-04 01:30:36 +010032 protected $_migration_enabled = FALSE;
33 protected $_migration_path = NULL;
34 protected $_migration_version = 0;
Phil Sturgeon9758d842011-02-07 20:39:00 +000035
Phil Sturgeoncb06c652011-05-04 10:50:25 +010036 protected $_error_string = '';
Phil Sturgeon9758d842011-02-07 20:39:00 +000037
Phil Sturgeon96bd33b2011-05-04 01:30:36 +010038 public function __construct($config = array())
Phil Sturgeon9758d842011-02-07 20:39:00 +000039 {
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
Phil Sturgeon96bd33b2011-05-04 01:30:36 +010065 // Load migration language
66 $this->lang->load('migration');
67
Phil Sturgeon9758d842011-02-07 20:39:00 +000068 // They'll probably be using dbforge
69 $this->load->dbforge();
70
71 // If the migrations table is missing, make it
72 if ( ! $this->db->table_exists('migrations'))
73 {
74 $this->dbforge->add_field(array(
75 'version' => array('type' => 'INT', 'constraint' => 3),
76 ));
77
78 $this->dbforge->create_table('migrations', TRUE);
79
80 $this->db->insert('migrations', array('version' => 0));
81 }
82 }
83
84 // --------------------------------------------------------------------
85
86 /**
87 * Migrate to a schema version
88 *
89 * Calls each migration step required to get to the schema version of
90 * choice
91 *
92 * @access public
93 * @param $version integer Target schema version
94 * @return mixed TRUE if already latest, FALSE if failed, int if upgraded
95 */
Phil Sturgeon96bd33b2011-05-04 01:30:36 +010096 public function version($target_version)
Phil Sturgeon9758d842011-02-07 20:39:00 +000097 {
98 $start = $current_version = $this->_get_version();
99 $stop = $target_version;
100
101 if ($target_version > $current_version)
102 {
103 // Moving Up
104 ++$start;
105 ++$stop;
106 $step = 1;
107 }
108
109 else
110 {
111 // Moving Down
112 $step = -1;
113 }
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100114
Phil Sturgeon9758d842011-02-07 20:39:00 +0000115 $method = $step === 1 ? 'up' : 'down';
116 $migrations = array();
117
118 // We now prepare to actually DO the migrations
119 // But first let's make sure that everything is the way it should be
120 for ($i = $start; $i != $stop; $i += $step)
121 {
122 $f = glob(sprintf($this->_migration_path . '%03d_*.php', $i));
123
124 // Only one migration per step is permitted
125 if (count($f) > 1)
126 {
Phil Sturgeoncb06c652011-05-04 10:50:25 +0100127 $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $i);
Phil Sturgeon9758d842011-02-07 20:39:00 +0000128 return FALSE;
129 }
130
131 // Migration step not found
132 if (count($f) == 0)
133 {
134 // If trying to migrate up to a version greater than the last
135 // existing one, migrate to the last one.
136 if ($step == 1)
137 {
138 break;
139 }
140
141 // If trying to migrate down but we're missing a step,
142 // something must definitely be wrong.
Phil Sturgeoncb06c652011-05-04 10:50:25 +0100143 $this->_error_string = sprintf($this->lang->line('migration_not_found'), $i);
Phil Sturgeon9758d842011-02-07 20:39:00 +0000144 return FALSE;
145 }
146
147 $file = basename($f[0]);
148 $name = basename($f[0], '.php');
149
150 // Filename validations
151 if (preg_match('/^\d{3}_(\w+)$/', $name, $match))
152 {
153 $match[1] = strtolower($match[1]);
154
155 // Cannot repeat a migration at different steps
156 if (in_array($match[1], $migrations))
157 {
Phil Sturgeoncb06c652011-05-04 10:50:25 +0100158 $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $match[1]);
Phil Sturgeon9758d842011-02-07 20:39:00 +0000159 return FALSE;
160 }
161
162 include $f[0];
163 $class = 'Migration_' . ucfirst($match[1]);
164
165 if ( ! class_exists($class))
166 {
Phil Sturgeoncb06c652011-05-04 10:50:25 +0100167 $this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
Phil Sturgeon9758d842011-02-07 20:39:00 +0000168 return FALSE;
169 }
170
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100171 if ( ! is_callable(array($class, $method)))
Phil Sturgeon9758d842011-02-07 20:39:00 +0000172 {
Phil Sturgeoncb06c652011-05-04 10:50:25 +0100173 $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class);
Phil Sturgeon9758d842011-02-07 20:39:00 +0000174 return FALSE;
175 }
176
177 $migrations[] = $match[1];
178 }
179 else
180 {
Phil Sturgeoncb06c652011-05-04 10:50:25 +0100181 $this->_error_string = sprintf($this->lang->line('migration_invalid_filename'), $file);
Phil Sturgeon9758d842011-02-07 20:39:00 +0000182 return FALSE;
183 }
184 }
185
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100186 log_message('debug', 'Current migration: ' . $current_version);
Phil Sturgeon9758d842011-02-07 20:39:00 +0000187
188 $version = $i + ($step == 1 ? -1 : 0);
189
190 // If there is nothing to do so quit
191 if ($migrations === array())
192 {
193 return TRUE;
194 }
195
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100196 log_message('debug', 'Migrating from ' . $method . ' to version ' . $version);
Phil Sturgeon9758d842011-02-07 20:39:00 +0000197
198 // Loop through the migrations
199 foreach ($migrations AS $migration)
200 {
201 // Run the migration class
202 $class = 'Migration_' . ucfirst(strtolower($migration));
203 call_user_func(array(new $class, $method));
204
205 $current_version += $step;
206 $this->_update_version($current_version);
207 }
208
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100209 log_message('debug', 'Finished migrating to '.$current_version);
Phil Sturgeon9758d842011-02-07 20:39:00 +0000210
211 return $current_version;
212 }
213
214 // --------------------------------------------------------------------
215
216 /**
217 * Set's the schema to the latest migration
218 *
219 * @access public
220 * @return mixed true if already latest, false if failed, int if upgraded
221 */
222 public function latest()
223 {
224 if ( ! $migrations = $this->find_migrations())
225 {
Phil Sturgeoncb06c652011-05-04 10:50:25 +0100226 $this->_error_string = $this->line->lang('migration_none_found');
Phil Sturgeon9758d842011-02-07 20:39:00 +0000227 return false;
228 }
229
230 $last_migration = basename(end($migrations));
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100231
Phil Sturgeon9758d842011-02-07 20:39:00 +0000232 // Calculate the last migration step from existing migration
233 // filenames and procceed to the standard version migration
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100234 return $this->version((int) substr($last_migration, 0, 3));
Phil Sturgeon9758d842011-02-07 20:39:00 +0000235 }
236
237 // --------------------------------------------------------------------
238
239 /**
240 * Set's the schema to the migration version set in config
241 *
242 * @access public
243 * @return mixed true if already current, false if failed, int if upgraded
244 */
245 public function current()
246 {
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100247 return $this->version($this->_migration_version);
248 }
249
250 // --------------------------------------------------------------------
251
252 /**
253 * Error string
254 *
255 * @access public
256 * @return string Error message returned as a string
257 */
258 public function error_string()
259 {
Phil Sturgeoncb06c652011-05-04 10:50:25 +0100260 return $this->_error_string;
Phil Sturgeon9758d842011-02-07 20:39:00 +0000261 }
262
263 // --------------------------------------------------------------------
264
265 /**
266 * Set's the schema to the latest migration
267 *
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100268 * @access protected
Phil Sturgeon9758d842011-02-07 20:39:00 +0000269 * @return mixed true if already latest, false if failed, int if upgraded
270 */
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100271 protected function find_migrations()
Phil Sturgeon9758d842011-02-07 20:39:00 +0000272 {
273 // Load all *_*.php files in the migrations path
274 $files = glob($this->_migration_path . '*_*.php');
275 $file_count = count($files);
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100276
Phil Sturgeon9758d842011-02-07 20:39:00 +0000277 for ($i = 0; $i < $file_count; $i++)
278 {
279 // Mark wrongly formatted files as false for later filtering
280 $name = basename($files[$i], '.php');
281 if ( ! preg_match('/^\d{3}_(\w+)$/', $name))
282 {
283 $files[$i] = FALSE;
284 }
285 }
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100286
Phil Sturgeon9758d842011-02-07 20:39:00 +0000287 sort($files);
288
289 return $files;
290 }
291
292 // --------------------------------------------------------------------
293
294 /**
295 * Retrieves current schema version
296 *
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100297 * @access protected
298 * @return integer Current Migration
Phil Sturgeon9758d842011-02-07 20:39:00 +0000299 */
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100300 protected function _get_version()
Phil Sturgeon9758d842011-02-07 20:39:00 +0000301 {
302 $row = $this->db->get('migrations')->row();
303 return $row ? $row->version : 0;
304 }
305
306 // --------------------------------------------------------------------
307
308 /**
309 * Stores the current schema version
310 *
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100311 * @access protected
312 * @param $migrations integer Migration reached
Phil Sturgeon9758d842011-02-07 20:39:00 +0000313 * @return void Outputs a report of the migration
314 */
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100315 protected function _update_version($migrations)
Phil Sturgeon9758d842011-02-07 20:39:00 +0000316 {
317 return $this->db->update('migrations', array(
318 'version' => $migrations
319 ));
320 }
321
322 // --------------------------------------------------------------------
323
324 /**
Phil Sturgeon9758d842011-02-07 20:39:00 +0000325 * Enable the use of CI super-global
326 *
327 * @access public
328 * @param $var
329 * @return mixed
330 */
331 public function __get($var)
332 {
333 return get_instance()->$var;
334 }
Phil Sturgeon96bd33b2011-05-04 01:30:36 +0100335}
336
337/* End of file Migration.php */
338/* Location: ./system/libraries/Migration.php */