diff options
author | Phil Sturgeon <email@philsturgeon.co.uk> | 2011-02-07 21:39:00 +0100 |
---|---|---|
committer | Phil Sturgeon <email@philsturgeon.co.uk> | 2011-02-07 21:39:00 +0100 |
commit | 9758d84b69185f80fd8197f28046af7ef3b2a2d3 (patch) | |
tree | ed4d30bd2b0d9bee4603cd5cd2943969e59f7119 | |
parent | ef112c0830df4a31563351125888b0d522a1c965 (diff) |
Added Migrations library, config and an example controller/migration file.
-rw-r--r-- | application/config/migration.php | 38 | ||||
-rw-r--r-- | application/controllers/migrate.php | 40 | ||||
-rw-r--r-- | application/migrations/001_Create_accounts.php | 32 | ||||
-rw-r--r-- | system/libraries/Migration.php | 336 |
4 files changed, 446 insertions, 0 deletions
diff --git a/application/config/migration.php b/application/config/migration.php new file mode 100644 index 000000000..37b1b8534 --- /dev/null +++ b/application/config/migration.php @@ -0,0 +1,38 @@ +<?php defined('BASEPATH') OR exit('No direct script access allowed');
+/*
+|--------------------------------------------------------------------------
+| Enable/Disable Migrations
+|--------------------------------------------------------------------------
+|
+| Migrations are disabled by default for security reasons.
+| You should enable migrations whenever you intend to do a schema migration
+| and disable it back when you're done.
+|
+*/
+$config['migration_enabled'] = TRUE;
+
+
+/*
+|--------------------------------------------------------------------------
+| Migrations version
+|--------------------------------------------------------------------------
+|
+| This is used to set migration version that the file system should be on.
+| If you run $this->migration->latest() this is the version that schema will
+| be upgraded / downgraded to.
+|
+*/
+$config['migration_version'] = 1;
+
+
+/*
+|--------------------------------------------------------------------------
+| Migrations Path
+|--------------------------------------------------------------------------
+|
+| Path to your migrations folder.
+| Typically, it will be within your application path.
+| Also, writing permission is required within the migrations path.
+|
+*/
+$config['migration_path'] = APPPATH . 'migrations/';
diff --git a/application/controllers/migrate.php b/application/controllers/migrate.php new file mode 100644 index 000000000..e5442e79c --- /dev/null +++ b/application/controllers/migrate.php @@ -0,0 +1,40 @@ +<?php +class Migrate extends CI_Controller +{ + function __construct() + { + parent::__construct(); + + $this->load->library('migration'); + + /** VERY IMPORTANT - only turn this on when you need it. */ +// show_error('Access to this controller is blocked, turn me on when you need me.'); + } + + // Install up to the most up-to-date version. + function install() + { + if ( ! $this->migration->current()) + { + show_error($this->migration->error); + exit; + } + + echo "<br />Migration Successful<br />"; + } + + // This will migrate up to the configed migration version + function version($id = NULL) + { + // No $id supplied? Use the config version + $id OR $id = $this->config->item('migration_version'); + + if ( ! $this->migration->version($id)) + { + show_error($this->migration->error); + exit; + } + + echo "<br />Migration Successful<br />"; + } +} diff --git a/application/migrations/001_Create_accounts.php b/application/migrations/001_Create_accounts.php new file mode 100644 index 000000000..4b2fc936f --- /dev/null +++ b/application/migrations/001_Create_accounts.php @@ -0,0 +1,32 @@ +<?php defined('BASEPATH') OR exit('No direct script access allowed');
+
+class Migration_Create_accounts extends CI_Migration {
+
+ function up()
+ {
+ if ( ! $this->db->table_exists('accounts'))
+ {
+ // Setup Keys
+ $this->dbforge->add_key('id', TRUE);
+
+ $this->dbforge->add_field(array(
+ 'id' => array('type' => 'INT', 'constraint' => 5, 'unsigned' => TRUE, 'auto_increment' => TRUE),
+ 'company_name' => array('type' => 'VARCHAR', 'constraint' => '200', 'null' => FALSE),
+ 'first_name' => array('type' => 'VARCHAR', 'constraint' => '200', 'null' => FALSE),
+ 'last_name' => array('type' => 'VARCHAR', 'constraint' => '200', 'null' => FALSE),
+ 'phone' => array('type' => 'TEXT', 'null' => FALSE),
+ 'email' => array('type' => 'TEXT', 'null' => FALSE),
+ 'address' => array('type' => 'TEXT', 'null' => FALSE),
+ 'Last_Update' => array('type' => 'DATETIME', 'null' => FALSE)
+ ));
+
+ $this->dbforge->add_field("Created_At TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");
+ $this->dbforge->create_table('accounts', TRUE);
+ }
+ }
+
+ function down()
+ {
+ $this->dbforge->drop_table('accounts');
+ }
+}
diff --git a/system/libraries/Migration.php b/system/libraries/Migration.php new file mode 100644 index 000000000..73c55c346 --- /dev/null +++ b/system/libraries/Migration.php @@ -0,0 +1,336 @@ +<?php defined('BASEPATH') OR exit('No direct script access allowed'); +/** + * CodeIgniter + * + * An open source application development framework for PHP 5.1.6 or newer + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2006 - 2011, EllisLab, Inc. + * @license http://codeigniter.com/user_guide/license.html + * @link http://codeigniter.com + * @since Version 1.0 + * @filesource + */ + +// ------------------------------------------------------------------------ + +/** + * Migration Class + * + * All migrations should implement this, forces up() and down() and gives + * access to the CI super-global. + * + * @package CodeIgniter + * @subpackage Libraries + * @category Libraries + * @author Reactor Engineers + * @link + */ +class CI_Migration { + + private $_migration_enabled = FALSE; + private $_migration_path = NULL; + private $_migration_version = 0; + + public $error = ''; + + function __construct($config = array()) + { + # Only run this constructor on main library load + if (get_parent_class($this) !== FALSE) + { + return; + } + + foreach ($config as $key => $val) + { + $this->{'_' . $key} = $val; + } + + log_message('debug', 'Migrations class initialized'); + + // Are they trying to use migrations while it is disabled? + if ($this->_migration_enabled !== TRUE) + { + show_error('Migrations has been loaded but is disabled or set up incorrectly.'); + } + + // If not set, set it + $this->_migration_path == '' OR $this->_migration_path = APPPATH . 'migrations/'; + + // Add trailing slash if not set + $this->_migration_path = rtrim($this->_migration_path, '/').'/'; + + // They'll probably be using dbforge + $this->load->dbforge(); + + // If the migrations table is missing, make it + if ( ! $this->db->table_exists('migrations')) + { + $this->dbforge->add_field(array( + 'version' => array('type' => 'INT', 'constraint' => 3), + )); + + $this->dbforge->create_table('migrations', TRUE); + + $this->db->insert('migrations', array('version' => 0)); + } + } + + // -------------------------------------------------------------------- + + /** + * Migrate to a schema version + * + * Calls each migration step required to get to the schema version of + * choice + * + * @access public + * @param $version integer Target schema version + * @return mixed TRUE if already latest, FALSE if failed, int if upgraded + */ + function version($target_version) + { + $start = $current_version = $this->_get_version(); + $stop = $target_version; + + if ($target_version > $current_version) + { + // Moving Up + ++$start; + ++$stop; + $step = 1; + } + + else + { + // Moving Down + $step = -1; + } + + $method = $step === 1 ? 'up' : 'down'; + $migrations = array(); + + // We now prepare to actually DO the migrations + // But first let's make sure that everything is the way it should be + for ($i = $start; $i != $stop; $i += $step) + { + $f = glob(sprintf($this->_migration_path . '%03d_*.php', $i)); + + // Only one migration per step is permitted + if (count($f) > 1) + { + $this->error = sprintf($this->lang->line('multiple_migration_version'), $i); + return FALSE; + } + + // Migration step not found + if (count($f) == 0) + { + // If trying to migrate up to a version greater than the last + // existing one, migrate to the last one. + if ($step == 1) + { + break; + } + + // If trying to migrate down but we're missing a step, + // something must definitely be wrong. + $this->error = sprintf($this->lang->line('migration_not_found'), $i); + return FALSE; + } + + $file = basename($f[0]); + $name = basename($f[0], '.php'); + + // Filename validations + if (preg_match('/^\d{3}_(\w+)$/', $name, $match)) + { + $match[1] = strtolower($match[1]); + + // Cannot repeat a migration at different steps + if (in_array($match[1], $migrations)) + { + $this->error = sprintf($this->lang->line('multiple_migrations_name'), $match[1]); + return FALSE; + } + + include $f[0]; + $class = 'Migration_' . ucfirst($match[1]); + + if ( ! class_exists($class)) + { + $this->error = sprintf($this->lang->line('migration_class_doesnt_exist'), $class); + return FALSE; + } + + if ( ! is_callable(array($class, 'up')) || ! is_callable(array($class, 'down'))) + { + $this->error = sprintf($this->lang->line('wrong_migration_interface'), $class); + return FALSE; + } + + $migrations[] = $match[1]; + } + else + { + $this->error = sprintf($this->lang->line('invalid_migration_filename'), $file); + return FALSE; + } + } + + $this->log('Current schema version: ' . $current_version); + + $version = $i + ($step == 1 ? -1 : 0); + + // If there is nothing to do so quit + if ($migrations === array()) + { + return TRUE; + } + + $this->log('Moving ' . $method . ' to version ' . $version); + + // Loop through the migrations + foreach ($migrations AS $migration) + { + // Run the migration class + $class = 'Migration_' . ucfirst(strtolower($migration)); + call_user_func(array(new $class, $method)); + + $current_version += $step; + $this->_update_version($current_version); + } + + $this->log('All done. Schema is at version '.$current_version); + + return $current_version; + } + + // -------------------------------------------------------------------- + + /** + * Set's the schema to the latest migration + * + * @access public + * @return mixed true if already latest, false if failed, int if upgraded + */ + public function latest() + { + if ( ! $migrations = $this->find_migrations()) + { + throw new Exception('no_migrations_found'); + return false; + } + + $last_migration = basename(end($migrations)); + + // Calculate the last migration step from existing migration + // filenames and procceed to the standard version migration + $last_version = intval(substr($last_migration, 0, 3)); + return $this->version($last_version); + } + + // -------------------------------------------------------------------- + + /** + * Set's the schema to the migration version set in config + * + * @access public + * @return mixed true if already current, false if failed, int if upgraded + */ + public function current() + { + $version = $this->_migration_version; + return $this->version($version); + } + + // -------------------------------------------------------------------- + + /** + * Set's the schema to the latest migration + * + * @access public + * @return mixed true if already latest, false if failed, int if upgraded + */ + + protected static function find_migrations() + { + // Load all *_*.php files in the migrations path + $files = glob($this->_migration_path . '*_*.php'); + $file_count = count($files); + + for ($i = 0; $i < $file_count; $i++) + { + // Mark wrongly formatted files as false for later filtering + $name = basename($files[$i], '.php'); + if ( ! preg_match('/^\d{3}_(\w+)$/', $name)) + { + $files[$i] = FALSE; + } + } + + sort($files); + + return $files; + } + + // -------------------------------------------------------------------- + + /** + * Retrieves current schema version + * + * @access private + * @return integer Current Schema version + */ + private function _get_version() + { + $row = $this->db->get('migrations')->row(); + return $row ? $row->version : 0; + } + + // -------------------------------------------------------------------- + + /** + * Stores the current schema version + * + * @access private + * @param $migrations integer Schema version reached + * @return void Outputs a report of the migration + */ + private function _update_version($migrations) + { + return $this->db->update('migrations', array( + 'version' => $migrations + )); + } + + // -------------------------------------------------------------------- + + /** + * Stores the current schema version + * + * @access private + * @param $migrations integer Schema version reached + * @return void Outputs a report of the migration + */ + private function log($text) + { + echo $text.'<br/>'; + } + + // -------------------------------------------------------------------- + + /** + * Enable the use of CI super-global + * + * @access public + * @param $var + * @return mixed + */ + public function __get($var) + { + return get_instance()->$var; + } +}
\ No newline at end of file |