Migration.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <?php if ( ! defined('BASEPATH')) 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 - 2014, EllisLab, Inc.
  10. * @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/)
  11. * @license http://codeigniter.com/user_guide/license.html
  12. * @link http://codeigniter.com
  13. * @since Version 1.0
  14. * @filesource
  15. */
  16. // ------------------------------------------------------------------------
  17. /**
  18. * Migration Class
  19. *
  20. * All migrations should implement this, forces up() and down() and gives
  21. * access to the CI super-global.
  22. *
  23. * @package CodeIgniter
  24. * @subpackage Libraries
  25. * @category Libraries
  26. * @author Reactor Engineers
  27. * @link
  28. */
  29. class CI_Migration {
  30. protected $_migration_enabled = FALSE;
  31. protected $_migration_path = NULL;
  32. protected $_migration_version = 0;
  33. protected $_error_string = '';
  34. public function __construct($config = array())
  35. {
  36. # Only run this constructor on main library load
  37. if (get_parent_class($this) !== FALSE)
  38. {
  39. return;
  40. }
  41. foreach ($config as $key => $val)
  42. {
  43. $this->{'_' . $key} = $val;
  44. }
  45. log_message('debug', 'Migrations class initialized');
  46. // Are they trying to use migrations while it is disabled?
  47. if ($this->_migration_enabled !== TRUE)
  48. {
  49. show_error('Migrations has been loaded but is disabled or set up incorrectly.');
  50. }
  51. // If not set, set it
  52. $this->_migration_path == '' AND $this->_migration_path = APPPATH . 'migrations/';
  53. // Add trailing slash if not set
  54. $this->_migration_path = rtrim($this->_migration_path, '/').'/';
  55. // Load migration language
  56. $this->lang->load('migration');
  57. // They'll probably be using dbforge
  58. $this->load->dbforge();
  59. // If the migrations table is missing, make it
  60. if ( ! $this->db->table_exists('migrations'))
  61. {
  62. $this->dbforge->add_field(array(
  63. 'version' => array('type' => 'INT', 'constraint' => 3),
  64. ));
  65. $this->dbforge->create_table('migrations', TRUE);
  66. $this->db->insert('migrations', array('version' => 0));
  67. }
  68. }
  69. // --------------------------------------------------------------------
  70. /**
  71. * Migrate to a schema version
  72. *
  73. * Calls each migration step required to get to the schema version of
  74. * choice
  75. *
  76. * @param int Target schema version
  77. * @return mixed TRUE if already latest, FALSE if failed, int if upgraded
  78. */
  79. public function version($target_version)
  80. {
  81. $start = $current_version = $this->_get_version();
  82. $stop = $target_version;
  83. if ($target_version > $current_version)
  84. {
  85. // Moving Up
  86. ++$start;
  87. ++$stop;
  88. $step = 1;
  89. }
  90. else
  91. {
  92. // Moving Down
  93. $step = -1;
  94. }
  95. $method = ($step === 1) ? 'up' : 'down';
  96. $migrations = array();
  97. // We now prepare to actually DO the migrations
  98. // But first let's make sure that everything is the way it should be
  99. for ($i = $start; $i != $stop; $i += $step)
  100. {
  101. $f = glob(sprintf($this->_migration_path . '%03d_*.php', $i));
  102. // Only one migration per step is permitted
  103. if (count($f) > 1)
  104. {
  105. $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $i);
  106. return FALSE;
  107. }
  108. // Migration step not found
  109. if (count($f) == 0)
  110. {
  111. // If trying to migrate up to a version greater than the last
  112. // existing one, migrate to the last one.
  113. if ($step == 1)
  114. {
  115. break;
  116. }
  117. // If trying to migrate down but we're missing a step,
  118. // something must definitely be wrong.
  119. $this->_error_string = sprintf($this->lang->line('migration_not_found'), $i);
  120. return FALSE;
  121. }
  122. $file = basename($f[0]);
  123. $name = basename($f[0], '.php');
  124. // Filename validations
  125. if (preg_match('/^\d{3}_(\w+)$/', $name, $match))
  126. {
  127. $match[1] = strtolower($match[1]);
  128. // Cannot repeat a migration at different steps
  129. if (in_array($match[1], $migrations))
  130. {
  131. $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $match[1]);
  132. return FALSE;
  133. }
  134. include $f[0];
  135. $class = 'Migration_' . ucfirst($match[1]);
  136. if ( ! class_exists($class))
  137. {
  138. $this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
  139. return FALSE;
  140. }
  141. if ( ! is_callable(array($class, $method)))
  142. {
  143. $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class);
  144. return FALSE;
  145. }
  146. $migrations[] = $match[1];
  147. }
  148. else
  149. {
  150. $this->_error_string = sprintf($this->lang->line('migration_invalid_filename'), $file);
  151. return FALSE;
  152. }
  153. }
  154. log_message('debug', 'Current migration: ' . $current_version);
  155. $version = $i + ($step == 1 ? -1 : 0);
  156. // If there is nothing to do so quit
  157. if ($migrations === array())
  158. {
  159. return TRUE;
  160. }
  161. log_message('debug', 'Migrating from ' . $method . ' to version ' . $version);
  162. // Loop through the migrations
  163. foreach ($migrations AS $migration)
  164. {
  165. // Run the migration class
  166. $class = 'Migration_' . ucfirst(strtolower($migration));
  167. call_user_func(array(new $class, $method));
  168. $current_version += $step;
  169. $this->_update_version($current_version);
  170. }
  171. log_message('debug', 'Finished migrating to '.$current_version);
  172. return $current_version;
  173. }
  174. // --------------------------------------------------------------------
  175. /**
  176. * Set's the schema to the latest migration
  177. *
  178. * @return mixed true if already latest, false if failed, int if upgraded
  179. */
  180. public function latest()
  181. {
  182. if ( ! $migrations = $this->find_migrations())
  183. {
  184. $this->_error_string = $this->lang->line('migration_none_found');
  185. return false;
  186. }
  187. $last_migration = basename(end($migrations));
  188. // Calculate the last migration step from existing migration
  189. // filenames and procceed to the standard version migration
  190. return $this->version((int) substr($last_migration, 0, 3));
  191. }
  192. // --------------------------------------------------------------------
  193. /**
  194. * Set's the schema to the migration version set in config
  195. *
  196. * @return mixed true if already current, false if failed, int if upgraded
  197. */
  198. public function current()
  199. {
  200. return $this->version($this->_migration_version);
  201. }
  202. // --------------------------------------------------------------------
  203. /**
  204. * Error string
  205. *
  206. * @return string Error message returned as a string
  207. */
  208. public function error_string()
  209. {
  210. return $this->_error_string;
  211. }
  212. // --------------------------------------------------------------------
  213. /**
  214. * Set's the schema to the latest migration
  215. *
  216. * @return mixed true if already latest, false if failed, int if upgraded
  217. */
  218. protected function find_migrations()
  219. {
  220. // Load all *_*.php files in the migrations path
  221. $files = glob($this->_migration_path . '*_*.php');
  222. $file_count = count($files);
  223. for ($i = 0; $i < $file_count; $i++)
  224. {
  225. // Mark wrongly formatted files as false for later filtering
  226. $name = basename($files[$i], '.php');
  227. if ( ! preg_match('/^\d{3}_(\w+)$/', $name))
  228. {
  229. $files[$i] = FALSE;
  230. }
  231. }
  232. sort($files);
  233. return $files;
  234. }
  235. // --------------------------------------------------------------------
  236. /**
  237. * Retrieves current schema version
  238. *
  239. * @return int Current Migration
  240. */
  241. protected function _get_version()
  242. {
  243. $row = $this->db->get('migrations')->row();
  244. return $row ? $row->version : 0;
  245. }
  246. // --------------------------------------------------------------------
  247. /**
  248. * Stores the current schema version
  249. *
  250. * @param int Migration reached
  251. * @return bool
  252. */
  253. protected function _update_version($migrations)
  254. {
  255. return $this->db->update('migrations', array(
  256. 'version' => $migrations
  257. ));
  258. }
  259. // --------------------------------------------------------------------
  260. /**
  261. * Enable the use of CI super-global
  262. *
  263. * @param mixed $var
  264. * @return mixed
  265. */
  266. public function __get($var)
  267. {
  268. return get_instance()->$var;
  269. }
  270. }
  271. /* End of file Migration.php */
  272. /* Location: ./system/libraries/Migration.php */