Миграции баз данных

В ходе разработки и ведения баз данных приложений, которые управляют данными, структуры используемых баз данных развиваются, как и исходный код приложений. Например, при разработке приложения, в будущем может оказаться необходимой новая таблица; уже после того, как приложение будет развернуто в рабочем режиме (продакшене), также может быть обнаружено, что для повышения производительности запросов должен быть создан определённый индекс; и так далее. В связи с тем, что изменение структуры базы данных часто требует изменение исходного кода, yii поддерживает так называемую возможность миграции баз данных, которая позволяет отслеживать изменения в базах данных при помощи терминов миграции баз данных, которые являются системой контроля версий вместе с исходным кодом.

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

  1. Илья создает новую миграцию (например, создается новая таблица или изменяется определение столбца и т.п.).
  2. Илья фиксирует новую миграцию в системе управления версиями (например, в Git, Mercurial).
  3. Алексей обновляет свой репозиторий из системы контроля версий и получает новую миграцию.
  4. Алексей применяет миграцию к своей локальной базе данных, тем самым синхронизируя свою базу данных, для того чтобы отразить изменения, которые сделал Илья.

А следующие шаги показывают, как развернуть новый релиз с миграциями баз данных в рабочем режиме (продакшена):

  1. Сергей создаёт новую версию проекта репозитория, которая содержит некоторые новые миграции баз данных.
  2. Сергей обновляет исходный код на рабочем сервере до новой версии.
  3. Сергей применяет любую из накопленных миграций баз данных в рабочую базу данных.

Yii предоставляет набор инструментов для миграций из командной строки, которые позволяют:

  • создавать новые миграции;
  • применять миграции;
  • отменять миграции;
  • применять миграции повторно;
  • показывать историю и статус миграций;

Все эти инструменты доступны через команду yii migrate. В этом разделе мы опишем подробно, как выполнять различные задачи, используя эти инструменты. Вы также можете сами посмотреть как использовать каждый отдельный инструмент при помощи команды yii help migrate.

Tip: Миграции могут не только изменять схему базы данных, но и приводить данные в соответствие с новой схемой, создавать иерархию RBAC или очищать кеш.

Создание миграций

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

yii migrate/create <name>

Требуемый аргумент name даёт краткое описание новой миграции. Например, если миграция о создании новой таблицы с именем news, Вы можете использовать имя create_news_table и выполнить следующую команду:

yii migrate/create create_news_table

Note: Поскольку аргумент name будет использован как часть имени класса создаваемой миграции, он должен содержать только буквы, цифры и/или символы подчеркивания.

Приведенная выше команда создаст новый PHP класс с именем файла m150101_185401_create_news_table.php в директории @app/migrations. Файл содержит следующий код, который главным образом декларирует класс миграции m150101_185401_create_news_table с следующим каркасом кода:

<?php

use yii\db\Migration;

class m150101_185401_create_news_table extends Migration
{
    public function up()
    {

    }

    public function down()
    {
        echo "m101129_185401_create_news_table cannot be reverted.\n";

        return false;
    }

    /*
    // Use safeUp/safeDown to run migration code within a transaction
    public function safeUp()
    {
    }

    public function safeDown()
    {
    }
    */
}

Каждая миграция базы данных определяется как PHP класс расширяющийся от [[yii\db\Migration]]. Имя класса миграции автоматически создается в формате m<YYMMDD_HHMMSS>_<Name> (m<ГодМесяцДень_ЧасыМинутыСекунды>_<Имя>), где

  • <YYMMDD_HHMMSS> относится к UTC дате-времени при котором команда создания миграции была выполнена.
  • <Name> это тоже самое значение аргумента name которое вы прописываете в команду.

В классе миграции, Вы должны прописать код в методе up() когда делаете изменения в структуре базы данных. Вы также можете написать код в методе down(), чтобы отменить сделанные up() изменения. Метод up вызывается для обновления базы данных с помощью данной миграции, а метод down() вызывается для отката изменений базы данных. Следующий код показывает как можно реализовать класс миграции, чтобы создать таблицу news:

<?php

use yii\db\Schema;
use yii\db\Migration;

class m150101_185401_create_news_table extends Migration
{
    public function up()
    {
        $this->createTable('news', [
            'id' => Schema::TYPE_PK,
            'title' => Schema::TYPE_STRING . ' NOT NULL',
            'content' => Schema::TYPE_TEXT,
        ]);
    }

    public function down()
    {
        $this->dropTable('news');
    }
}

Info: Не все миграции являются обратимыми. Например, если метод up() удаляет строку из таблицы, возможно что у вас уже не будет возможности вернуть эту строку методом down(). Иногда Вам может быть просто слишком лень реализовывать метод down(), в связи с тем, что это не очень распространено - откатывать миграции базы данных. В этом случае вы должны в методе down() вернуть false, чтобы указать, что миграция не является обратимой.

Базовый класс миграций [[yii\db\Migration]] предоставляет подключение к базе данных через свойство [[yii\db\Migration::db|db]]. Вы можете использовать его для манипулирования схемой базы данных используя методы описанные в работе со схемой базы данных.

Вместо использования физических типов данных, при создании таблицы или столбца, следует использовать абстрактные типы для того, чтобы ваша миграция являлась независимой от конкретной СУБД. Класс [[yii\db\Schema]] определяет набор констант для предоставления поддержки абстрактных типов. Эти константы называются в следующем формате TYPE_<Name>. Например, TYPE_PK относится к типу автоинкремента (AUTO_INCREMENT) первичного ключа; TYPE_STRING относится к строковому типу. Когда миграция применяется к конкретной базе данных, абстрактные типы будут переведены в соответствующие физические типы. В случае с MySQL, TYPE_PK будет превращено в int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, а TYPE_STRING станет varchar(255).

Вы можете добавить дополнительные ограничения при использовании абстрактных типов. В приведенном выше примере, NOT NULL добавляется к Schema::TYPE_STRING чтобы указать, что столбец не может быть NULL.

Info: Сопоставление абстрактных типов и физических типов определяется свойством [[yii\db\QueryBuilder::$typeMap|$typeMap]] в каждом конкретном QueryBuilder классе.

Начиная с версии 2.0.6, появился новый построитель схем, который является более удобным инструментом для описания структуры столбцов. Теперь, при написании миграций, можно использовать такой код:

<?php

use yii\db\Migration;

class m150101_185401_create_news_table extends Migration
{
    public function up()
    {
        $this->createTable('news', [
            'id' => $this->primaryKey(),
            'title' => $this->string()->notNull(),
            'content' => $this->text(),
        ]);
    }

    public function down()
    {
        $this->dropTable('news');
    }
}

Весь список методов описания типов столбцов доступен в API документации [[yii\db\SchemaBuilderTrait]].

Генерация миграций

Начиная с версии 2.0.7 появился удобный способ создания миграций из консоли.

В том случае, если миграция названа особым образом, таким как, например, create_xxx_table или drop_xxx_table сгенерированный файл миграции будет содержать дополнительный код.

Создание таблицы

yii migrate/create create_post_table

сгенерирует

class m150811_220037_create_post_table extends Migration
{
    public function up()
    {
        $this->createTable('post', [
            'id' => $this->primaryKey()
        ]);
    }

    public function down()
    {
        $this->dropTable('post');
    }
}

Чтобы сразу создать поля таблицы, укажите их через опцию --fields.

yii migrate/create create_post_table --fields=title:string,body:text

сгенерирует

class m150811_220037_create_post_table extends Migration
{
    public function up()
    {
        $this->createTable('post', [
            'id' => $this->primaryKey(),
            'title' => $this->string(),
            'body' => $this->text()
        ]);
    }

    public function down()
    {
        $this->dropTable('post');
    }
}

Можно указать дополнительные параметры.

yii migrate/create create_post_table --fields=title:string(12):notNull:unique,body:text

сгенерирует

class m150811_220037_create_post_table extends Migration
{
    public function up()
    {
        $this->createTable('post', [
            'id' => $this->primaryKey(),
            'title' => $this->string(12)->notNull()->unique(),
            'body' => $this->text()
        ]);
    }

    public function down()
    {
        $this->dropTable('post');
    }
}

Note: первичный ключ добавляется автоматически и по умолчанию называется id. Если вам необходимо другое имя, указать его можно через опцию --fields=name:primaryKey.

Удаление таблицы

yii migrate/create drop_post_table --fields=title:string(12):notNull:unique,body:text

сгенерирует

class m150811_220037_drop_post_table extends Migration
{
    public function up()
    {
        $this->dropTable('post');
    }

    public function down()
    {
        $this->createTable('post', [
            'id' => $this->primaryKey(),
            'title' => $this->string(12)->notNull()->unique(),
            'body' => $this->text()
        ]);
    }
}

Добавление столбца

Если имя миграции задано как add_xxx_column_to_yyy_table, файл будет содержать необходимые методы addColumn и dropColumn.

Для добавления столбца:

yii migrate/create add_position_column_to_post_table --fields=position:integer

сгенерирует

class m150811_220037_add_position_column_to_post_table extends Migration
{
    public function up()
    {
        $this->addColumn('post', 'position', $this->integer());
    }

    public function down()
    {
        $this->dropColumn('post', 'position');
    }
}

Удаление столбца

Если имя миграции задано как drop_xxx_column_from_yyy_table, файл будет содержать необходимые методы addColumn и dropColumn.

yii migrate/create drop_position_column_from_post_table --fields=position:integer

сгенерирует

class m150811_220037_drop_position_column_from_post_table extends Migration
{
    public function up()
    {
        $this->dropColumn('post', 'position');
    }

    public function down()
    {
        $this->addColumn('post', 'position', $this->integer());
    }
}

Добавление промежуточной таблицы

Если имя миграции задано как create_junction_table_for_xxx_and_yyy_tables, файл будет содержать код для создания промежуточной таблцы.

yii migrate/create create_junction_table_for_post_and_tag_tables

сгенерирует

class m150811_220037_create_junction_post_and_tag extends Migration
{
    public function up()
    {
        $this->createTable('post_tag', [
            'post_id' => $this->integer(),
            'tag_id' => $this->integer(),
            'PRIMARY KEY(post_id, tag_id)'
        ]);

        $this->createIndex('idx-post_tag-post_id', 'post_tag', 'post_id');
        $this->createIndex('idx-post_tag-tag_id', 'post_tag', 'tag_id');

        $this->addForeignKey('fk-post_tag-post_id', 'post_tag', 'post_id', 'post', 'id', 'CASCADE');
        $this->addForeignKey('fk-post_tag-tag_id', 'post_tag', 'tag_id', 'tag', 'id', 'CASCADE');
    }

    public function down()
    {
        $this->dropTable('post_tag');
    }
}

Транзакции Миграций

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

Самый простой способ реализации транзакций миграций это прописать код миграций в методы safeUp() и safeDown(). Эти два метода отличаются от методов up() и down() тем, что они неявно заключены в транзакции. В результате, если какая-либо операция в этих методах не удается, все предыдущие операции будут отменены автоматически.

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

<?php

use yii\db\Migration;

class m150101_185401_create_news_table extends Migration
{
    public function safeUp()
    {
        $this->createTable('news', [
            'id' => $this->primaryKey(),
            'title' => $this->string()->notNull(),
            'content' => $this->text(),
        ]);

        $this->insert('news', [
            'title' => 'test 1',
            'content' => 'content 1',
        ]);
    }

    public function safeDown()
    {
        $this->delete('news', ['id' => 1]);
        $this->dropTable('news');
    }
}

Обратите внимание, что обычно при выполнении нескольких операций в базе данных при помощи метода safeUp(), вы должны реализовать обратный порядок исполнения в методе safeDown(). В приведенном выше примере мы сначала создали таблицу, а затем вставили строку в safeUp(); а в safeDown() мы сначала удаляем строку и затем удаляем таблицу.

Note: Не все СУБД поддерживают транзакции. И некоторые запросы к базам данных не могут быть введены в транзакции. Для различных примеров, пожалуйста, обратитесь к негласным обязательствам. В этом случае вместо этих методов вы должны реализовать методы up() и down().

Методы доступа к базе данных

Базовый класс миграции [[yii\db\Migration]] предоставляет набор методов, которые позволяют Вам получить доступ и управлять базами данных. Вы можете найти эти методы, их названия аналогичны методам DAO, предоставленным в классе [[yii\db\Command]]. Например, метод [[yii\db\Migration::createTable()]] позволяет создать новую таблицу, подобно методу [[yii\db\Command::createTable()]].

Преимущество методов, описанных при помощи [[yii\db\Migration]] заключается в том, что Вам не нужно явно создавать экземпляр/копию [[yii\db\Command]] и исполнение каждого метода будет автоматически отображать полезные сообщения говорящие вам, что операции с базой данных выполняются и сколько они идут.

Ниже представлен список всех этих методов доступа к базам данных:

  • [[yii\db\Migration::execute()|execute()]]: выполнение SQL инструкции
  • [[yii\db\Migration::insert()|insert()]]: вставка одной строки
  • [[yii\db\Migration::batchInsert()|batchInsert()]]: вставка нескольких строк
  • [[yii\db\Migration::update()|update()]]: обновление строк
  • [[yii\db\Migration::delete()|delete()]]: удаление строк
  • [[yii\db\Migration::createTable()|createTable()]]: создание таблицы
  • [[yii\db\Migration::renameTable()|renameTable()]]: переименование таблицы
  • [[yii\db\Migration::dropTable()|dropTable()]]: удаление таблицы
  • [[yii\db\Migration::truncateTable()|truncateTable()]]: удаление всех строк в таблице
  • [[yii\db\Migration::addColumn()|addColumn()]]: добавление столбца
  • [[yii\db\Migration::renameColumn()|renameColumn()]]: переименование столбца
  • [[yii\db\Migration::dropColumn()|dropColumn()]]: удаление столбца
  • [[yii\db\Migration::alterColumn()|alterColumn()]]: изменения столбца
  • [[yii\db\Migration::addPrimaryKey()|addPrimaryKey()]]: добавление первичного ключа
  • [[yii\db\Migration::dropPrimaryKey()|dropPrimaryKey()]]: удаление первичного ключа
  • [[yii\db\Migration::addForeignKey()|addForeignKey()]]: добавление внешнего ключа
  • [[yii\db\Migration::dropForeignKey()|dropForeignKey()]]: удаление внешнего ключа
  • [[yii\db\Migration::createIndex()|createIndex()]]: создание индекса
  • [[yii\db\Migration::dropIndex()|dropIndex()]]: удаление индекса

Info: [[yii\db\Migration]] не предоставляет методы запросов к базе данных. Это потому, что обычно не требуется отображать дополнительные сообщения об извлечении данных из базы данных. Это также, потому, что можно использовать более мощный Построитель Запросов для построения и выполнения сложных запросов.

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

Применение Миграций

Для обновления базы данных до последней структуры, Вы должны применить все новые миграции, используя следующую команду:

yii migrate

Эта команда выведет список всех миграций, которые не применялись до сих пор. Если Вы подтвердите, что Вы хотите применить эти миграций, то этим самым запустите метод up() или safeUp() в каждом новом классе миграции, один за другим, в порядке их временного значения timestamp.

Для каждой миграции которая была успешно проведена, эта команда будет вставлять строку в таблицу базы данных с именем migration записав успешное проведение миграции. Это позволяет инструменту миграции выявлять какие миграции были применены, а какие - нет.

Note: Инструмент миграции автоматически создаст таблицу migration в базе данных указанной в параметре [[yii\console\controllers\MigrateController::db|db]]. По умолчанию база данных определяется как компонент приложения db.

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

yii migrate 3

Также можно явно указать конкретную миграцию, которая должна быть применена к базе данных, это можно сделать при помощи команды migrate/to в одном из следующих форматов:

yii migrate/to 150101_185401                      # используя временную метку определяющую миграцию
yii migrate/to "2015-01-01 18:54:01"              # используя строку, которая может быть получена путем использования функции strtotime()
yii migrate/to m150101_185401_create_news_table   # используя полное имя
yii migrate/to 1392853618                         # используя временную метку UNIX

Если раньше имелись какие-либо не применённые миграции, до указанной конкретной миграции, то все они будут применены до данной миграции. А если указанная миграция уже применялась ранее, то любые более поздние версии данной прикладной миграции будут отменены.

Отмена Миграций

Чтобы отменить (откатить) одну или несколько миграций, которые применялись ранее, нужно запустить следующую команду:

yii migrate/down     # отменяет самую последнюю применённую миграцию
yii migrate/down 3   # отменяет 3 последних применённых миграции

Note: Не все миграции являются обратимыми. При попытке отката таких миграций произойдёт ошибка и остановится весь процесс отката.

Перезагрузка Миграций

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

yii migrate/redo        # перезагрузить последнюю применённую миграцию
yii migrate/redo 3      # перезагрузить 3 последние применённые миграции

Note: Если миграция не является обратимой, Вы не сможете её перезагрузить.

Список Миграций

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

yii migrate/history     # показать последних 10 применённых миграций
yii migrate/history 5   # показать последних 5 применённых миграций
yii migrate/history all # показать все применённые миграции

yii migrate/new         # показать первых 10 новых миграций
yii migrate/new 5       # показать первых 5 новых миграций
yii migrate/new all     # показать все новые миграции

Изменение Истории Миграций

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

yii migrate/mark 150101_185401                      # используя временную метку определённой миграции
yii migrate/mark "2015-01-01 18:54:01"              # используя строку, которая может быть получена путем использования функции strtotime()
yii migrate/mark m150101_185401_create_news_table   # используя полное имя
yii migrate/mark 1392853618                         # используя временную метку UNIX

Эта команда изменит таблицу migration добавив или удалив определенные строки, тем самым указав, что к базе данных была применена указанная миграция. Никаких миграций не будет применяться или отменяться по этой команде.

Настройка Миграций

Есть несколько способов настроить команду миграции.

Используя параметры командной строки

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

  • interactive: логический тип - boolean (по умолчанию true). Указывает, следует ли выполнять миграцию в интерактивном режиме. Если это значение является - true, то пользователю будет выдан запрос, перед выполнением командой определенных действий. Вы можете установить это значение в false если команда используется в фоновом режиме.

  • migrationPath: строка - string (по умолчанию @app/migrations). Указывает каталог для хранения всех файлов классов миграций. Этот параметр может быть определён либо как путь до директории, либо как псевдоним пути. Обратите внимание, что данный каталог должен существовать, иначе команда будет выдавать ошибку.

  • migrationTable: строка - string (по умолчанию migration). Определяет имя таблицы в базе данных в которой хранится информация о истории миграций. Эта таблица будет автоматически создана командой миграции, если её не существует. Вы также можете создать её вручную, используя структуру version varchar(255) primary key, apply_time integer.

  • db: строка - string (по умолчанию db). Определяет ID базы данных компонента приложения. Этот параметр представляет собой базу данных, которая подвергается миграциям при помощи команды миграций.

  • templateFile: строка - string (по умолчанию @yii/views/migration.php). Указывает путь до файла шаблона, который используется для формирования скелета класса файлов миграции. Этот параметр может быть определён либо как путь до файла, либо как псевдоним пути. Файл шаблона - это PHP скрипт, в котором можно использовать предопределенную переменную с именем $className для того, чтобы получить имя класса миграции.

  • generatorTemplateFiles: массив (по умолчанию `[

      'create_table' => '@yii/views/createTableMigration.php',
      'drop_table' => '@yii/views/dropTableMigration.php',
      'add_column' => '@yii/views/addColumnMigration.php',
      'drop_column' => '@yii/views/dropColumnMigration.php',
      'create_junction' => '@yii/views/createJunctionMigration.php'
    

    ]`), в котором указаны файлы шаблонов для генерации миграций. Подробнее в разделе «Генерация миграций».

  • fields: массив конфигураций столбцов, который используется для генерации кода миграции. По умолчанию пуст. Формат каждой конфигурации ИМЯ_СТОЛБЦА:ТИП_СТОЛБЦА:ДЕКОРАТОР_СТОЛБЦА. Например, --fields=name:string(12):notNull даст нам столбец типа строка размера 12 с ограничением not null.

В следующем примере показано, как можно использовать эти параметры.

Например, если мы хотим перенести модуль forum, чьи файлы миграций расположены в каталоге migrations данного модуля, для этого мы можем использовать следующую команду:

# не интерактивная миграция модуля форума
yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0

Глобальная настройка команд

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

return [
    'controllerMap' => [
        'migrate' => [
            'class' => 'yii\console\controllers\MigrateController',
            'migrationTable' => 'backend_migration',
        ],
    ],
];

С приведённой выше конфигурацией, каждый раз при запуске команды миграции, таблица backend_migration будет использована для записи истории миграций. И Вам больше не нужно указывать её через параметр migrationTable в командной строке.

Миграции в Несколько Баз Данных

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

yii migrate --db=db2

Приведенная выше команда применит миграции к базе данных db2.

Иногда может случиться так, что Вы захотите применить некоторые из миграций к одной базе данных, а некоторые другие к другой базе данных. Для достижения этой цели, при реализации класса миграции, необходимо явно указать идентификатор ID компонента базы данных, который миграция будет использовать, следующим образом:

<?php

use yii\db\Migration;

class m150101_185401_create_news_table extends Migration
{
    public function init()
    {
        $this->db = 'db2';
        parent::init();
    }
}

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

Если у вас есть несколько миграций, которые используют ту же другую базу данных, то рекомендуется создать базовый класс миграций выше кода init(). Затем каждый класс миграции может расширяться от этого базового класса.

Совет: Кроме установки свойства [[yii\db\Migration::db|db]], Вы также можете работать с разными базами данных путем создания нового соединения с конкретной базой данных в классе Вашей миграции. Можно использовать DAO методы с этими соединениями для манипулирования различными базами данных.

Другая стратегия, которую Вы можете выбрать, чтобы перенести (мигрировать) несколько баз данных - это сохранить миграции различных баз данных в разные директории. Затем вы можете перенести эти базы данных в нужные базы следующими командами:

yii migrate --migrationPath=@app/migrations/db1 --db=db1
yii migrate --migrationPath=@app/migrations/db2 --db=db2
...

Первая команда применит миграции в директории @app/migrations/db1 к базе данных db1, а вторая команда применит миграции в директории @app/migrations/db2 к базе данных db2 и так далее.

results matching ""

    No results matching ""