summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--application/config/migration.php18
-rw-r--r--system/language/english/migration_lang.php1
-rw-r--r--system/libraries/Migration.php230
-rw-r--r--user_guide_src/source/changelog.rst3
-rw-r--r--user_guide_src/source/libraries/migration.rst84
5 files changed, 207 insertions, 129 deletions
diff --git a/application/config/migration.php b/application/config/migration.php
index 7645ade7c..476da1b8e 100644
--- a/application/config/migration.php
+++ b/application/config/migration.php
@@ -39,6 +39,24 @@ $config['migration_enabled'] = FALSE;
/*
|--------------------------------------------------------------------------
+| Migration Style
+|--------------------------------------------------------------------------
+|
+| Migration file names may be based on a sequential identifier or on
+| a timestamp. Options are:
+|
+| 'sequential' = Default migration naming (001_add_blog.php)
+| 'timestamp' = Timestamp migration naming (20121031104401_add_blog.php)
+| Use timestamp format YYYYMMDDHHIISS.
+|
+| If this configuration value is missing the Migration library defaults
+| to 'sequential' for backward compatibility.
+|
+*/
+$config['migration_style'] = 'timestamp';
+
+/*
+|--------------------------------------------------------------------------
| Migrations table
|--------------------------------------------------------------------------
|
diff --git a/system/language/english/migration_lang.php b/system/language/english/migration_lang.php
index 5753c00bf..a262d3018 100644
--- a/system/language/english/migration_lang.php
+++ b/system/language/english/migration_lang.php
@@ -27,6 +27,7 @@
$lang['migration_none_found'] = 'No migrations were found.';
$lang['migration_not_found'] = 'No migration could be found with the version number: %d.';
+$lang['migration_sequence_gap'] = 'There is a gap in the migration sequence near version number: %d.';
$lang['migration_multiple_version'] = 'There are multiple migrations with the same version number: %d.';
$lang['migration_class_doesnt_exist'] = 'The migration class "%s" could not be found.';
$lang['migration_missing_up_method'] = 'The migration class "%s" is missing an "up" method.';
diff --git a/system/libraries/Migration.php b/system/libraries/Migration.php
index 5d637d44a..2a06aa011 100644
--- a/system/libraries/Migration.php
+++ b/system/libraries/Migration.php
@@ -45,6 +45,13 @@ class CI_Migration {
* @var bool
*/
protected $_migration_enabled = FALSE;
+
+ /**
+ * Migration numbering style
+ *
+ * @var bool
+ */
+ protected $_migration_style = 'sequential';
/**
* Path to migration classes
@@ -73,6 +80,13 @@ class CI_Migration {
* @var bool
*/
protected $_migration_auto_latest = FALSE;
+
+ /**
+ * Migration basename regex
+ *
+ * @var bool
+ */
+ protected $_migration_regex = NULL;
/**
* Error message
@@ -125,12 +139,21 @@ class CI_Migration {
{
show_error('Migrations configuration file (migration.php) must have "migration_table" set.');
}
+
+ // Migration basename regex
+ $this->_migration_regex = $this->_migration_style === 'timestamp' ? '/^\d{14}_(\w+)$/' : '/^\d{3}_(\w+)$/';
+
+ // Make sure a valid migration numbering style was set.
+ if ( ! in_array($this->_migration_style, array('sequential', 'timestamp')))
+ {
+ show_error('An invalid migration numbering style was specified: '.$this->_migration_style);
+ }
// If the migrations table is missing, make it
if ( ! $this->db->table_exists($this->_migration_table))
{
$this->dbforge->add_field(array(
- 'version' => array('type' => 'INT', 'constraint' => 3),
+ 'version' => array('type' => 'BIGINT', 'constraint' => 3),
));
$this->dbforge->create_table($this->_migration_table, TRUE);
@@ -158,113 +181,80 @@ class CI_Migration {
*/
public function version($target_version)
{
- $start = $current_version = $this->_get_version();
- $stop = $target_version;
-
+ $current_version = (int) $this->_get_version();
+ $target_version = (int) $target_version;
+
+ $migrations = $this->find_migrations();
+
+ if ($target_version > 0 AND ! isset($migrations[$target_version]))
+ {
+ $this->_error_string = sprintf($this->lang->line('migration_not_found'), $target_version);
+ return FALSE;
+ }
+
if ($target_version > $current_version)
{
// Moving Up
- ++$start;
- ++$stop;
- $step = 1;
+ $method = 'up';
}
else
{
- // Moving Down
- $step = -1;
+ // Moving Down, apply in reverse order
+ $method = 'down';
+ krsort($migrations);
}
- $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)
+ if (empty($migrations))
{
- $f = glob(sprintf($this->_migration_path.'%03d_*.php', $i));
+ return TRUE;
+ }
+
+ $previous = FALSE;
- // Only one migration per step is permitted
- if (count($f) > 1)
+ // Validate all available migrations, and run the ones within our target range
+ foreach ($migrations as $number => $file)
+ {
+ // Check for sequence gaps
+ if ($this->_migration_style === 'sequential' AND $previous !== FALSE AND abs($number - $previous) > 1)
{
- $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $i);
+ $this->_error_string = sprintf($this->lang->line('migration_sequence_gap'), $number);
return FALSE;
}
+
+ include $file;
+ $class = 'Migration_'.ucfirst(strtolower($this->_get_migration_name(basename($file, '.php'))));
- // Migration step not found
- if (count($f) === 0)
+ // Validate the migration file structure
+ if ( ! class_exists($class))
{
- // 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_string = sprintf($this->lang->line('migration_not_found'), $i);
+ $this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
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_string = sprintf($this->lang->line('migration_multiple_version'), $match[1]);
- return FALSE;
- }
-
- include $f[0];
- $class = 'Migration_'.ucfirst($match[1]);
-
- if ( ! class_exists($class))
- {
- $this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
- return FALSE;
- }
-
- if ( ! is_callable(array($class, $method)))
- {
- $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class);
- return FALSE;
- }
-
- $migrations[] = $match[1];
- }
- else
+ elseif ( ! is_callable(array($class, $method)))
{
- $this->_error_string = sprintf($this->lang->line('migration_invalid_filename'), $file);
+ $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class);
return FALSE;
}
+
+ $previous = $number;
+
+ // Run migrations that are inside the target range
+ if (
+ ($method === 'up' AND $number > $current_version AND $number <= $target_version) OR
+ ($method === 'down' AND $number <= $current_version AND $number > $target_version)
+ ) {
+ log_message('debug', 'Migrating '.$method.' from version '.$current_version.' to version '.$number);
+ call_user_func(array(new $class, $method));
+ $current_version = $number;
+ $this->_update_version($current_version);
+ }
}
-
- log_message('debug', 'Current migration: '.$current_version);
-
- $version = $i + ($step === 1 ? -1 : 0);
-
- // If there is nothing to do so quit
- if ($migrations === array())
- {
- return TRUE;
- }
-
- log_message('debug', 'Migrating from '.$method.' to version '.$version);
-
- // Loop through the migrations
- foreach ($migrations AS $migration)
+
+ // This is necessary when moving down, since the the last migration applied
+ // will be the down() method for the next migration up from the target
+ if ($current_version <> $target_version)
{
- // Run the migration class
- $class = 'Migration_'.ucfirst(strtolower($migration));
- call_user_func(array(new $class, $method));
-
- $current_version += $step;
+ $current_version = $target_version;
$this->_update_version($current_version);
}
@@ -282,17 +272,19 @@ class CI_Migration {
*/
public function latest()
{
- if ( ! $migrations = $this->find_migrations())
+ $migrations = $this->find_migrations();
+
+ if (empty($migrations))
{
$this->_error_string = $this->lang->line('migration_none_found');
return FALSE;
}
$last_migration = basename(end($migrations));
-
+
// Calculate the last migration step from existing migration
// filenames and procceed to the standard version migration
- return $this->version((int) $last_migration);
+ return $this->version($this->_get_migration_number($last_migration));
}
// --------------------------------------------------------------------
@@ -326,22 +318,60 @@ class CI_Migration {
*
* @return array list of migration file paths sorted by version
*/
- protected function find_migrations()
+ public function find_migrations()
{
+ $migrations = array();
+
// Load all *_*.php files in the migrations path
- $files = glob($this->_migration_path.'*_*.php');
-
- for ($i = 0, $c = count($files); $i < $c; $i++)
+ foreach (glob($this->_migration_path.'*_*.php') as $file)
{
- // Mark wrongly formatted files as false for later filtering
- if ( ! preg_match('/^\d{3}_(\w+)$/', basename($files[$i], '.php')))
+ $name = basename($file, '.php');
+
+ // Filter out non-migration files
+ if (preg_match($this->_migration_regex, $name))
{
- $files[$i] = FALSE;
+ $number = $this->_get_migration_number($name);
+
+ // There cannot be duplicate migration numbers
+ if (isset($migrations[$number]))
+ {
+ $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $number);
+ show_error($this->_error_string);
+ }
+
+ $migrations[$number] = $file;
}
}
- sort($files);
- return $files;
+ ksort($migrations);
+ return $migrations;
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Extracts the migration number from a filename
+ *
+ * @return int Numeric portion of a migration filename
+ */
+ protected function _get_migration_number($migration)
+ {
+ $parts = explode('_', $migration);
+ return (int) $parts[0];
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Extracts the migration class name from a filename
+ *
+ * @return string text portion of a migration filename
+ */
+ protected function _get_migration_name($migration)
+ {
+ $parts = explode('_', $migration);
+ array_shift($parts);
+ return implode('_', $parts);
}
// --------------------------------------------------------------------
@@ -365,10 +395,10 @@ class CI_Migration {
* @param int Migration reached
* @return void Outputs a report of the migration
*/
- protected function _update_version($migrations)
+ protected function _update_version($migration)
{
return $this->db->update($this->_migration_table, array(
- 'version' => $migrations
+ 'version' => $migration
));
}
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 065daf54b..f45cc5cd7 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -227,6 +227,9 @@ Release Date: Not Released
- Added support for hashing algorithms other than SHA1 and MD5.
- Removed previously deprecated ``sha1()`` method.
- Changed :doc:`Language Library <libraries/language>` method ``load()`` to filter the language name with ``ctype_digit()``.
+ - :doc:`Migration Library <libraries/migration>` changes include:
+ - Added support for timestamp-based migrations (enabled by default)
+ - Added ``$config['migration_style']`` to allow switching between sequential migrations and timestamp migrations
- Core
diff --git a/user_guide_src/source/libraries/migration.rst b/user_guide_src/source/libraries/migration.rst
index cb7d96a6d..246396171 100644
--- a/user_guide_src/source/libraries/migration.rst
+++ b/user_guide_src/source/libraries/migration.rst
@@ -13,17 +13,40 @@ run so all you have to do is update your application files and
call **$this->migrate->current()** to work out which migrations should be run.
The current version is found in **config/migration.php**.
+********************
+Migration file names
+********************
+
+Each Migration is run in numeric order forward or backwards depending on the
+method taken. Two numbering styles are available:
+
+* **Sequential:** each migration is numbered in sequence, starting with **001**.
+ Each number must be three digits, and there must not be any gaps in the
+ sequence. (This was the numbering scheme prior to CodeIgniter 3.0.)
+* **Timestamp:** each migration is numbered using the timestamp when the migration
+ was created, in **YYYYMMDDHHIISS** format (e.g. **20121031100537**). This
+ helps prevent numbering conflicts when working in a team environment, and is
+ the preferred scheme in CodeIgniter 3.0 and later.
+
+The desired style may be selected using the **$config['migration_style']**
+setting in your **migration.php** config file.
+
+Regardless of which numbering style you choose to use, prefix your migration
+files with the migration number followed by an underscore and a descriptive
+name for the migration. For example:
+
+* **001_add_blog.php** (sequential numbering)
+* **20121031100537_add_blog.php** (timestamp numbering)
+
******************
Create a Migration
******************
-
-.. note:: Each Migration is run in numerical order forward or backwards
- depending on the method taken. Use a prefix of 3 numbers followed by an
- underscore for the filename of your migration.
This will be the first migration for a new site which has a blog. All
migrations go in the folder **application/migrations/** and have names such
-as: **001_add_blog.php**.::
+as **20121031100537_add_blog.php**.::
+
+ <?php
defined('BASEPATH') OR exit('No direct script access allowed');
@@ -47,7 +70,7 @@ as: **001_add_blog.php**.::
'null' => TRUE,
),
));
-
+ $this->dbforge->add_key('blog_id', TRUE);
$this->dbforge->create_table('blog');
}
@@ -55,6 +78,7 @@ as: **001_add_blog.php**.::
{
$this->dbforge->drop_table('blog');
}
+ }
Then in **application/config/migration.php** set **$config['migration_version'] = 1;**.
@@ -65,25 +89,25 @@ Usage Example
In this example some simple code is placed in **application/controllers/migrate.php**
to update the schema.::
- $this->load->library('migration');
-
- if ( ! $this->migration->current())
+ <?php
+
+ class Migrate extends CI_Controller
{
- show_error($this->migration->error_string());
+ public function index()
+ {
+ $this->load->library('migration');
+
+ if ($this->migration->current() === FALSE)
+ {
+ show_error($this->migration->error_string());
+ }
+ }
}
******************
Function Reference
******************
-There are five available methods for the Migration class:
-
-- $this->migration->current();
-- $this->migration->error_string();
-- $this->migration->find_migrations();
-- $this->migration->latest();
-- $this->migration->version();
-
$this->migration->current()
============================
@@ -124,14 +148,16 @@ Migration Preferences
The following is a table of all the config options for migrations.
-========================== ====================== ============= =============================================
-Preference Default Options Description
-========================== ====================== ============= =============================================
-**migration_enabled** FALSE TRUE / FALSE Enable or disable migrations.
-**migration_path** APPPATH.'migrations/' None The path to your migrations folder.
-**migration_version** 0 None The current version your database should use.
-**migration_table** migrations None The table name for storing the shema
- version number.
-**migration_auto_latest** FALSE TRUE / FALSE Enable or disable automatically
- running migrations.
-========================== ====================== ============= =============================================
+========================== ====================== ========================== =============================================
+Preference Default Options Description
+========================== ====================== ========================== =============================================
+**migration_enabled** FALSE TRUE / FALSE Enable or disable migrations.
+**migration_path** APPPATH.'migrations/' None The path to your migrations folder.
+**migration_version** 0 None The current version your database should use.
+**migration_table** migrations None The table name for storing the schema
+ version number.
+**migration_auto_latest** FALSE TRUE / FALSE Enable or disable automatically
+ running migrations.
+**migration_style** 'timestamp' 'timestamp' / 'sequential' The type of numeric identifier used to name
+ migration files.
+========================== ====================== ========================== =============================================