Миграции баз данных — обзор библиотеки и ее использование

Как вы уже могли прочесть, недавно вышла новая версия CodeIgniter, одним из нововведений которого является библиотека Migration. Один из главных ее разработчиков, Phil Sturgeon был настолько воодушевлен удобством управления версиями баз данных для Rails, что решил создать аналог такого метода для CodeIgniter, и вот, в конце-концов вы можете видеть эту библиотеку в официальной поставке.
Из этой статьи вы получите общее представление о миграциях, а также научитесь их создавать. Во второй же части, мы с вами увидим, как легко они могут быть интегрированы в ваше приложение.
Данная статья будет полезна начинающим пользователям CodeIgniter, но я надеюсь что и более продвинутые коллеги узнают об этой чудесной библиотеке и подчерпнут для себя что-нибудь новое.

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


Обзор библиотеки


Библиотека Migration позволяет нам хранить в приложении все изменения базы данных, и накатывать или откатывать их при необходимости. Это может быть удобно и для разработки в команде — не надо объяснять вашему коллеге, что надо изменить в базе, чтобы ваш код заработал, так и при выгрузке новых версий сайта на боевой сервер. Ну или, на худой конец, можно всегда быстро откатиться до старой версии, если вы где-то напортачили.

Чтобы понять в общих чертах, как работает библиотека рассмотрим следующий примитивный сценарий:
0. Допустим, у нас есть 0 версия приложения с начальной базой (также версии 0).
1. В ходе разработки мы понимаем, что для реализации нового функционала (например системы подписки на новости) нам необходимо будет внести изменения не только в код приложения, но и в структуру базы данных.
2. Для этого мы в конфиге укажем, что версия базы данных для этой версии кода будет 1 и вместе с новым кодом создадим миграцию 001, указав для нее действия как для апгрейда, так и для даунгрейда базы (на случай если нужно будет откатить версию приложения).
3. Затем мы реализуем какой-либо механизм накатывания обновлений — это может быть и страничка в админке, и решение, использующее CLI, или даже полностью автоматическое обновление при выгрузке из системы контроля версий.
4. Выгружаем новый код проекта на сайт, запускаем миграцию до нужной версии, и вуаля!

А теперь представьте, что вам нужно обновить приложение через 3 версии вперед, или же на тестовом сервере посмотреть как была реализована версия, сделанная пару месяцев назад? Легко, просто изменив код, мигрируйте до нужной версии базы (миграция произойдет постепенно, допустим с 4-й до 1-й сначала накатятся 3-я и 2-я, а затем 1-я версии)!

Вы воодушевились такой возможностью как и я и хотите попробовать? Нет ничего проще!

Практическое использование


Собственно говоря, библиотека совершенно проста в использовании, и для ее внедрения в проект требуется добавить\поправить совсем немного строчек кода.
Весь процесс создания миграций не займет десяток минут времени, а сэкономит вам и вашим коллегам, работающим над этим же проектом прилично.

Создаем класс MY_Migration

Итак, для начала нам будет нужно немного допилить напильником расширить класс Migration.
Зачем? Все очень просто, по умолчанию этот класс не умеет сообщать о том, какие версии базы и кода являются текущими, а реализовывать эти проверки по всему коду не совсем правильно, лучше это сделать в одном месте.
Для этого воспользуемся стандартной методикой расширения классов, используемой в CodeIgniter — создадим файл %site_path%/application/libraries/MY_Migration.php со парой десятков строк кода внутри.
Этот класс добавит к реализации предка еще 2 публичных метода:
	/**
	 * Wrapper function for the protected _get_version.
	 * Get's the database current version
	 *
	 * @access	public
	 * @return	integer	Current DB Migration version
	 */
	public function get_db_version() {	
		return parent::_get_version();
	}
	
	/**
	 * Retrieves current file system version
	 *
	 * @access	public
	 * @return	integer	Current file system Migration version
	 */
	public function get_fs_version() {
		return $this->_migration_version;
	}

Примечание: здесь и далее могут быть приведены сокращенные листинги кода, ссылка на все файлы, использованные в туториале в конце статьи

Как видите, функции являются лишь обертками для protected метода для получения версии базы данных и опять же protected свойства $_migration_version, и вполне наглядно демонстрируют применение инкапсуляции.
Также советую обратить внимание на важный момент: в оригинальном классе Migration стоит ограничение, из-за которого его конструктор выполняется только при инициализации родительского класса.
Поэтому чтобы приложение работало правильно, автоматически инициализируя MY_Migration вместо оригинала, необходимо продублировать конструктор родительского класса, изменив в нем условие на 41 строке:
	public function __construct($config = array()) { 
		# Only run this constructor on main library load
		if (get_parent_class($this) !== FALSE)
		{  
			return;
		}
	###	   Other code here...
	}

на
	public function __construct($config = array()) { 
		# Only run this constructor on main library load
		if (get_parent_class($this) !== FALSE && get_class($this)!='MY_Migration')
		{  
			return;
		}
	###	  Other code here...
	}


Создаем миграцию

Расширив базовый класс, дело остается за малым. Для того, чтобы библиотека могла накатывать или откатывать изменения на базу данных, необходимо создать миграционный файл, содержащий в себе класс-потомок Migration(в нашем случае MY_Migration).
Так как у нас это первая миграция, то создадим следующий файл: %site_path%/application/migrations/001_add_messages.php, содержащий в себе фунции up и down:

<?php defined('BASEPATH') OR exit('No direct script access allowed');
class Migration_Add_messages extends MY_Migration {
	public function up() {
		$this->db->query("
			CREATE TABLE IF NOT EXISTS `email_list` (
				`list_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
				`email` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
			) ENGINE=MyISAM;");
	}
	public function down() {
		$this->db->query("DROP TABLE IF EXISTS `email_list`");
	}
}

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

Правим конфиг

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

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


Итог


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

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

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

PS: Не хватает кармы для публикации в блог CodeIgniter, так что пока публикую обще-php-шный блог
UPD: Добрые анонимы налили немного кармы, перенес в блог CodeIgniter
+36
28 ноября 2011, 15:16
67

комментарии (22)

+4
delmot #
Великолепно! Не хватает только запуска миграций из командной строки, что было бы полезно при автоматизации деплоя
+4
TrueDrago #
Сегодня-завтра планировал дописать 2-ю статью про интеграцию с приложением, правда там пример пока-что делал с обновлением из админки.
В принципе, могу накидать примерчик и с обновлением через CLI, если это интересно.
0
TrueDrago #
Во второй статье про это написал: habrahabr.ru/blogs/codeigniter/133395/
–5
hazg #
проснулся и думаю — а может карму себе убить

… и чего эти русские не придумают, лишь бы дороги не строить (руби учить)
+1
TrueDrago #
К.О. ответил бы, что программист, работающий на php, выучив руби, не всегда смог бы отказаться от поддержки оставшихся на php проектов.
–2
hazg #
Из ответа К.О. получается, что CodeIgniter написан для рубистов, которые поддерживают старые проекты на PHP?
–1
hazg #
Нихрена не понимаю. За что _тут_ минус, почему без о ответа (и так все понятно?)

Люди — они такие люди ))))

P.S. Жаль Капитана Очевидность, его авторитет сейчас явно спаразитирован.
+5
aleks_raiden #
да и почему это вообще другие языки существуют, а не только руби :)
0
hazg #
да и почему это вообще другие языки существуют, а не только php :)
0
VolCh #
Бложик на рельсах я напишу немногим медленнее бложика на симфони, а вот с его деплоем и администрированием в продакшене проблем будет больше. Можно, конечно, добавить, что надо еще баш выучить, научиться пакеты собирать и т. п., но мне это мало интересно. Прототип на рельсах, потом релиз на пхп :)
+1
hazg #
>> а вот с его деплоем и администрированием в продакшене проблем будет больше
Почему?

>> Можно, конечно, добавить, что надо еще баш выучить, научиться пакеты собирать и т. п.
учить баш, собирать «пакеты», для «бложика» зачем?

>> Прототип на рельсах, потом релиз на пхп :)
realy?
0
VolCh #
Я имею в виду, что настроить и администрировать сервер под рельсы сложнее, чем для пхп. Особенно, если хочется, чтобы не нарушалась идеология привычного дистрибутива, чтобы, например, gem'ы управлялись штатным менеджером, а не рубивским. Для пхп тоже заморочек хватает, чтобы пакеты из PEAR или PECL в, например, deb перевести, но и требуется это реже и как-то субъективно проще.

Реально, прототипировать на рельсах проще: и локально разрабатывать, и показать кому-нибудь, выгрузив на хероку, да и гемов много для типовых вещей. А когда общая концепция приложения сложилась, основные детали утверждены, то на пхп лично мне писать и поддерживать проще, да и выгоднее, чем разбираться как гемы кастомизировать. Никто не хочет, почему-то, чтоб я просто развил прототип за те же деньги, узнав сколько стоит хостинг на том же хероку или услуги админа для поддержки рельс, или другого разработчика для развития приложения. Хотят чтоб я вдску сам админил, а я такие риски брать на себя не хочу (хотя беру часто) даже с пхп, не говоря про рельсы — тут точно не возьму, некомпетентен.
0
ajaxtelamonid #
А чем можно сделать diff изменений БД? Например, у меня есть база в продакшене (old), к которой можно подключиться по сети и на локалхосте, где я разрабатываю (new). Можно ли сделать автоматическую генерацию up() и down()? Должны же быть алгоритмы.

Вопрос не применительно к CI, хотелось бы услышать ответ от людей, кто активно юзает миграции в своих проектах. Потому что гугление показывает, что такого инструмента, похоже, не существует.
0
lair #
Ну, например в EF CodeFirst CodeMigrations вполне себе есть автогенерация миграций на основании разницы между моделью в коде и моделью в БД.
0
TrueDrago #
Я по работе искал немного, но так уж вышло, что быстрее было вручную в моем случае написать.
Тем неменее может вам эти ссылки помогут (правда там под mysql вроде всё): www.mysqldiff.org/ и stackoverflow.com/questions/218499/mysql-diff-tool.
0
ajaxtelamonid #
да, mysqldiff.org я нагуглил первым делом, но на первый взгляд для нормальной работы там было применять не просто напильник, а бензопилу.
Посмотрю остальные ссылки, спасибо.
0
Nc_Soft #
Имхо, даунгрейд полная панацея, только бэкапом можно откатиться корректно, если в базе есть данные.
+1
TrueDrago #
Ну бэкапы никто и не отменял. Хотя без вреда новым данным бэкап скорее всего не восстановишь.
+1
mustangostang #
посмотрите еще на code.google.com/p/mygrate/
0
TrueDrago #
Интересная вещь, спасибо.
А оно само генерит диффы получается при коммитах?
0
mustangostang #
да. и потом еще можно файл миграции поправить ручками, если что не так (там, ренейм поля в таблице вместо добавления/удаления или сразу первоначальный попьюлейшн данных).
+1
tenshi #
Как видите, функции являются лишь обертками для protected метода для получения версии базы данных и опять же protected свойства $_migration_version, и вполне наглядно демонстрируют применение инкапсуляции.

Паблик Морозов: Класс-потомок, созданный в соответствии с этим антипаттерном, выдает по запросу все данные класса-предка, независимо от степени их сокрытия. Название данного анти-паттерна — это каламбур, основанный на созвучии ключевого слова public (паблик), часто означающего открытый доступ к методам и полям класса в объектно-ориентированных языках программирования, и имени пионера-героя Павлика Морозова, известного тем, что он выдал своего отца-кулака.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.