PageRenderTime 610ms CodeModel.GetById 40ms RepoModel.GetById 7ms app.codeStats 0ms

/system/libraries/Migration.php

https://github.com/deanhowe/CodeIgniter
PHP | 391 lines | 171 code | 63 blank | 157 comment | 21 complexity | 8f8f77bb4e6df4af53d6a7777dd55a36 MD5 | raw file
  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.2.4 or newer
  6. *
  7. * NOTICE OF LICENSE
  8. *
  9. * Licensed under the Open Software License version 3.0
  10. *
  11. * This source file is subject to the Open Software License (OSL 3.0) that is
  12. * bundled with this package in the files license.txt / license.rst. It is
  13. * also available through the world wide web at this URL:
  14. * http://opensource.org/licenses/OSL-3.0
  15. * If you did not receive a copy of the license and are unable to obtain it
  16. * through the world wide web, please send an email to
  17. * licensing@ellislab.com so we can send you a copy immediately.
  18. *
  19. * @package CodeIgniter
  20. * @author EllisLab Dev Team
  21. * @copyright Copyright (c) 2006 - 2012, EllisLab, Inc. (http://ellislab.com/)
  22. * @license http://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
  23. * @link http://codeigniter.com
  24. * @since Version 3.0
  25. * @filesource
  26. */
  27. /**
  28. * Migration Class
  29. *
  30. * All migrations should implement this, forces up() and down() and gives
  31. * access to the CI super-global.
  32. *
  33. * @package CodeIgniter
  34. * @subpackage Libraries
  35. * @category Libraries
  36. * @author Reactor Engineers
  37. * @link
  38. */
  39. class CI_Migration {
  40. /**
  41. * Whether the library is enabled
  42. *
  43. * @var bool
  44. */
  45. protected $_migration_enabled = FALSE;
  46. /**
  47. * Path to migration classes
  48. *
  49. * @var string
  50. */
  51. protected $_migration_path = NULL;
  52. /**
  53. * Current migration version
  54. *
  55. * @var mixed
  56. */
  57. protected $_migration_version = 0;
  58. /**
  59. * Database table with migration info
  60. *
  61. * @var string
  62. */
  63. protected $_migration_table = 'migrations';
  64. /**
  65. * Whether to automatically run migrations
  66. *
  67. * @var bool
  68. */
  69. protected $_migration_auto_latest = FALSE;
  70. /**
  71. * Error message
  72. *
  73. * @var string
  74. */
  75. protected $_error_string = '';
  76. /**
  77. * Initialize Migration Class
  78. *
  79. * @param array
  80. * @return void
  81. */
  82. public function __construct($config = array())
  83. {
  84. # Only run this constructor on main library load
  85. if (get_parent_class($this) !== FALSE)
  86. {
  87. return;
  88. }
  89. foreach ($config as $key => $val)
  90. {
  91. $this->{'_'.$key} = $val;
  92. }
  93. log_message('debug', 'Migrations class initialized');
  94. // Are they trying to use migrations while it is disabled?
  95. if ($this->_migration_enabled !== TRUE)
  96. {
  97. show_error('Migrations has been loaded but is disabled or set up incorrectly.');
  98. }
  99. // If not set, set it
  100. $this->_migration_path !== '' OR $this->_migration_path = APPPATH.'migrations/';
  101. // Add trailing slash if not set
  102. $this->_migration_path = rtrim($this->_migration_path, '/').'/';
  103. // Load migration language
  104. $this->lang->load('migration');
  105. // They'll probably be using dbforge
  106. $this->load->dbforge();
  107. // Make sure the migration table name was set.
  108. if (empty($this->_migration_table))
  109. {
  110. show_error('Migrations configuration file (migration.php) must have "migration_table" set.');
  111. }
  112. // If the migrations table is missing, make it
  113. if ( ! $this->db->table_exists($this->_migration_table))
  114. {
  115. $this->dbforge->add_field(array(
  116. 'version' => array('type' => 'INT', 'constraint' => 3),
  117. ));
  118. $this->dbforge->create_table($this->_migration_table, TRUE);
  119. $this->db->insert($this->_migration_table, array('version' => 0));
  120. }
  121. // Do we auto migrate to the latest migration?
  122. if ($this->_migration_auto_latest === TRUE && ! $this->latest())
  123. {
  124. show_error($this->error_string());
  125. }
  126. }
  127. // --------------------------------------------------------------------
  128. /**
  129. * Migrate to a schema version
  130. *
  131. * Calls each migration step required to get to the schema version of
  132. * choice
  133. *
  134. * @param int Target schema version
  135. * @return mixed TRUE if already latest, FALSE if failed, int if upgraded
  136. */
  137. public function version($target_version)
  138. {
  139. $start = $current_version = $this->_get_version();
  140. $stop = $target_version;
  141. if ($target_version > $current_version)
  142. {
  143. // Moving Up
  144. ++$start;
  145. ++$stop;
  146. $step = 1;
  147. }
  148. else
  149. {
  150. // Moving Down
  151. $step = -1;
  152. }
  153. $method = $step === 1 ? 'up' : 'down';
  154. $migrations = array();
  155. // We now prepare to actually DO the migrations
  156. // But first let's make sure that everything is the way it should be
  157. for ($i = $start; $i != $stop; $i += $step)
  158. {
  159. $f = glob(sprintf($this->_migration_path.'%03d_*.php', $i));
  160. // Only one migration per step is permitted
  161. if (count($f) > 1)
  162. {
  163. $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $i);
  164. return FALSE;
  165. }
  166. // Migration step not found
  167. if (count($f) === 0)
  168. {
  169. // If trying to migrate up to a version greater than the last
  170. // existing one, migrate to the last one.
  171. if ($step === 1)
  172. {
  173. break;
  174. }
  175. // If trying to migrate down but we're missing a step,
  176. // something must definitely be wrong.
  177. $this->_error_string = sprintf($this->lang->line('migration_not_found'), $i);
  178. return FALSE;
  179. }
  180. $file = basename($f[0]);
  181. $name = basename($f[0], '.php');
  182. // Filename validations
  183. if (preg_match('/^\d{3}_(\w+)$/', $name, $match))
  184. {
  185. $match[1] = strtolower($match[1]);
  186. // Cannot repeat a migration at different steps
  187. if (in_array($match[1], $migrations))
  188. {
  189. $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $match[1]);
  190. return FALSE;
  191. }
  192. include $f[0];
  193. $class = 'Migration_'.ucfirst($match[1]);
  194. if ( ! class_exists($class))
  195. {
  196. $this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
  197. return FALSE;
  198. }
  199. if ( ! is_callable(array($class, $method)))
  200. {
  201. $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class);
  202. return FALSE;
  203. }
  204. $migrations[] = $match[1];
  205. }
  206. else
  207. {
  208. $this->_error_string = sprintf($this->lang->line('migration_invalid_filename'), $file);
  209. return FALSE;
  210. }
  211. }
  212. log_message('debug', 'Current migration: '.$current_version);
  213. $version = $i + ($step === 1 ? -1 : 0);
  214. // If there is nothing to do so quit
  215. if ($migrations === array())
  216. {
  217. return TRUE;
  218. }
  219. log_message('debug', 'Migrating from '.$method.' to version '.$version);
  220. // Loop through the migrations
  221. foreach ($migrations AS $migration)
  222. {
  223. // Run the migration class
  224. $class = 'Migration_'.ucfirst(strtolower($migration));
  225. call_user_func(array(new $class, $method));
  226. $current_version += $step;
  227. $this->_update_version($current_version);
  228. }
  229. log_message('debug', 'Finished migrating to '.$current_version);
  230. return $current_version;
  231. }
  232. // --------------------------------------------------------------------
  233. /**
  234. * Set's the schema to the latest migration
  235. *
  236. * @return mixed true if already latest, false if failed, int if upgraded
  237. */
  238. public function latest()
  239. {
  240. if ( ! $migrations = $this->find_migrations())
  241. {
  242. $this->_error_string = $this->lang->line('migration_none_found');
  243. return FALSE;
  244. }
  245. $last_migration = basename(end($migrations));
  246. // Calculate the last migration step from existing migration
  247. // filenames and procceed to the standard version migration
  248. return $this->version((int) $last_migration);
  249. }
  250. // --------------------------------------------------------------------
  251. /**
  252. * Set's the schema to the migration version set in config
  253. *
  254. * @return mixed true if already current, false if failed, int if upgraded
  255. */
  256. public function current()
  257. {
  258. return $this->version($this->_migration_version);
  259. }
  260. // --------------------------------------------------------------------
  261. /**
  262. * Error string
  263. *
  264. * @return string Error message returned as a string
  265. */
  266. public function error_string()
  267. {
  268. return $this->_error_string;
  269. }
  270. // --------------------------------------------------------------------
  271. /**
  272. * Retrieves list of available migration scripts
  273. *
  274. * @return array list of migration file paths sorted by version
  275. */
  276. protected function find_migrations()
  277. {
  278. // Load all *_*.php files in the migrations path
  279. $files = glob($this->_migration_path.'*_*.php');
  280. for ($i = 0, $c = count($files); $i < $c; $i++)
  281. {
  282. // Mark wrongly formatted files as false for later filtering
  283. if ( ! preg_match('/^\d{3}_(\w+)$/', basename($files[$i], '.php')))
  284. {
  285. $files[$i] = FALSE;
  286. }
  287. }
  288. sort($files);
  289. return $files;
  290. }
  291. // --------------------------------------------------------------------
  292. /**
  293. * Retrieves current schema version
  294. *
  295. * @return int Current Migration
  296. */
  297. protected function _get_version()
  298. {
  299. $row = $this->db->select('version')->get($this->_migration_table)->row();
  300. return $row ? $row->version : 0;
  301. }
  302. // --------------------------------------------------------------------
  303. /**
  304. * Stores the current schema version
  305. *
  306. * @param int Migration reached
  307. * @return void Outputs a report of the migration
  308. */
  309. protected function _update_version($migrations)
  310. {
  311. return $this->db->update($this->_migration_table, array(
  312. 'version' => $migrations
  313. ));
  314. }
  315. // --------------------------------------------------------------------
  316. /**
  317. * Enable the use of CI super-global
  318. *
  319. * @param $var
  320. * @return mixed
  321. */
  322. public function __get($var)
  323. {
  324. return get_instance()->$var;
  325. }
  326. }
  327. /* End of file Migration.php */
  328. /* Location: ./system/libraries/Migration.php */