Так уж сложилось, что в 1С-Битрикс нет никакой штатной возможности по работе с модификацией структуры БД, кроме как делать это ручками в админке.

На конференциях разработчиков, у сотрудников Битрикса постоянно просят добавить в продукт механизм миграций БД, однако нам постоянно отвечают «завтраками», что может быть, когда-нибудь мы их сделаем.

Ну а работа не стоит на месте, потребность вносить изменения в БД в автоматическом режиме с соблюдением версионности никуда не делась, и даже наоборот - зреет с каждым днем.

Надоело! Прикручиваем миграции к Битриксу сами

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

worksolutions/bitrix-module-migrations

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

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

Но для меня этот модуль не подошел. Частично из-за тех же плюсов, которые с другой стороны являются минусами. Да, исходники доступны на гитхабе, но они в windows-1251 (очевидно, для удобства публикации модуля в marketplace). Содержать свой форк в utf-8 будет накладно.
Сами создатели модуля обязывают устанавливать модуль из маркетплейса, т.к. при установке и обновлении производятся определенные операции изменения БД.
Ну и все описанное выше делает невозможным подключение модуля через composer, а также делает невозможным его обновление при отсутствующей лицензии.
В дополнение к этому, я очень опасаюсь, что модуль миграций каким-то образом может заинтересовать администратора сайта, и он может случайно, или намеренно применить или откатить ряд миграций, что может сказаться на функциональности сайта.
Workflow работы с миграциями меня тоже не очень порадовал: версия БД почему-то хранится в файле. А еще хеши, за которыми иногда придется следить.

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

Phinx

Чудесная библиотека для создания миграций. Используется во многих проектах, но не имеет никакой нативной интеграции с битриксом (оно и понятно). Обладает всем необходимым функционалом для работы с БД, конфигурируется с помощью YML или php файлов. Но для упрощения работы с ним в битриксе пришлось бы написать определенный слой, в котором будет осуществляться помощь для создания миграций.

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

arrilot/bitrix-migrations

Эта библиотека оказалась для меня золотой серединой. Сделанная Ильёй Некрасовым из Greensight, простая, в основе которой лежат компоненты symfony и laravel, легко подключается через composer. Работа с библиотекой строится настолько просто и очевидно, что изучение возможностей укладывается в полчаса-час. Для упрощения и ускорения написания миграций для битрикса есть функционал «шаблонов», который содержит куски предустановленного кода. Также, как и в worksolutions/bitrix-module-migrations, есть возможность автоматически следить за изменениями схемы данных некоторых сущностей и создавать файлы миграций.
В общем говоря, простота модуля, его расширяемость, отсутствие административного интерфейса и следование стандартам подкупило меня, и я решил использовать именно его.

Интегрируемся

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

Библиотека миграций предоставляет перечень следующих команд: install, make, migrate, rollback, templates, status. Каждая команда выполнена с помощью Symfony Console Component. В console-jedi я хотел видеть эти команды в отдельном пространстве имен, чтобы не путать с командами самого console-jedi. Сказано - сделано.

сonsole-jedi позволяет встроить в себя команды из приложения двумя способами. Первый способ - встроить с помощью модуля. В корне вашего битриксового модуля должен находиться файлик cli.php, который должен возвращать массив вида

return [
    'commands' => [
        //Тут массив с инициализацией ваших команд
    ]
];

О втором способе я расскажу чуть ниже.
У нас в команде как раз есть подходящий для использования первого способа модуль - maximaster/tools, в который было бы круто прикрутить миграции, и дать возможность всем разработчикам при подключении данного модуля использовать миграции на любом проекте.

Для интеграции в maximaster/tools нужно было сделать несколько простых шагов:

  1. Добавить зависимость maximaster/tools от arrilot/bitrix-migrations и notamedia/console-jedi
  2. Написать небольшой класс-адаптер, который позволит инициализировать модуль миграций и консольные команды. Тут я разделил команды на 2 вида - файловые и БДшные, ниже поясню, зачем именно. В методе addNamespaceToCommands я добавил неймспейс migrate для всех инициализированных команд.
namespace Maximaster;

use Arrilot\BitrixMigrations\Commands\MakeCommand;
use Arrilot\BitrixMigrations\Commands\InstallCommand;
use Arrilot\BitrixMigrations\Commands\MigrateCommand;
use Arrilot\BitrixMigrations\Commands\RollbackCommand;
use Arrilot\BitrixMigrations\Commands\TemplatesCommand;
use Arrilot\BitrixMigrations\Commands\StatusCommand;
use Arrilot\BitrixMigrations\Migrator;
use Maximaster\Extend\Arrilot\BitrixMigrations\Storages\BitrixDatabaseStorage;
use Arrilot\BitrixMigrations\TemplatesCollection;

/**
 * Класс, который позволяет прицепить команды модуля миграции к console-jedi
 * @package Maximaster
 */
class MigrationsAdapter
{
    private $migrator = null;
    private $storage = null;
    private $templates = null;

    public function __construct()
    {
        $config = $this->getConfig();

        $this->storage = new BitrixDatabaseStorage($config[ 'table' ]);
        $this->templates = new TemplatesCollection();
        $this->templates->registerBasicTemplates();

        $this->migrator = new Migrator($config, $this->templates, $this->storage);
    }

    /**
     * Получает массив с конфигурацией модуля
     * table - название таблицы в БД, которая хранит миграции
     * dir - название директории в корне проекта, которая содержит файлы миграций
     * @return array
     */
    public function getConfig()
    {
        return array(
            'table' => 'maximaster_db_migrations',
            'dir' => './migrations',
        );
    }

    /**
     * Получает перечень всех команд
     * @return \Arrilot\BitrixMigrations\Commands\AbstractCommand[]
     */
    public function getCommands()
    {
        $migrationCommands = array_merge($this->getDatabaseCommands(), $this->getFileCommands());
        return $migrationCommands;
    }

    /**
     * Добавляет пространство имен для команд
     * @param \Arrilot\BitrixMigrations\Commands\AbstractCommand[] $commands
     * @return array
     */
    private function addNamespaceToCommands(array $commands)
    {
        foreach ($commands as &$command) {
            $commandName = $command->getName();
            $command->setName('migration:' . $commandName);
        }

        return $commands;
    }

    /**
     * Получает список команд, для выполнения которых требуется наличие подключения к БД
     * @return \Arrilot\BitrixMigrations\Commands\AbstractCommand[]
     */
    public function getDatabaseCommands()
    {
        $config = $this->getConfig();
        $commands = array(
            new InstallCommand($config[ 'table' ], $this->storage),
            new MigrateCommand($this->migrator),
            new RollbackCommand($this->migrator),
            new StatusCommand($this->migrator),
        );

        return $this->addNamespaceToCommands($commands);
    }

    /**
     * Получает список команд, для выполнения которых НЕ требуется наличие подключения к БД
     * @return \Arrilot\BitrixMigrations\Commands\AbstractCommand[]
     */
    public function getFileCommands()
    {
        $commands = array(
            new MakeCommand($this->migrator),
            new TemplatesCommand($this->templates),
        );

        return $this->addNamespaceToCommands($commands);
    }
}
  1. Поскольку модуль миграций рассчитывает на то, что ядро уже инициализировано полностью в момент запуска, а console-jedi работает несколько иначе, то нужно было создать небольшой враппер для класса работы с БД из модуля миграций. Единственная его цель - предоставить подключение к БД в случае, если ядро инициализировалось. Исходники можно посмотреть на гитхабе, там все тривиально.
  2. Подключить команды в cli.php в корне модуля:
<?php

/**
 * Регистрируем все команды миграций arrilot/bitrix-migrations
 */
$migrationsConnector = new \Maximaster\MigrationsAdapter();
$migrationsCommands = $migrationsConnector->getCommands();

$config = array(
    'commands' => $migrationsCommands
);

return $config;

И вуаля!

Файловые и не файловые команды

Я уже писал в более ранних постах, что мы в команде хостим и ведем разработку проектов на отдельном сервере, однако весь код хранится локально на компьютерах разработчиков и синхронизируется с сервером разработки при изменениях.

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

maximaster/tools - это не совсем типичный модуль в понятии Битрикса. Он не использует нативную автозагрузку битрикса, благодаря чему его классы тоже можно использовать и вне битрикса. Поэтому подключив модуль через composer, вы получаете тот же модуль миграций и адаптер к нему, которые можно использовать благодаря psr-4.

В jedi есть еще один способ добавления команд - через файл .jedi.php в корне проекта. Схема примерно та же, что и в cli.php но в нем мы зарегистрируем только файловые операции:


use Maximaster\MigrationsAdapter;

return [
    'web-dir' => 'htdocs',
    'env-dir' => 'environments',
    'useModules' => true,
    'commands' => (new MigrationsAdapter())->getFileCommands()
];

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

Да, джедай будет ругаться на отсутствие битрикса, но это и ожидалось. Но зато теперь на любом ПК разработчика будут доступны команды, упрощающие создание миграций. Создав файл миграции через migration:make не придется идти по ssh на сервер разработки и выкачивать нужную миграцию (особенно когда их будут сотни и тысячи)

Вот такая получилась полезная интеграция!

Я не сомневаюсь, что и для Phinx и для ws.migrations можно сделать подобную интеграцию. Коллеги из Notamedia (спасибо им за console-jedi), кстати как раз используют Phinx в своих проектах, который интегрирован с джедаем. Данная статья больше о том, каким путем я пошел для подключения миграций в Битрикс, а не о том, каким именно инструментом пользоваться для этого.

Спасибо за внимание.