Phinx – Миграции без привязки к фреймворку

Так получилось, что некоторую часть проекта необходимо сделать не на каком-то современном фреймворке, наподобие yii2 или laravel’а, а на самописном или вообще без участия фреймворка.

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

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

Так вот, в том же yii2, миграции идут что называется «из коробки». А что же делать в описанном в начале случае.

Для себя я нашел удобным использовать Phinx (https://phinx.org/)

Почему я выбрал Phinx

Во-первых, из-за самодостаточности — в большинстве случаев не требуется интеграции с фреймворком или уже существующим кодом.
Во-вторых, из-за достаточной простоты и, в то же время, достаточной функциональности.
И, наконец, в-третьих — неплохая документация (http://docs.phinx.org/).

Есть, конечно тут небольшой минус — если потребуется не работа со структурой таблиц, а, например, с логикой работы моделей, то придется поломать голову. Особенно если загрузка моделей или при реализации методов моделей мы завязывались на специфические элементы фреймворка/библиотеки (процесс загрузки классов моделей, использование глобальных объектов фреймворка, которые инициализируются только при правильной точке входа и т. п.)

Установка

Как я уже говорил, мне нравится для зависимостей проекта использовать композер, поэтому и установку Phinx я тоже доверил ему.

Как установить есть на сайте phinx.org

В composer.json, в раздел require добавил

"robmorgan/phinx": "^0.5.3"

потом

composer install

Затем в рабочей папке создал линк на vendor/bin/phinx и выполнил

./phinx init .

После инициализации появился файл с настройками phinx.yml.

В нем настройки для phinx — пути где будут лежать классы миграций и данных для seed (обычно используется для заполнения бд тестовыми данными)
Тут же настройки для доступа к бд для трех окружений (environments) — production, development, testing
На данном этапе интересует подключение к development, его и заполнил. Фактически environment — это настройки доступа к конкретной бд.

Первая миграция

Для создания миграции выполняется такая команда:

phinx create MigrationName

Обратите внимание на то, что для имени миграции используется CamelCase нотация. Если сделать иначе Phinx напомнит )

По умолчанию создадутся каталоги db/migrations и внутри создастся файл вида 20160721140603_migration_name.php.

В этом файле, если и дальше следовать примеру, будет содержаться класс MigrationName, который наследуется Phinx\Migration\AbstractMigration.

И будет один только метод

public function change(){}

В Phinx предусмотрены 3 метода:

  • up()
  • down()
  • change()

Собственно методы up и down — классические методы для применения и отката миграции, как это реализуется в большинстве других библиотек для миграций.

Но с какой-то там версии появились так называемые умные обратимые миграции — ввели метод change.
Т.е. метод change соединяет и up и down «в одном флаконе». И метод будет выполняться в зависимости от команды migrate или rollback.

Вызвали команду, например, rollback, а Phinx сам проанализировал, была ли ранее выполнена эта миграция и если было создание таблицы — значит таблица будет удалена. В общем, тоже тема для рассмотрения — как же там происходит анализ отката.

Команды запуска миграции и отката

Собственно раз уже затронул

Запуск миграций

phinx migrate -e development

В общем случае команда после указывает environment, а от этого зависит какую базу данных будет использовать Phinx. Если изначально в настройках ничего не менять, то там уже development выбрана как default.

Итак, вернемся к примеру. Выполнение команды migrate в таком виде заставит phinx создать в бд таблицу phinxlog, если не была создана ранее. Или взять из этой таблицы уже выполненные миграции. И выполнить невыполненные миграции из указанного каталога (где лежат файлы с классами миграций).

Можно указать дополнительную опцию -t (или --target) и выбрать первую часть имени сгенерированного файла-миграции — те. часть где зашифрована дата-время. И будет выполнены миграции вплоть до указаной.

Если посмотреть что внутри класса команды миграции, то там еще есть упоминание недокументированной опции -d (или --date) и указать не всю часть дата-время, а только дату. Но пока еще не проверял работает ли она и не глянул еще — вообще реализована эта опция или нет.

Откат миграций

Откатить миграции можно командой

phinx rollback -e development

Как и в случае с командой migrate, опцию -e можно опустить. Тут также можно указать опцию target. И Phinx попробует откатить все с последней до указанной в target.

С откатом не все так просто — есть какая-то концепция breakpoints, до которой я еще не добрался, но ее использование влияет на результаты работы rollback.

Работа со структурой таблиц

Итак, пока что самое основное для меня в проекте было — это манипуляции со структурой таблиц и бд вообще.

Phinx позволяет создавать таблицы, удалять их, менять колонки и индексы.

При создании таблицы автоматически добавляется первичный ключ в поле id.

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

Пример создания таблицы

       $tabe = $this->table( 'users_scope' );
       $tabe->addColumn( 'firstuser_id', 'integer' )
             ->addColumn( 'seconduser_id', 'integer' )
             ->addColumn( 'scope', 'string' )
             ->addColumn( 'scope_params', 'string' )
             ->addColumn( 'version', 'integer', [ 'default' => 0 ] )
             ->create();

Что тут происходит: Создается таблица, настраиваются поля: указывается имя поля и тип, возможно добавляются дополнительные параметры. Так, в примере, задается автоматическое значение для поля version. В конце вызывается метод create.

Еще один приятный момент: реализация chain — очень удобно вызывать методы для одного и того же объекта.

Вот пример добавления колонок

        $users = $this->table( 'users' );
        $users->addColumn( 'some_col', 'string', [ 'after' => 'email', 'limit' => 50 ] )
            ->addColumn( 'flag', 'boolean', ['after' => 'some_col', 'default' => false] )
            ->save();

Что тут происходит: Добавляется поле some_col, его тип будет в зависимости от адаптера, в данном случае varchar. Добавится после поля email и длина будет ограничена 50 (т. е. varchar(50)). Потом добавляется булевское поле flag после только что созданной колонки и для него дефолтным будет false.

В документации описаны методы для манипуляции со структурой, допустимые типы полей, и допустимые ключи для массива дополнительных настроек (default, after и т.д.)

Выполнение запросов

Вообще, раз уж есть коннект к бд, то есть и возможность для выполнения запросов, написанным просто SQL-ом.

$rows = $this->query('SELECT * FROM users');

Для селекта, кстати есть специальные методы (пример из документации)

// fetch a user
$row = $this->fetchRow('SELECT * FROM users');

// fetch an array of messages
$rows = $this->fetchAll('SELECT * FROM messages');

Вставка данных

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

Можно вставить как одну строку, так и несколько. Вот пример вставки нескольких строк:

	$roles = $this->table('roles');

        $curDate = (new DateTime())->format( 'Y-m-d H:i:s' );

        $roles = [
            [
                'title'        => 'Role One',
                'type'         => 'type1',
                'group'        => 'group1',
                'date_created' => $curDate,
                'date_edited'  => $curDate,
            ],
            [
                'title'        => 'Role Two',
                'type'         => 'type2',
                'group'        => 'group1',
                'date_created' => $curDate,
                'date_edited'  => $curDate
            ]
        ];

        $this->insert('roles', $roles);

Сonclusion

В общем и целом, после приобретения привычки работать с миграциями, если попадаешь на фреймворк или просто проект, в котором миграций нет «из коробки», то Phinx получается достаточно приятным решением-заменой.

Есть еще что изучить — это и breakpoints, и подробней поковырять как реализованы команды, и «пощупать» seeding (вроде как можно прикрутить fzaninotto/faker — библиотеку для генерации случайных данных для seeding’а). В связи с моим интересом к PostgreSQL посмотреть адаптер к этой бд.

Еще раз повторюсь — на мой взгляд, главным плюсом Phinx’а (это же является и минусом, но не таким жирным) является то, что нет привязки к какому-то конкретному фреймворку.

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

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

В результате после полутора месяцев использования Phinx — впечатления только положительные.
Возможно позже напишу про него побольше.

Leave a Reply

Your email address will not be published. Required fields are marked *