Database Migrations - Integration with Your Application

    This article is devoted to the practical use of the Migraton library , which appeared in the CodeIgniter version 2.1.0 update. I strongly recommend that you read the first part of the article , which refers directly to the creation of migrations, before reading this material .


    Statement of the problem and its solution


    To begin with, we agree that the application will need to be updated through the admin interface, only by login, and before that, accordingly, 503 answers will be given to requests for any of the pages.
    We do not need intermediate versions, as well as kickbacks, so the site will be updated immediately to the latest version.


    Add alert for visitors

    In the project for which I did migration integration, all controllers also inherit not the basic CI_Controller , but the extended MY_Controller , which contains some utility actions in its constructor, including with the database. Therefore, for the file % site_path% / application / core / MY_Controller.php we will need to add a few lines:
    db_update = $this->migration->get_fs_version()!=$this->migration->get_db_version();
    		if ($this->db_update) {
    			if (!$is_admin_page)
    				show_error('Пожалуйста, попробуйте перезайти через несколько минут', 503, 'Простите, на сайте в настоящее время проходят технические работы');
    			else return TRUE;
    		}
    		### Your super overpowered code goes here..
    	}
    }
    

    Note: as in the previous article, all source files are listed at the end of the article.

    I think that the code is generally clear, it’s worth noting that we will show the error everywhere except the admin page ( Admin controller that will inherit MY_Controller ). But for the admin panel, we will not execute the constructor further, because it may contain calls to a database that has an outdated structure. By the way, as you see, the parent constructor code is called first in the constructor, which implements the Decorator pattern .

    Create an update script through the admin panel

    In my case, the entire admin panel was in the same Admin controller , so for simplicity of example we will take the same case.
    This admin controller contains several diverse methods that allow you to manage the contents of the site. Not a very good and elegant solution, to be honest - I still strongly advise you to distribute it among different controllers or even use the HMVC plugin for CodeIgniter (there was already a good example of its use on the hub )

    So, we have a task to make it so that you can log in to the admin panel, and on any of the pages a suggestion was made to update the database. The magic _remap method is great for this., which will be called when accessing any of the controller's methods, and make a decision, transfer control to the requested method or show a cookie to do some other action:
    	public function _remap($method, $params = array()) {
    		if (!$this->m_user->authorised() && $method != 'index') { 
    			header('Location:/admin/'); //If user isn't autorised, redirect him to the login form
    		}
    		if (!$this->db_update || (!$this->m_user->authorised() && $method== 'index') || $method=='logout' || $method=='update_db') {
    			return call_user_func_array(array($this, $method), $params); // Calls requested method if it is ok to do so
    		}
    		else {
    			$this->data['body'] = '

    Внимание!

    Необходимо обновить базу данных'; // Show update database link $this->load->view('admin/default.phtml', $this->data); } }

    It’s worth explaining that m_user is a model that contains all the methods for working with admin users (I suggest you implement it yourself), the index method can show the login form for non-logged-in users, but logout is clear what it does.
    In addition, we will create a method for updating the database there:
    	public function update_db() {
    		$this->data['body'] = '

    Обновление базы данных успешно завершено

    '; if ( ! $this->migration->current()) { show_error($this->migration->error_string()); } $this->load->view('admin/default.phtml', $this->data); }


    Edit the config and test

    Finally, it remains to fix the config, and we can take advantage of everything that we wrote for our project. To do this, add the Migration class to startup . Now the line with auto-initializing libraries in my case looks like this (file % site_path% / application / config / autoload.php ):
    	$autoload['libraries'] = array('database', 'session', 'migration');
    

    We also need to check in the file % site_path% / application / config / migration.php whether the migrations are turned on, see if the path and version of the database required for the correct operation of our code are specified to them (everything is exactly the same as in the previous one article ):
    	$config['migration_enabled'] = TRUE;
    	$config['migration_version'] = 1;
    	$config['migration_path'] = APPPATH . 'migrations/';
    

    Please note that I indicated the 1st version, which implies that we have already done the mailing list functionality, for which we wrote the migration in the previous tutorial .

    Now you can go to any page of your site that is not related to the admin panel and you should see an error message, because we have a zero version of the database (during the first initialization, the Migration library created the migrations plate in your database and indicated version 0 there), and the version of the code in the config is the first . Going to the admin panel and logging in, you will see a proposal to update the database, and clicking on the link will receive a fresh application, without having to complete all the requests manually!

    Bonus - update script via CLI


    As a bonus, consider creating a script for updating via cli , which will allow us to automate migrations when uploading them to the site, for example, from version control systems using hooks.
    Fortunately, CodeIgniter in its latest versions has the ability to run from the command line, which is called out of the box. Therefore, to begin with, we will create the controller % site_path% / application / controllers / cli.php with the private variable $ args , which will contain all the arguments with which the script was called, except for the name of the controller and its method (we will cut them off in the constructor):
    input->is_cli_request()) {
    			show_404();
    		}
    		$this->output->enable_profiler(FALSE);
    		$this->args = array_slice($_SERVER['argv'], 3);
    	}
    	// Other code goes here..
    }
    

    By the way, I advise you to pay attention to the place in the constructor, where it is checked whether the request came through the CLI. We do not want service methods to be performed through the site! As you can see, I also turned off the profiler here, because on test servers it’s on by default everywhere on me, but in the CLI it would obviously be superfluous.

    Finally, we write a method for migrations:
    public function migration() {
    	if ( !is_array($this->args) || empty($this->args)) {
    		print ( "Usage: php index.php cli migration [OPTIONS]\n\n" );
    		print ( "Options are:\n" );
    		print ( "-l, --last\t\tupdate database to the latest version\n" );
    		print ( "-c, --current\t\t show current versions of database and code\n" );
    		exit;
    	}
    	for ( $i=0; $iargs); $i++ ) {
    		$arg = $this->args[$i];
    		if ( $arg=="-l" || $arg=="--last" ) {
    			print "Updating your database to the latest version..\n";
    			if (!$this->migration->current()) {
    				print $this->migration->error_string().'\n';
    				exit;
    			}
    			else print "Update complete!\n";
    		}
    		elseif ( $arg=="-c" || $arg=="--current" ) {
    			print 'Current code version is:\t'. $this->migration->get_fs_version().'\n';
    			print 'Current database version is:\t'.$this->migration->get_db_version().'\n';
    		}
    	}
    }
    

    To call it, you just need to type " php /%index_dir%/index.php cli migration " at the command line without additional attributes, and the script will kindly tell you the options available for use. Well, if you type " php /%index_dir%/index.php cli migration -l ", the method will try to update your database and give you the result.
    Of course, this piece of code is just an example that performs only the basics themselves, but it gives a general idea of ​​using CLI and migrations, and it will not be difficult for you to add, for example, the '-r' option to update the database before the specified revision (which by the way will be a simple homework for you).


    Conclusion


    Now we have, albeit a simple, but debugged database update mechanism integrated into our project, which, among other things, can warn users about the current update. Agree, this is better than if they saw bugs related to the inconsistency of the versions of the database and code, or some other indecency. In addition, we can now add a hook to our favorite version control system, which will automatically update the database when uploading a new version of the site.

    Download the archive with full sources.

    First part: Database migrations - an overview of the library and its use

    Also popular now: