From f8490b994c959de3e1eabba27d8d919433e3fc70 Mon Sep 17 00:00:00 2001 From: Andrey Andreev Date: Wed, 16 Mar 2016 16:22:14 +0200 Subject: Fix #4539 --- system/libraries/Migration.php | 97 ++++++++++++++++++++++++------------- user_guide_src/source/changelog.rst | 2 + 2 files changed, 65 insertions(+), 34 deletions(-) diff --git a/system/libraries/Migration.php b/system/libraries/Migration.php index 7aefb6c23..74bec3d60 100644 --- a/system/libraries/Migration.php +++ b/system/libraries/Migration.php @@ -96,9 +96,9 @@ class CI_Migration { /** * Migration basename regex * - * @var bool + * @var string */ - protected $_migration_regex = NULL; + protected $_migration_regex; /** * Error message @@ -217,31 +217,61 @@ class CI_Migration { if ($target_version > $current_version) { - // Moving Up $method = 'up'; } else { - // Moving Down, apply in reverse order $method = 'down'; + // We need this so that migrations are applied in reverse order krsort($migrations); } - if (empty($migrations)) - { - return TRUE; - } - - $previous = FALSE; - - // Validate all available migrations, and run the ones within our target range + // 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' && $previous !== FALSE && abs($number - $previous) > 1) + if ($this->_migration_type === 'sequential') { - $this->_error_string = sprintf($this->lang->line('migration_sequence_gap'), $number); - return FALSE; + 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); @@ -253,27 +283,27 @@ class CI_Migration { $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; + } - $previous = $number; + $pending[$number] = array($class, $method); + } - // Run migrations that are inside the target range - if ( - ($method === 'up' && $number > $current_version && $number <= $target_version) OR - ($method === 'down' && $number <= $current_version && $number > $target_version) - ) - { - $instance = new $class(); - if ( ! is_callable(array($instance, $method))) - { - $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class); - return FALSE; - } + // Now just run the necessary migrations + foreach ($pending as $number => $migration) + { + log_message('debug', 'Migrating '.$method.' from version '.$current_version.' to version '.$number); - log_message('debug', 'Migrating '.$method.' from version '.$current_version.' to version '.$number); - call_user_func(array($instance, $method)); - $current_version = $number; - $this->_update_version($current_version); - } + $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 @@ -285,7 +315,6 @@ class CI_Migration { } log_message('debug', 'Finished migrating to '.$current_version); - return $current_version; } diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst index 8203aba41..b6d64fb8f 100644 --- a/user_guide_src/source/changelog.rst +++ b/user_guide_src/source/changelog.rst @@ -18,6 +18,8 @@ Bug fixes for 3.0.6 - Fixed a bug (#4516) - :doc:`Form Validation Library ` always accepted empty array inputs. - Fixed a bug where :doc:`Session Library ` allowed accessing ``$_SESSION`` values as class properties but ``isset()`` didn't work on them. - Fixed a bug where :doc:`Form Validation Library ` modified the ``$_POST`` array when the data being validated was actually provided via ``set_data()``. +- Fixed a bug (#4539) - :doc:`Migration Library ` applied migrations before validating that all migrations within the requested version range are valid. +- Fixed a bug (#4539) - :doc:`Migration Library ` triggered failures for migrations that are out of the requested version range. Version 3.0.5 ============= -- cgit v1.2.3-24-g4f1b