Миграция базы данных в Zend Framework: Akrabat_Db_Schema_Manager

В процессе работы над одним огромным проектом на Zend Framework, возникла необходимость миграции баз данных и перемещение между версиями, т.е. кроме update, был необходим так называемый downdate. Немного погуглив натолкнулся на интересную статью Роба Алана (в дальнейшем Автор) «Akrabat_Db_Schema_Manager: Zend Framework database migrations». Данная статья не является переводом оригинала, а скорее синтезом его и возникшей проблемы. Об этом и пойдет разговор.

После каждого изменения в базе данных (добавление столбцов, таблиц, индексов, ключей и т.д.), нужно создавать отдельный файл миграции. Подобные файлы можно размещать в каталоге ./scripts/migrations. Автор предлагает именовать файл миграции как 001-CreateUsersTable.php. Номер определяет порядок, в котором данные файлы будут запускаться, т.е. по сути идет [порядковый номер]-[краткое описание миграции].php. Но в этом не так много информативности. По этому мы решили именовать миграцию как [номер-ревизии]-[краткое описание миграции].php. Для тех кто пользуется hg могут использовать порядковый номер changeset. В связи с этим пришлось немного переписать класс Akrabat_Db_Schema_Manager, т.к. в одну ревизию могло входить несколько миграций и имена файлов переопределялись. Метод _getMigrationFiles() возвращает не массив вида:
$files["v$versionNumber"] = array(
                        'path'=>$path,
                        'filename'=>$entry,
                        'version'=>$versionNumber,
                        'classname'=>$className);
А массив массивов:
$files["v$versionNumber"][] = array(
                        'path'=>$path,
                        'filename'=>$entry,
                        'version'=>$versionNumber,
                        'classname'=>$className);

Соответственно изменения коснулись и метода _processFile($migration, $direction), в который передавался параметр $migration не как массив, а массив массивов. Кому интересно, исходные коды классов выложу на GitHub, а пока расскажу про основной алгоритм модернизации.

Файл миграции содержит класс наследуемый от Akrabat_Db_Schema_AbstractChange и содержащий два метода: up() и down(). Метод up() запускает процесс обновления БД, а down() — в случае отката ревизии. Вот немного измененный пример, который приводит Автор:
class CreateUsersTable extends Akrabat_Db_Schema_AbstractChange 
{
    function up()
    {
$tableName = $this->_tablePrefix . 'users';
$sql = "CREATE TABLE IF NOT EXISTS $tableName (
                  id int(11) NOT NULL AUTO_INCREMENT,
                  username varchar(50) NOT NULL,
                  password varchar(75) NOT NULL,
                  roles varchar(200) NOT NULL DEFAULT 'user',
                  PRIMARY KEY (id)
                )";
        $this->_db->query($sql);
        $data = array();
        $data['username'] = 'admin';
        $data['password'] = sha1('password');
        $data['roles'] = 'user,admin';
        $this->_db->insert('users', $data);
    }

  function down()
    {
$tableName = $this->_tablePrefix . 'users';
        	$sql = "DROP TABLE IF EXISTS $tableName";
        	$this->_db->query($sql);
    }
}

В примере введена поддержка префикса таблиц, который указывается в конфигурационном файле, в ключе resources.db.table_prefix. Но опять есть 1 минус, имя класса совпадает с [кратким описанием миграции], и если у нас будет хотя бы 2 одинаковых описания, то вылетит Fatal error: Cannot redeclare class. Для этого определим индекс массива 'classname' как
'classname'  => $className.$versionNumber

и аналогично именуем класс — это позволит избежать нам ошибки.
А теперь — самое вкусное. Прикручиваем всю эту красоту к zend Tool. Как установить Zend Tool я рассказывать не буду, об этом уже много писали. Для начала определимся где хранить Akrabat ZF Library, я предпочитаю все PHP библиотеки хранить в /usr/share/php/, это значит что путь к ней будет /usr/share/php/Akrabat. Далее все просто:
$ zf --setup storage-directory
$ zf --setup config-file
$ echo "`php -r 'echo get_include_path().PATH_SEPARATOR;'`/usr/share/php/Akrabat">~/.zf.ini
$ echo ‘basicloader.classes.0 = "Akrabat_Tool_DatabaseSchemaProvider"’>>~/.zf.ini

Теперь убедимся что все работает:
$ zf ? database-schema

Должны увидеть что-то похожее:
Zend Framework Command Line Console Tool v1.11.11
Actions supported by provider "DatabaseSchema"
  DatabaseSchema
    zf update database-schema env[=development] dir[=./scripts/migrations]
    zf update-to database-schema version env[=development] dir[=./scripts/migrations]
    zf decrement database-schema versions[=1] env[=development] dir[=./scripts/migrations]
    zf increment database-schema versions[=1] env[=development] dir[=./scripts/migrations]
    zf current database-schema env[=development] dir[=./migrations]
    zf get-table-prefix database-schema

Источники

  1. Статья Автора
  2. Akrabat ZF Library на GitHub.com
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 8
  • +1
    Надо поковырять, миграции — вещь в хозяйстве нужная

    Может кого заинтересуют и наши миграции из ZFCore — code.google.com/p/zfcore/wiki/Core_Migration
    • 0
      Да, впечатляет. Я бы посоветовал использовать .zfproject.xml для получения информации о проекте. Раздел
      <applicationDirectory>
      ...
          <configsDirectory>
              <applicationConfigFile type="ini"/>
          </configsDirectory>
      ...
      </applicationDirectory>
      
      содержит исчерпывающую информацию и обязательные параметры при инстанцировании класса Core_Migration_Manager можно опустить, получив их из конфига директории приложения, при этом директория приложения по дефолту application, но изменяется, если я не ошибаюсь в атрибуте filesystemName.
    • 0
      Подскажите, как решать проблему с одинаковыми индексами миграции на разных ветках в DCVS? Т.е. например, есть 20 файлов-миграций и два разработчика между которыми всё засинхронизированно. Оба начинают разрабатывать новый ф-ционал на разных ветках и каждый создаёт 21-ый файл миграции. После слияния получается две 21-ых миграции (21.a + 21.b) и ещё пачка миграций: 22, 23, 24,…
      Переименовывать 21.b => 22 проблематично:
      1. Придётся переименовывать все последующие миграции.
      2. У одного из участников сбивается нумерация: он уже накатывал свою миграцию 21.b, но 22 ещё не накатывал. При накате 22-ой, он ещё раз выполняет свою миграцию, но другую миграцию 21.a он не накатит.

      Есть ли удобный способ решения данной проблемы, для максимальной автоматизации без ручного труда?

      Я пока придумал способ (но ещё не реализовал), что бы для каждого участника хранить набор миграций в отдельной поддиректории ./scripts/migrations/user2/ и соответственно хранить статус наката для каждой директории. При апдейте и даунгрейде указывать с какой папкой работаем:
      update -v=22 -u=user2
      

      Получается, что-то типа бранчей внутри системы миграции.
      А может есть способ лучше?
      • 0
        В ROR миграции маркеруются таймстемпом. В специальной табличке хранятся таймстемпы всех миграций, которые были применены.
        • –1
          Ну всё!
          Срочно переписываем все проекты с ZF на ROR! увольняем весь штат php-прогеров и пол года ищем новую команду рубистов!
          • –1
            Было бы здорово. Но для начала можете просто повторить решения великого RoR.
            • 0
              Да ну что вы! Переписывать RoR на php — это же костыль! Лучше уж тотальный рефакторинг.

              Хотя ниже уже подсказали изящное решение на php. Рефакторинг и переписывание отменяются.
        • 0
          К примеру, есть файлы:
          FromDeveloper1/22-someAction.php
          FromDeveloper2/22-someAction.php
          Необходимо данные привести к виду:
          $files=array(
              '22' => array(
                  'FromDeveloper1/22-someAction.php',
                  'FromDeveloper2/22-someAction.php'
              )
          );
          

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

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