Данная статья посвящена практическому использованию библиотеки Migraton, появившейся в обновлении CodeIgniter версии 2.1.0. Настоятельно рекомендую вам перед ознакомлением с данным материалом прочесть первую часть статьи, в которой говорится непосредственно о создании миграций.
Для начала договоримся, что приложение должно будет обновляться через интерфейс админки, только по логину, а до этого, соответственно, на запросы на любую из страниц будет отдавать 503 ответ.
Промежуточные версии, как и откаты нам пока не нужны, так что сайт будет обновляться сразу до последней версии.
В проекте, для которого я делал интеграцию миграций, все контроллеры также наследуют не базовый CI_Controller, а расширенный MY_Controller, который содержит в своем конструкторе некие служебные действия, в том числе и с базой данных. Поэтому для файла %site_path%/application/core/MY_Controller.php нам будет необходимо добавить несколько строчек:
Примечание: как и в прошлой статье, все файлы с исходным кодом приведены в конце статьи
Я думаю, что код в целом понятен, стоит лишь заметить, что ошибку мы будем показывать везде, кроме страницы админки (контроллер Admin, который унаследует MY_Controller). Но для админки не будем выполнять конструктор дальше, т.к. он может содержать в себе обращения к базе данных, которая имеет устаревшую структуру. Кстати, как вы видите, сначала в конструкторе вызывается код родительского конструктора, что реализует паттерн Decorator.
В моем случае вся админка находилась в одном контроллере Admin, так что и мы для простоты примера возьмем такой же случай.
Этот контроллер админки содержит несколько разноплановых методов, которые позволяют управлять содержимым сайта. Не очень хорошее и элегантное решение, если честно — все же настоятельно советую разносить это по разным контроллерам или даже использовать HMVC-плагин для CodeIgniter (на хабре уже был неплохой пример его использования).
Итак, у нас есть задача сделать так, чтобы можно было авторизироваться в админке, и на любой из страниц выводилось предложение обновить базу данных. Для этого отлично подойдет волшебный метод _remap, который будет вызываться при обращении к любому из методов контроллера, и принимать решение, передать управление запрашиваемому методу илипоказать кукиш сделать какое-либо другое действие:
Тут стоит пояснить, что m_user — модель, которая содержит в себе все методы для работы с пользователями админки (предлагаю вам реализовать самостоятельно), метод index умеет показывать форму входа для незалогиненных пользователей, ну а logout понятно что делает.
Кроме того, создадим там же метод для обновления базы:
Наконец, осталось поправить конфиг, и мы сможем воспользоваться всем тем, что написали для нашего проекта. Для этого добавим в автозагрузку класс Migration. Теперь строка с авто инициализацией библиотек в моем случае выглядит следующим образом (файл %site_path%/application/config/autoload.php):
Еще нам необходимо проверить в файле %site_path%/application/config/migration.php, ключены ли миграции, посмотреть правильно ли указаны к ним путь и версия базы данных, требуемой для корректной работы нашего кода (все точно так же, как и в прошлой статье):
Обратите внимание, что я указал 1-ю версию, что подразумевает, что мы уже сделали функционал рассылок, миграцию для которого мы писали в предыдущем туториале.
Теперь вы можете перейти на любую страницу вашего сайта, не относящуюся к админке и должны будете увидеть сообщение об ошибке, т.к. версия базы данных у нас сейчас нулевая (при первой инициализации библиотека Migration создала в вашей базе табличку migrations и указала там версию 0), а версия кода в конфиге указана первая. Перейдя же в админку и пройдя авторизацию, вы увидите предложение обновить базу данных, и нажав на ссылку получите свежее приложение, без необходимости выполнять все запросы вручную!
В качестве бонуса, рассмотрим создание скрипта для обновления через cli, который позволит нам автоматизировать миграции при выгрузке их на сайт, например из систем контроля версий с помощью хуков.
К счастью, CodeIgniter в своих последних версиях имеет возможность запускаться из командной строки, что называется из коробки. Поэтому для начала создадим контроллер %site_path%/application/controllers/cli.php с private переменной $args, которая будет содержать все аргументы, с которыми был вызван скрипт, кроме названия контроллера и его метода (отсечем их в конструкторе):
Кстати говоря, советую еще обратить внимание на место в конструкторе, где проверяется, пришел ли запрос через CLI. Мы же не хотим чтобы через сайт можно было выполнить служебные методы! Как видите, я еще и профайлер отключил тут, т.к. на тестовых серверах он у меня по умолчанию везде включен, а в CLI он явно был бы лишним.
Наконец, напишем метод для миграций:
Для его вызова нужно всего лишь набрать в командной строке "php /%index_dir%/index.php cli migration" без дополнительных атрибутов, и скрипт вам любезно подскажет доступные для использования опции. Ну а если набрать "php /%index_dir%/index.php cli migration -l", то метод попытается обновить вашу бд, и выдаст вам результат.
Конечно, этот кусок кода всего-лишь пример, который выполняет лишь сами основы, но общее представление о использовании CLI и миграций он даёт, и вам не составит труда добавить, например, опцию '-r ' для апдейта базы до указанной ревизии (что кстати будет простеньким домашним заданием для вас).
Теперь у нас есть хоть и простой, но отлаженный механизм обновления базы данных, интегрированный в наш проект, который кроме всего прочего умеет предупреждать пользователей о текущем обновлении. Согласитесь, это лучше, чем если бы они увидели баги, связанные с нестыковкой версий базы и кода, или еще какое-либо непотребство. Кроме того, мы теперь можем добавить в нашу любимую систему контроля версий хук, который будет при выгрузке новой версии сайта автоматически обновлять базу.
Скачать архив с полными исходниками
Первая часть: Миграции баз данных — обзор библиотеки и ее использование
Постановка задачи и ее решение
Для начала договоримся, что приложение должно будет обновляться через интерфейс админки, только по логину, а до этого, соответственно, на запросы на любую из страниц будет отдавать 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 ' для апдейта базы до указанной ревизии (что кстати будет простеньким домашним заданием для вас).
Заключение
Теперь у нас есть хоть и простой, но отлаженный механизм обновления базы данных, интегрированный в наш проект, который кроме всего прочего умеет предупреждать пользователей о текущем обновлении. Согласитесь, это лучше, чем если бы они увидели баги, связанные с нестыковкой версий базы и кода, или еще какое-либо непотребство. Кроме того, мы теперь можем добавить в нашу любимую систему контроля версий хук, который будет при выгрузке новой версии сайта автоматически обновлять базу.
Скачать архив с полными исходниками
Первая часть: Миграции баз данных — обзор библиотеки и ее использование