<?php
defined('BASEPATH') or exit('No direct script access allowed');
class MY_Migration extends CI_Migration
{
/**
* Initialize Migration Class
*
* @param array $config
* @return void
*/
public function __construct($config = array())
{
// Only run this constructor on main library load
if ( ! in_array(get_class($this), array('CI_Migration', config_item('subclass_prefix').'Migration'), TRUE))
{
return;
}
// sets initial configuration
$this->setConfig($config);
log_message('info', '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, '/').'/';
// Load migration language
$this->lang->load('migration');
// They'll probably be using dbforge
$this->load->dbforge();
// Make sure the migration table name was set.
if (empty($this->_migration_table))
{
show_error('Migrations configuration file (migration.php) must have "migration_table" set.');
}
// Migration basename regex
$this->_migration_regex = ($this->_migration_type === 'timestamp')
? '/^\d{14}_(\w+)$/'
: '/^\d{3}_(\w+)$/';
// Make sure a valid migration numbering type was set.
if ( ! in_array($this->_migration_type, array('sequential', 'timestamp')))
{
show_error('An invalid migration numbering type was specified: '.$this->_migration_type);
}
// Do we auto migrate to the latest migration?
if ($this->_migration_auto_latest === TRUE && ! $this->latest())
{
show_error($this->error_string());
}
}
/**
* Sets library configuration vars.
*
* @param array $config
* @return void
*/
public function setConfig($config = array())
{
// loads configuration file
$this->config->load('migration');
// checks configuration to override
if (count($config) > 0) {
foreach ($config as $key => $val) {
$this->{'_'.$key} = $val;
}
}
}
/**
* Checks if required migrations table exists. Creates if not.
*
* @return void
*/
protected function checkMigrationsTable()
{
if ( ! $this->db->table_exists($this->_migration_table))
{
$this->dbforge->add_field(array(
'version' => array('type' => 'BIGINT', 'constraint' => 20),
));
$this->dbforge->create_table($this->_migration_table, TRUE);
$this->db->insert($this->_migration_table, array('version' => 0));
}
}
/**
* Migrate to a schema version
*
* Calls each migration step required to get to the schema version of
* choice
*
* @param string $target_version Target schema version
* @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure
*/
public function version($target_version)
{
// checks migrations table
$this->checkMigrationsTable();
// Note: We use strings, so that timestamp versions work on 32-bit systems
$current_version = $this->_get_version();
if ($this->_migration_type === 'sequential')
{
$target_version = sprintf('%03d', $target_version);
}
else
{
$target_version = (string) $target_version;
}
$migrations = $this->find_migrations();
if ($target_version > 0 && ! isset($migrations[$target_version]))
{
$this->_error_string = sprintf($this->lang->line('migration_not_found'), $target_version);
return FALSE;
}
if ($target_version > $current_version)
{
$method = 'up';
}
elseif ($target_version < $current_version)
{
$method = 'down';
// We need this so that migrations are applied in reverse order
krsort($migrations);
}
else
{
// Well, there's nothing to migrate then ...
return TRUE;
}
// Validate all available migrations within our target range.
//
// Unfortunately, we'll have to use another loop to run them
// in order to avoid leaving the procedure in a broken state.
//
// See https://github.com/bcit-ci/CodeIgniter/issues/4539
$pending = array();
foreach ($migrations as $number => $file)
{
// Ignore versions out of our range.
//
// Because we've previously sorted the $migrations array depending on the direction,
// we can safely break the loop once we reach $target_version ...
if ($method === 'up')
{
if ($number <= $current_version)
{
continue;
}
elseif ($number > $target_version)
{
break;
}
}
else
{
if ($number > $current_version)
{
continue;
}
elseif ($number <= $target_version)
{
break;
}
}
// Check for sequence gaps
if ($this->_migration_type === 'sequential')
{
if (isset($previous) && abs($number - $previous) > 1)
{
$this->_error_string = sprintf($this->lang->line('migration_sequence_gap'), $number);
return FALSE;
}
$previous = $number;
}
include_once($file);
$class = 'Migration_'.ucfirst(strtolower($this->_get_migration_name(basename($file, '.php'))));
// Validate the migration file structure
if ( ! class_exists($class, FALSE))
{
$this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
return FALSE;
}
// method_exists() returns true for non-public methods,
// while is_callable() can't be used without instantiating.
// Only get_class_methods() satisfies both conditions.
elseif ( ! in_array($method, array_map('strtolower', get_class_methods($class))))
{
$this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class);
return FALSE;
}
$pending[$number] = array($class, $method);
}
// Now just run the necessary migrations
foreach ($pending as $number => $migration)
{
log_message('debug', 'Migrating '.$method.' from version '.$current_version.' to version '.$number);
$migration[0] = new $migration[0];
call_user_func($migration);
$current_version = $number;
$this->_update_version($current_version);
}
// 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)
{
$current_version = $target_version;
$this->_update_version($current_version);
}
log_message('debug', 'Finished migrating to '.$current_version);
return $current_version;
}
}