Модуль для маркетплейс — от идеи до старта продаж. Часть 7 — еще немного возни с инсталлятором

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

Странно, что эту задачу до сих пор не решил вендор. Видимо, не очень-то ему это надо.

Ранее я выделил 3 этапа инсталляции модуля — БД, файлы и почтовые события. Точнее, выделил их не я, а сам вендор. И именно на этих трех этапах стараются сосредотачиваться и другие разработчики. В частности, в библиотеке notamedia/console.jedi, реализован механизм автоматической установки из cli, где есть явная зависимость на наличие всех этих трех методов.

Не знаю как у других, но у меня инсталлирование файлов модуля всегда вызывает головную боль. И все это из-за коряво спроектированного ядра. Для того, чтобы админские странички корректно открывались, они зачем-то обязательно должны быть в /bitrix/admin директории. По каким-то неизвестным причинам файлы компонентов модуля нужно копипастить в процессе инсталляции в /bitrix/components, хотя я уверен, что вендор мог бы реализовать возможность подключения компонентов из файлов модуля. Скрипты, стили, картинки … все это тоже нужно копировать куда-то в /bitrix/js, /bitrix/css и т.д. Короче — об этом нужно думать. И это вызывает проблемы в процессе инсталляции/деинсталляции — нужно следить за файлами, нужно следить за правами. Нужно при деинсталляции следить за тем, чтобы файлики удалились. Очень много всего того, зачем надо следить. А если файлов дофига — то проблем прибавляется на порядок. И что самое обидное — практически никакой поддержки со стороны вендора в этом плане нет, лишь пара функций. Но обо всем по порядку …

Главный класс инсталлятора

В момент инсталляции битрикс вызывает метод DoInstall(). Определим его как входную точку для всего процесса инсталляции и внутри него будем вызывать методы инсталлирования отдельных этапов. Но надо решить — в каком именно порядке, чтобы было удобно в случае возникновения проблемы откатить процесс инсталляции. В конкретно моем случае, инсталлировать почтовые события не нужно вообще, поэтому нужно лишь придумать, в каком порядке: «БД > Файлы» или «Файлы > БД». И в данном случае ответ очевиден. Регистрация модуля — это часть процесса инсталляции БД, без регистрации модуля нет вообще никакого смысла перекидывать файлы из инсталлятора в битрикс. К тому же при инсталляции БД можно использовать транзакции, которые сами нам все откатят в случае проблем. Поэтому сначала ставим БД, а уже потом файлы. Метод инсталляции почтовых событий оставляем пустым, но не удаляем его для совместимости. Ну и нужно добавить проверки на то, был ли инсталлирован предыдущий шаг, и если нет, то откатываем предыдущие успешно выполненные шаги. Получается нечто такое:

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

Инсталляция БД

Весь процесс инсталляции у нас уже был сделан ранее. Я просто беру и выношу всю логику инсталляции БД в отдельный класс. Для работы процесса инсталляции мне понадобится, как и ранее, подключение к БД и идентификатор модуля. Соответственно конструктор инсталлятора БД принимает только эти параметры и работает только с ними. Класс инсталлятора БД будет иметь только 2 публичных метода — install() и uninstall() для соответствующих операций. Код, который будет работать внутри инсталлятора, может породить исключение, поэтому нужно ловить это исключение и преобразовывать его в ошибку, которую надо показать пользователю, выполняющему инсталляцию. Получается нечто такое:

Абсолютно то же самое для деинсталлятора, только другие методы вызываются.

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

И опять то же самое при деинсталляции 🙂

В общем-то, на мой взгляд, все очень просто. Остается пара вопросов — куда файл с инсталлятором БД положить, и как его подключать 🙂 С одной стороны — это классы, необходимые для работы модуля, а значит нужно положить их либо в %module_root%/lib, либо в%module_root%/classes. Но, во-первых, их тогда можно будет случайно использовать уже при установленном модуле с помощью автозагрузчика, и тем самым удалить модуль. Вряд ли это, конечно, произойдет, но все же. Во-вторых, это все-таки классы, необходимые не для работы модуля, а только для его инсталляции, а значит ни%module_root%/classes, ни%module_root%/lib не подойдут. Ну ок, раз это относится к инсталляции, то и положим в директорию%module_root%/install, а заодно и проверим, как на это отреагирует команда модерации со стороны битрикса 🙂 Никакого автозагрузчика тут у нас нет, да и не надо, поэтому подключаем данный класс вручную в конструкторе главного инсталлятора.

Инсталляция файлов

Делаем по аналогии с БД. Создаем класс FileInstaller с тем же самым интерфейсом. Для работы этого инсталлятора необходим путь до директории модуля, а также докрут веб-сервера. Прокинем все необходимые зависимости в этот класс снаружи, чтобы не обращаться к ним через суперглобалки и синглтоны битрикса.

Абсолютно точно известно, что мне нужно будет инсталлировать вызывающие скрипты для административных скриптов модуля (сколько бы их ни было в будущем). Также абсолютно точно известно, что понадобится инсталлировать все компоненты. Об остальном пока думать не будем.

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

Тут уже начинается головняк. Надо думать о разделителе файлов. В большинстве случаев php корректно обработает unix-style файловые пути, но в некоторых случаях при работе на винде это может стать проблемой. Поэтому создадим функцию, которая будет на основании переданных аргументов строить пути к директориям независимо от ОС. Эдакое мелкое подобие path.join() из node.js

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

Инсталляция админских скриптов

Заключается она в том, чтобы перекинуть файлы из %module_root%/install/admin в /bitrix/admin. Но я уже писал ранее, что хранить пустые файлы с одним require бессмысленно, это мертвый груз. Гораздо логичнее было бы считывать список административных скриптов из %module_root%/admin и создавать на эти файлы ссылки в /bitrix/admin.  Тут надо предусмотреть пару ограничений — нужно создавать ссылки только на php файлы, а также не нужно создавать ссылку на файл menu.php Сказано — сделано:

Метод деинсталляции, по идее, должен быть таким же длинным. Нужно сканировать %module_root%/admin и по очереди удалять все файлы, которые есть в /bitrix/admin. Но я решил упростить его, сделав просто glob по именам файлов, которые я сам сгенерировал при инсталляции:

Инсталляция компонентов

Для копирования файлов директорий с учетом вложенности в битриксе есть специальная функция — \CopyDirFiles. Ей и воспользуемся. Получается очень простой метод инсталляции компонентов:

Для удаления файлов в битриксе есть аж две функции — \DeleteDirFiles и \DeleteDirFilesEx. Первая удаляет одноименные файлы из одной директории, которые были найдены в другой директории. Но функция не работает рекурсивно. А вторая работает рекурсивно, но просто удаляет все файлы из директории без сравнения. Поэтому сравнение придется написать:

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

Итоги

Бизнес логику в этот раз начать не удалось, зато привели в порядок сам инсталлятор. Надеюсь битриксоиды одобрят этот подход, иначе придется весь код возвращать в класс \maximaster_coupanda, и тогда он разрастется до 400+ строк, что будет не очень удобно в поддержке.

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

Да, кстати! Я тут залил исходники модуля на гитхаб. Можно посмотреть, как это все выглядит в сборе, и попробовать.

Ну и все, теперь можно приступать к реализации бизнес-логики и ключевого функционала.

  • nook85

    В частности, в библиотеке notamedia/console.jedi, реализован механизм автоматической установки из cli, где есть явная зависимость на наличие всех этих трех методов.

    Точно так же автоматом устанавливает модули и сам Битрикс. Небольшой ликбез по этому поводу сохранился в issues этой либы.

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

    Лучше на это не надеяться, для таблиц типа MyISAM транзакции не поддерживаются. Commit и rollback на них просто не будут ничего делать.

    • Марат, спасибо за ценные замечания! Не ожидал получить ответ от автора функциональности автоматической установки модулей в console.jedi 🙂

      Значит, битрикс вызывает автоматическую установку именно в том порядке, который ты указал? БД > События > Файлы? Получается, что при интерактивной установке я могу дать возможность пользователю самостоятельно настроить параметры установки, если это требуется. А при автоматической эти параметры (если они вообще есть) нужно выставить в дефолтные значения. Нужно будет учесть этот момент, если добавятся события в этот модуль, да и вообще на будущее.

      Про транзакции — да, вот это я облажался 🙂 Уже так давно не работал с MyISAM, что уже не задумываюсь о наличии или отсутствии транзакционной модели. Надо учить матчасть. Сделал пометку себе, подумаю как получше обработать ситуацию для MyISAM.

      Спасибо!