CodeIgniter Forums
CI Migration Library and dynamic config values - Printable Version

+- CodeIgniter Forums (https://forum.codeigniter.com)
+-- Forum: Using CodeIgniter (https://forum.codeigniter.com/forumdisplay.php?fid=5)
+--- Forum: General Help (https://forum.codeigniter.com/forumdisplay.php?fid=24)
+--- Thread: CI Migration Library and dynamic config values (/showthread.php?tid=64639)



CI Migration Library and dynamic config values - josepostiga - 03-16-2016

Hi everyone! Yesterday, I was working on a project's migrations and I might found a little thing with CI's migrations and config classes.

I have, in config/migration.php file, defined the base directory for my migrations (APPPATH/database/migrations) but, as this project must handle different projects (multi-tenant app), I notice two situations when trying to create different migrations for each new project:

1. I use $this->config->set_item() to update the migration path dynamically, but the system doesn't seem to really update it as I run $this->migration->latest() right after and the path used is always the one defined on the config file. It's like the change is never reflected on the Migration library properties. Probably it's because the moment when the config values are loaded is on the library construct method, an I'm loading the migration library on the controller's construct method, which precedes everything else on my code.

2. On the Migration library construct method, there's a check for the existance of the migrations table but, at this point, my app's code has yet to use $this->db->db_select() method to change the database to migrate which, by default, will always check for it on the master db and that's not what I intend.

In your opinion, what's the best approach to handle this situations?

P.s: if you find any typo, I'm sorry. English is not my mother language!


RE: CI Migration Library and dynamic config values - josepostiga - 04-02-2016

Just for future reference, I've managed to handle this situations by extending the core Migration Library and making a few changes regarding the configuration getters and setters. The final class goes like this:

PHP Code:
<?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_tableTRUE);

            
$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 && ! 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($classFALSE))
            {
                
$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($methodarray_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;
    }