- PVSM.RU - https://www.pvsm.ru -

Еще немного о миграциях. Версия для PHP

Вольно цитируя вступление к соответствующей статье на RailsGuides,

Миграции — это удобный способ управления структурой и изменениями схемы БД.

Конечно, можно вести дела по старинке, оперирую множеством SQL-файлов, или, о ужас!, редактируя куски SQL-кода в одном большом файле, который представляет собой актуальную схему БД.

Однако следить за этими изменениями, начиная с некоторого момента, становится очень сложно, не говоря уже о применении соответствующих изменений на продакшен-машине: тут нужно обладать ловкостью гепарда, силой медведя и мудростью всех восточных мудрецов, вместе взятых, чтобы все сделать правильно и ничего не уронить.
Но как быть, если Вы не обладаете какими-либо из вышеперечисленных качеств? Правильно, нужно систематизировать и автоматизировать процесс, переложив большую часть работы на машину.

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

Начав работать с Ruby on Rails довольно быстро знакомишься с механизмом миграций, а уже через некоторое время не понимаешь, как можно вообще было обходиться без этого невероятно удобного инструмента.
Придя в проект, который разрабатывался на PHP, я постарался привнести в него хотя бы минимальный набор полезных инструментов, знакомых по опыту общения с Ruby on Rails. Одним из пунктов была система для поддержки миграций.
После некоторого поиска выбор был сделан в пользу Ruckusing Migrations [1], как наиболее похожего на то, что я видел в рельсах. Рапортую, что по прошествии более полугода полет нормальный!

Установка

Предполагается, что Вы используете Composer для управления зависимостями. Если нет, обязательно попробуйте, оно того стоит!
Также при желании Вы можете клонировать с GitHub репозиторий с примером: github.com/ArtemPyanykh/php_migrations_example [2]

Для начала добавьте в свой composer.json:

"require" : {
    "ruckusing/ruckusing-migrations": "dev-master"
}

и выполните

~/dev/php_migrations_example(master)$ php composer.phar install

Composer подтянет Ruckusing Migrations и установит в директорию vendors.

Далее, я советую поступить следующим образом:

  • Во-первых, создайте следующую структуру директорий:
    .
    └── db
        ├── logs
        ├── migrations
        │   ├── main
        │   ├── php_migrations_example -> main/
        │   └── php_migrations_example_test -> main/
        └── utility
    

    В директории db будут храниться Ваши миграции, конфиги и другие полезные штуки. Считайте этот каталог точкой отсчета для всего, что связано с миграциями.
    Сами файлы с миграциями будут храниться в db/migrations/main.
    php_migrations_example и php_migrations_example_test — это названия девелоперской и тестовой баз данных.
    Каталоги с соответствующими именами это просто символические ссылки на директорию main, так как вряд ли у Вас будут разные миграции для разных окружений.

  • Создайте файл db/ruckus со следующим содержимым:
    #!/bin/bash
    ruckus_dir="./../vendor/ruckusing/ruckusing-migrations"
    if [ ! -d $ruckus_dir ]; then
        echo "Ruckusing-Migrations wasn't detected. Please, use Composer to install the library."
        exit
    fi
    if [ ! -f "ruckusing.conf.php" ]; then
        echo "Ruckusing conf. file wasn't detected. Please, create proper ruckusing.conf.php."
        exit
    fi
    if [ "$#" -lt 1 ]; then
    	echo "At least 1 argument required"
    	echo "See ./ruckus --help"
    	exit
    fi
    if [ "$1" == "--help" -o "$1" == "-h" ]; then
    	echo "Usage: ./ruckus [--help] <task-name> [<parameter1>[ <parameter2> [...]]]"
    	echo ""
    	echo "The available ruckus commands are:"
    	echo "    ./ruckus db:migrate                     Ruckus scenario for tasks such as db:migrate, db:setup, etc."
    	echo "    ./ruckus db:generate <migration_name>   Ruckus scenarion for generating migration scaffolding"
    	echo ""
    	exit
    fi
    php $ruckus_dir"/ruckus.php" "$1" "${@:2}"
    

    Это просто небольшой скрипт, который я позволил себе написать, дабы упростить работу с Ruckusing Migrations.

  • Наконец, создайте конфиг-файл db/ruckusing.conf.php:
    <?php
    //----------------------------
    // DATABASE CONFIGURATION
    //----------------------------
    /*
    Valid types (adapters) are Postgres & MySQL:
    'type' must be one of: 'pgsql' or 'mysql'
    */
    return array(
        'db' => array(
            'development' => array(
                'type'      => 'mysql',
                'host'      => 'localhost',
                'port'      => 3306,
                'database'  => 'php_migrations_example',
                'user'      => 'root',
                'password'  => 'root'
            ),
            'test'  => array(
                'type'  => 'mysql',
                'host'  => 'localhost',
                'port'  => 3306,
                'database'  => 'php_migrations_example_test',
                'user'  => 'root',
                'password'  => 'root'
            )
        ),
        'migrations_dir' => RUCKUSING_WORKING_BASE . '/migrations',
        'db_dir' => RUCKUSING_WORKING_BASE . '/utility',
        'log_dir' => RUCKUSING_WORKING_BASE . '/logs'
    );
    ?>
    

Все, больше никаких настроек не требуется, Вы успешно интегрировали себе систему миграций!

Использование

В целом все очень просто. Давайте начнем с того, что сгенерируем миграцию:

~/dev/php_migrations_example(master)$ cd db
~/dev/php_migrations_example/db(master)$ ./ruckus db:generate CreateTestTable
	Created migration: 20130508145210_CreateTestTable.php

Вы можете заметить, что в каталоге db/migrations/main после этого добавится файл примерно с таким же названием (timestamp будет другой) следующего содержания:

<?php

class CreateTestTable extends Ruckusing_Migration_Base
{
    public function up()
    {
    }//up()

    public function down()
    {
    }//down()
}

Миграции обладают тем свойством, что могут быть не только применены, но и отменены, если существует адекватный способ отката изменений. Именно такова семантика методов up() (применение изменений) и down() (откат изменений). Давайте создадим таблицу test с несколькими полями и парой индексов. Дополним файл следующим образом:

<?php

class CreateTestTable extends Ruckusing_Migration_Base
{
    public function up()
    {
        $table = $this->create_table('test', array('options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'));

        $table->column('this', 'integer', array('unsigned' => true, 'null' => false, 'default' => '42'));
        $table->column('that', 'string', array('limit' => '7'));
        $table->column('those', 'datetime');
        $table->finish();

        $this->add_index('test', array('this', 'that'), array('unique' => true));
    }//up()

    public function down()
    {
        $this->drop_table('test');
    }//down()
}

и запустим миграции:

~/dev/php_migrations_example/db(master)$ ./ruckus db:migrate
Started: 2013-05-08 7:05pm MSK
[db:migrate]: 
	Schema version table does not exist. Auto-creating.
	Creating schema version table: schema_migrations
	Migrating UP:
========= CreateTestTable ======== (0.31)
Finished: 2013-05-08 7:05pm MSK

Вы заметите, что помимо создания таблицы test, которая полностью соответствует описанной выше спецификации, также создалась таблица schema_migrations. Это нормально — именно здесь Ruckusing Migrations хранит информацию о том, какие миграции были применены, а какие не были.

Также просто можно откатиться или запустить миграции для другого окружения:

~/dev/php_migrations_example/db(master)$ ./ruckus db:migrate VERSION=-1
Started: 2013-05-08 7:13pm MSK
[db:migrate]: 
	Migrating DOWN:
========= CreateTestTable ======== (0.21)
Finished: 2013-05-08 7:13pm MSK
~/dev/php_migrations_example/db(master)$ ./ruckus db:migrate ENV=test
Started: 2013-05-08 7:14pm MSK
[db:migrate]: 
	Schema version table does not exist. Auto-creating.
	Creating schema version table: schema_migrations
	Migrating UP:
========= CreateTestTable ======== (0.24)
Finished: 2013-05-08 7:14pm MSK

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

Замечания

  1. Если вы попробуете запустить скрипт ruckus не из директории db/, то получите ошибку. Это связано с тем, что все пути в скрипте относительные, и при желании это легко исправляется. Нужно, однако, учесть один момент: по умолчанию, конфиг ищется в рабочей директории.
  2. При применении миграций на продакшене нужно быть очень осторожным: если у Вас есть достаточно увесистая таблица, скажем в несколько гигабайт, и Вы примените к ней миграцию, которая каким-либо образом меняет схему, скорее всего будет беда. Хотя это, возможно, и не недостаток миграций как таковых, а скорее недостаток СУБД, тем не менее это несколько ограничивает возможности применения системы. Для обновления больших таблиц нужно использовать специализированные инструменты, например Percona Toolkit [3].

Ссылки

Автор: Deshene

Источник [5]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/php-2/33929

Ссылки в тексте:

[1] Ruckusing Migrations: https://github.com/ruckus/ruckusing-migrations

[2] github.com/ArtemPyanykh/php_migrations_example: https://github.com/ArtemPyanykh/php_migrations_example

[3] Percona Toolkit: http://www.percona.com/software/percona-toolkit

[4] getcomposer.org/: http://getcomposer.org/

[5] Источник: http://habrahabr.ru/post/179155/