/lib/Ruckusing/Util/Migrator.php
PHP | 324 lines | 209 code | 19 blank | 96 comment | 26 complexity | a1c0b1d2bef6f8bae8424493b99e84bf MD5 | raw file
- <?php
- /**
- * Ruckusing
- *
- * @category Ruckusing
- * @package Ruckusing_Util
- * @author Cody Caughlan <codycaughlan % gmail . com>
- * @link https://github.com/ruckus/ruckusing-migrations
- */
- /**
- * Implementation of Ruckusing_Util_Migrator
- *
- * @category Ruckusing
- * @package Ruckusing_Util
- * @author Cody Caughlan <codycaughlan % gmail . com>
- * @link https://github.com/ruckus/ruckusing-migrations
- */
- class Ruckusing_Util_Migrator
- {
- /**
- * adapter
- *
- * @var Ruckusing_Adapter_Base
- */
- private $_adapter = null;
- /**
- * migrations
- *
- * @var array
- */
- private $_migrations = array();
- /**
- * Creates an instance of Ruckusing_Util_Migrator
- *
- * @param Ruckusing_Adapter_Base $adapter The current adapter being used
- *
- * @return Ruckusing_Util_Migrator
- */
- public function __construct($adapter)
- {
- $this->setAdapter($adapter);
- }
- /**
- * set adapter
- *
- * @param Ruckusing_Adapter_Base $adapter the current adapter
- *
- * @return Ruckusing_Util_Migrator
- */
- public function setAdapter($adapter)
- {
- if (!($adapter instanceof Ruckusing_Adapter_Base)) {
- throw new Ruckusing_Exception(
- 'Adapter must be implement Ruckusing_Adapter_Base!',
- Ruckusing_Exception::INVALID_ADAPTER
- );
- }
- $this->_adapter = $adapter;
- return $this;
- }
- /**
- * Return the max version number from the DB, or "0" in the case of no versions available.
- * We must use strings because our date/timestamp when treated as an integer would cause overflow.
- *
- * @return string
- */
- public function get_max_version()
- {
- // We only want one row but we cannot assume that we are using MySQL and use a LIMIT statement
- // as it is not part of the SQL standard. Thus we have to select all rows and use PHP to return
- // the record we need
- $versions_nested = $this->_adapter->select_all(sprintf("SELECT version FROM %s", RUCKUSING_TS_SCHEMA_TBL_NAME));
- $versions = array();
- foreach ($versions_nested as $v) {
- $versions[] = $v['version'];
- }
- $num_versions = count($versions);
- if ($num_versions) {
- sort($versions); //sorts lowest-to-highest (ascending)
- return (string) $versions[$num_versions-1];
- } else {
- return null;
- }
- }
- /**
- * This methods calculates the actual set of migrations that should be performed, taking into account
- * the current version, the target version and the direction (up/down). When going up this method will
- * skip migrations that have not been executed, when going down this method will only include migrations
- * that have been executed.
- *
- * @param string $directory the migration dir
- * @param string $direction up/down
- * @param string $destination the version to migrate to
- * @param boolean $use_cache the current logger
- *
- * @return array
- */
- public function get_runnable_migrations($directory, $direction, $destination = null, $use_cache = true)
- {
- // cache migration lookups and early return if we've seen this requested set
- if ($use_cache == true) {
- $key = $direction . '-' . $destination;
- if (array_key_exists($key, $this->_migrations)) {
- return($this->_migrations[$key]);
- }
- }
- $runnable = array();
- $migrations = array();
- $migrations = $this->get_migration_files($directory, $direction);
- $current = $this->find_version($migrations, $this->get_max_version());
- $target = $this->find_version($migrations, $destination);
- if (is_null($target) && !is_null($destination) && $destination > 0) {
- throw new Ruckusing_Exception(
- "Could not find target version {$destination} in set of migrations.",
- Ruckusing_Exception::INVALID_TARGET_MIGRATION
- );
- }
- $start = $direction == 'up' ? 0 : array_search($current, $migrations);
- $start = $start !== false ? $start : 0;
- $finish = array_search($target, $migrations);
- $finish = $finish !== false ? $finish : (count($migrations) - 1);
- $item_length = ($finish - $start) + 1;
- $runnable = array_slice($migrations, $start, $item_length);
- //dont include first item if going down but not if going all the way to the bottom
- if ($direction == 'down' && count($runnable) > 0 && $target != null) {
- array_pop($runnable);
- }
- $executed = $this->get_executed_migrations();
- $to_execute = array();
- foreach ($runnable as $migration) {
- //Skip ones that we have already executed
- if ($direction == 'up' && in_array($migration['version'], $executed)) {
- continue;
- }
- //Skip ones that we never executed
- if ($direction == 'down' && !in_array($migration['version'], $executed)) {
- continue;
- }
- $to_execute[] = $migration;
- }
- if ($use_cache == true) {
- $this->_migrations[$key] = $to_execute;
- }
- return($to_execute);
- }
- /**
- * Generate a timestamp for the current time in UTC format
- * Returns a string like '20090122193325'
- *
- * @return string
- */
- public static function generate_timestamp()
- {
- return gmdate('YmdHis', time());
- }
- /**
- * If we are going UP then log this version as executed, if going DOWN then delete
- * this version from our set of executed migrations.
- *
- * @param object $version the version
- * @param object $direction up/down
- *
- * @return string
- */
- public function resolve_current_version($version, $direction)
- {
- if ($direction === 'up') {
- $this->_adapter->set_current_version($version);
- }
- if ($direction === 'down') {
- $this->_adapter->remove_version($version);
- }
- return $version;
- }
- /**
- * Returns an array of strings which represent version numbers that we *have* migrated
- *
- * @return array
- */
- public function get_executed_migrations()
- {
- return $this->executed_migrations();
- }
- /**
- * Return a set of migration files, according to the given direction.
- * If nested, then return a complex array with the migration parts broken up into parts
- * which make analysis much easier.
- *
- * @param string $directory the migration dir
- * @param string $direction the direction up/down
- *
- * @return array
- */
- public static function get_migration_files($directory, $direction)
- {
- $valid_files = array();
- if (!is_dir($directory)) {
- printf("\n\tMigrations directory (%s doesn't exist, attempting to create.", $directory);
- if (mkdir($directory, 0755, true) === FALSE) {
- throw new Ruckusing_Exception(
- "\n\tUnable to create migrations directory at %s, check permissions?", $directory,
- Ruckusing_Exception::INVALID_MIGRATION_DIR
- );
- } else {
- printf("\n\tCreated OK");
- }
- }
- $files = scandir($directory);
- $file_cnt = count($files);
- if ($file_cnt > 0) {
- for ($i = 0; $i < $file_cnt; $i++) {
- if (preg_match('/^(\d+)_(.*)\.php$/', $files[$i], $matches)) {
- if (count($matches) == 3) {
- $valid_files[] = $files[$i];
- }
- }
- }
- }
- sort($valid_files); //sorts in place
- if ($direction == 'down') {
- $valid_files = array_reverse($valid_files);
- }
- //user wants a nested structure
- $files = array();
- $cnt = count($valid_files);
- for ($i = 0; $i < $cnt; $i++) {
- $migration = $valid_files[$i];
- if (preg_match('/^(\d+)_(.*)\.php$/', $migration, $matches)) {
- $files[] = array(
- 'version' => $matches[1],
- 'class' => $matches[2],
- 'file' => $matches[0]
- );
- }
- }
- return $files;
- }
- //== Private methods
- /**
- * Find the specified structure (representing a migration) that matches the given version
- *
- * @param array $migrations the list of migrations
- * @param string $version the version being searched
- *
- * @return string
- */
- private function find_version($migrations, $version)
- {
- $len = count($migrations);
- for ($i = 0; $i < $len; $i++) {
- if ($migrations[$i]['version'] == $version) {
- return $migrations[$i];
- }
- }
- return null;
- }
- /**
- * Find the index of the migration in the set of migrations that match the given version
- *
- * @param array $migrations the list of migrations
- * @param string $version the version being searched
- *
- * @return integer
- */
- private function find_version_index($migrations, $version)
- {
- //edge case
- if (is_null($version)) {
- return null;
- }
- $len = count($migrations);
- for ($i = 0; $i < $len; $i++) {
- if ($migrations[$i]['version'] == $version) {
- return $i;
- }
- }
- return null;
- }
- /**
- * Query the database and return a list of migration versions that *have* been executed
- *
- * @return array
- */
- private function executed_migrations()
- {
- $query_sql = sprintf('SELECT version FROM %s', RUCKUSING_TS_SCHEMA_TBL_NAME);
- $versions = $this->_adapter->select_all($query_sql);
- $executed = array();
- foreach ($versions as $v) {
- $executed[] = $v['version'];
- }
- sort($executed);
- return $executed;
- }
- }