Pull to refresh

Миграции баз данных — интеграция с вашим приложением

Reading time6 min
Views4.7K
Данная статья посвящена практическому использованию библиотеки Migraton, появившейся в обновлении CodeIgniter версии 2.1.0. Настоятельно рекомендую вам перед ознакомлением с данным материалом прочесть первую часть статьи, в которой говорится непосредственно о создании миграций.


Постановка задачи и ее решение


Для начала договоримся, что приложение должно будет обновляться через интерфейс админки, только по логину, а до этого, соответственно, на запросы на любую из страниц будет отдавать 503 ответ.
Промежуточные версии, как и откаты нам пока не нужны, так что сайт будет обновляться сразу до последней версии.


Добавляем оповещение для посетителей

В проекте, для которого я делал интеграцию миграций, все контроллеры также наследуют не базовый CI_Controller, а расширенный MY_Controller, который содержит в своем конструкторе некие служебные действия, в том числе и с базой данных. Поэтому для файла %site_path%/application/core/MY_Controller.php нам будет необходимо добавить несколько строчек:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class MY_Controller extends CI_Controller {
	protected $db_update = FALSE; // it's TRUE if site's database  needs update, FALSE otherwise

	public function __construct() {
		parent::__construct();
		$is_admin_page = is_a($this, 'Admin');
		$this->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..
	}
}

Примечание: как и в прошлой статье, все файлы с исходным кодом приведены в конце статьи

Я думаю, что код в целом понятен, стоит лишь заметить, что ошибку мы будем показывать везде, кроме страницы админки (контроллер Admin, который унаследует MY_Controller). Но для админки не будем выполнять конструктор дальше, т.к. он может содержать в себе обращения к базе данных, которая имеет устаревшую структуру. Кстати, как вы видите, сначала в конструкторе вызывается код родительского конструктора, что реализует паттерн Decorator.

Создаем скрипт обновления через админку

В моем случае вся админка находилась в одном контроллере Admin, так что и мы для простоты примера возьмем такой же случай.
Этот контроллер админки содержит несколько разноплановых методов, которые позволяют управлять содержимым сайта. Не очень хорошее и элегантное решение, если честно — все же настоятельно советую разносить это по разным контроллерам или даже использовать HMVC-плагин для CodeIgniter (на хабре уже был неплохой пример его использования).

Итак, у нас есть задача сделать так, чтобы можно было авторизироваться в админке, и на любой из страниц выводилось предложение обновить базу данных. Для этого отлично подойдет волшебный метод _remap, который будет вызываться при обращении к любому из методов контроллера, и принимать решение, передать управление запрашиваемому методу или показать кукиш сделать какое-либо другое действие:
	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'] = '<h1>Внимание!</h1>
				Необходимо <a href="/admin/update_db">обновить базу данных</a>'; // Show update database link
			$this->load->view('admin/default.phtml', $this->data);
		}
	}

Тут стоит пояснить, что m_user — модель, которая содержит в себе все методы для работы с пользователями админки (предлагаю вам реализовать самостоятельно), метод index умеет показывать форму входа для незалогиненных пользователей, ну а logout понятно что делает.
Кроме того, создадим там же метод для обновления базы:
	public function update_db() {
		$this->data['body'] = '<h1>Обновление базы данных успешно завершено</h1>';
		if ( ! $this->migration->current()) {
			show_error($this->migration->error_string());
		}	
		$this->load->view('admin/default.phtml', $this->data);
	}


Правим конфиг и тестируем

Наконец, осталось поправить конфиг, и мы сможем воспользоваться всем тем, что написали для нашего проекта. Для этого добавим в автозагрузку класс Migration. Теперь строка с авто инициализацией библиотек в моем случае выглядит следующим образом (файл %site_path%/application/config/autoload.php):
	$autoload['libraries'] = array('database', 'session', 'migration');

Еще нам необходимо проверить в файле %site_path%/application/config/migration.php, ключены ли миграции, посмотреть правильно ли указаны к ним путь и версия базы данных, требуемой для корректной работы нашего кода (все точно так же, как и в прошлой статье):
	$config['migration_enabled'] = TRUE;
	$config['migration_version'] = 1;
	$config['migration_path'] = APPPATH . 'migrations/';

Обратите внимание, что я указал 1-ю версию, что подразумевает, что мы уже сделали функционал рассылок, миграцию для которого мы писали в предыдущем туториале.

Теперь вы можете перейти на любую страницу вашего сайта, не относящуюся к админке и должны будете увидеть сообщение об ошибке, т.к. версия базы данных у нас сейчас нулевая (при первой инициализации библиотека Migration создала в вашей базе табличку migrations и указала там версию 0), а версия кода в конфиге указана первая. Перейдя же в админку и пройдя авторизацию, вы увидите предложение обновить базу данных, и нажав на ссылку получите свежее приложение, без необходимости выполнять все запросы вручную!

Бонус — скрипт обновления через CLI


В качестве бонуса, рассмотрим создание скрипта для обновления через cli, который позволит нам автоматизировать миграции при выгрузке их на сайт, например из систем контроля версий с помощью хуков.
К счастью, CodeIgniter в своих последних версиях имеет возможность запускаться из командной строки, что называется из коробки. Поэтому для начала создадим контроллер %site_path%/application/controllers/cli.php с private переменной $args, которая будет содержать все аргументы, с которыми был вызван скрипт, кроме названия контроллера и его метода (отсечем их в конструкторе):
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Cli extends MY_Controller {
	private $args = array(); // Contains CLI arguments, except controller name and its method
	
	public function __construct() {
        parent::__construct();
		if(!$this->input->is_cli_request()) {
			show_404();
		}
		$this->output->enable_profiler(FALSE);
		$this->args = array_slice($_SERVER['argv'], 3);

	}
	// Other code goes here..
}

Кстати говоря, советую еще обратить внимание на место в конструкторе, где проверяется, пришел ли запрос через CLI. Мы же не хотим чтобы через сайт можно было выполнить служебные методы! Как видите, я еще и профайлер отключил тут, т.к. на тестовых серверах он у меня по умолчанию везде включен, а в CLI он явно был бы лишним.

Наконец, напишем метод для миграций:
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; $i<count($this->args); $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';
		}
	}
}

Для его вызова нужно всего лишь набрать в командной строке "php /%index_dir%/index.php cli migration" без дополнительных атрибутов, и скрипт вам любезно подскажет доступные для использования опции. Ну а если набрать "php /%index_dir%/index.php cli migration -l", то метод попытается обновить вашу бд, и выдаст вам результат.
Конечно, этот кусок кода всего-лишь пример, который выполняет лишь сами основы, но общее представление о использовании CLI и миграций он даёт, и вам не составит труда добавить, например, опцию '-r ' для апдейта базы до указанной ревизии (что кстати будет простеньким домашним заданием для вас).


Заключение


Теперь у нас есть хоть и простой, но отлаженный механизм обновления базы данных, интегрированный в наш проект, который кроме всего прочего умеет предупреждать пользователей о текущем обновлении. Согласитесь, это лучше, чем если бы они увидели баги, связанные с нестыковкой версий базы и кода, или еще какое-либо непотребство. Кроме того, мы теперь можем добавить в нашу любимую систему контроля версий хук, который будет при выгрузке новой версии сайта автоматически обновлять базу.

Скачать архив с полными исходниками

Первая часть: Миграции баз данных — обзор библиотеки и ее использование
Tags:
Hubs:
Total votes 20: ↑18 and ↓2+16
Comments6

Articles