Пора начинать наконец заниматься делом и приступать к созданию модуля. Первым делом нужно реализовать базовую файловую структуру и механизм инсталляции. Каждый раз так влом создавать эту структуру всю, лень писать инсталлятор, но надо перебороть себя и сделать.
Ложка меда и бочка дегтя
Кстати, в маркетплейсе есть модуль, который помогает создавать модули — bitrix.mpbuilder. Разработал его Битрикс, видимо, в помощь партнерам. Но, судя по комментариям, этот модуль уже 2 года не получал обновлений и вообще работает не полностью. Меня это поначалу не смутило, поэтому я все равно поставил этот модуль и попытался сгенерировать из него структуру файлов.
Модуль предлагает разработчику пройти 5 шагов: создать структуру модуля, выделить языковые фразы, редактор языковых фраз (который не работает), создать архив с модулем и собрать новую версию для обновления модуля в маркете. Иду на первый шаг, заполняю там все интересующие меня на данный момент поля:
Вот что получилось из этого:
Он создал директорию install, в которой лежит готовый инсталлятор и файл с описанием версии. Все языковые фразы, которые я вводил (описание модуля, название и т.д.) он выделил в необходимый для этого языковой файл. Также он создал необходимый для работы модуль include.php. Все бы хорошо, но …
Все это не годится и придется переделывать 🙂 Почему? Причины:
- описание класса инсталлятора ну вообще не годится. Обилие устаревших конструкций, конструктор в формате php4, отсутствие модификаторов доступа к методам класса, табы вместо пробелов, куча лишнего кода …
- закрывающие теги в php файлах! Мало того, что они есть, так после них есть еще и переносы строк!
что будет расцениваться как вывод в стандартный поток вывода и в некоторых случаях может привести к очень трудноуловимым ошибкам (на эту тему напишу отдельный пост)
- часть логики вынесено в include.php, а именно создание пункта меню. Я понимаю, что битрикс рекомендует это делать, нужно это для того, чтобы обеспечить работу бесплатного пробного периода (весь код include.php обфусцируется, и туда добавляется проверка на пробный режим). Но я бы выносил в эту часть что-то более существенное, наверно. Кстати, надо бы подумать, что вынести туда 🙂
В довесок к этому, в модуле не работает третий шаг (редактор языковых файлов). В общем у битрикса как всегда — подзабили…
Придется создавать самому. Кстати, вот неплохая идея для опенсорса — запилить конструктор модулей с помощью symfony/console и composer create-project, думаю будет быстрее, удобнее, и более гибко, а соответственно расширяемо, по сравнению с этим bitrix.mpbuilder. Туда же можно будет прикрутить сборку пакета с модулем, а также сборку файлов обновлений на основании версионного контроля. Найти бы время … Или может быть уже есть что-то подобное?
Для начала нам понадобится раздобыть инсталляцию битрикса. Можно взять официальную демку, и развернуть чистую битру по мануалу. Но я не буду заморачиваться и просто начну работу по модулю в одном из своих существующих проектов, чтобы не тратить время на развертывание.
Кроме этого нам понадобится версионный контроль. Думаю, в этом блоге никому не надо объяснять целесообразность его использования даже для одного разработчика.
Создадим новый проект в PhpStorm и создадим там структуру файлов и папок по мануалу.
Жутковато, правда? Но ничего, справимся. Тут я создал полную версию всей структуры, за исключением устаревших возможностей и с учетом того, что модуль будет работать на mysql-подобной базе. Я сохраню всю эту структуру на будущее для того, чтобы может быть потом взяться за конструктор модулей, а сейчас пока удалю все лишнее. Исходя из ограничений, принятых на проекте, я буду использовать только d7 для разработки своих классов, а значит директория /classes мне не понадобится. Также мне не понадобится файл /admin/menu.php, т.к. меню у меня будет добавляться с помощью обработчика события. /install/images и /install/js мне тоже не понадобятся, т.к. все механизмы, связанные с интерактивом я буду выносить в компоненты. Может быть потом появится какой-то красивый логотип, тогда добавлю его в пункт меню, но не более. Кастомизировать внешний вид админки мы тоже не будем, поэтому директория /panel тоже не нужна. Поскольку я буду работать с таблицами d7, то и создание таблиц будет происходить с помощью ORM, а значит мне не понадобится (по крайней мере пока), директория /install/db. Еще я хочу удалить директорию /install/admin (битрикс называет скрипты из этой папки «вызывающими»). Вот и у меня эти скрипты вызывают много вопросов и из-за них я впадаю в уныние, поэтому не буду хранить их в проекте а просто буду генерировать на лету в процессе инсталляции. Ну и напоследок, можно удалить файл /prolog.php, который имеет весьма сомнительное предназначение. Если он мне понадобится, то заведу, но сейчас не вижу в нем никакого смысла. Итого получаем следующую структуру:
Уже не так страшно.
В .gitignore добавим директорию /vendor, которая скорее всего у меня тут появится для хранения скриптов, нужных для разработки.
Для того, чтобы иметь отдельный проект, но при этом иметь возможность работать с этим кодом в другом проекте, я просто создал симлинк из одного проекта в другой. В проекте, где используется битрикс я просто создал симлинк /local/modules/maximaster.coupanda, который ссылается на мой проект. Но тут можно придумать 100500 схем работы с модулем внутри другого проекта, дело вкуса.
Код
Наконец-то, добрался я до разработки. Инсталлятор должен находиться в директории /install/index.php. Это должен быть класс, наследник от \CModule с именем maximaster_coupanda. Данный класс должен присвоить значения свойствам:
1 2 3 4 5 6 7 8 9 10 |
public $MODULE_NAME; public $MODULE_DESCRIPTION; public $MODULE_VERSION; public $MODULE_VERSION_DATE; public $MODULE_ID; public $MODULE_SORT; public $SHOW_SUPER_ADMIN_GROUP_RIGHTS; public $MODULE_GROUP_RIGHTS; public $PARTNER_NAME; public $PARTNER_URI; |
Часть из них обязательны, часть нет. Все свойства в родителе объявлены через var, поэтому являются публичными. В конструкторе создадим набор методов, который будет инициализировать значения этих свойств:
1 2 3 4 5 6 7 8 9 |
public function __construct() { $this->initModuleId(); $this->initModuleVersionDefinition(); $this->initModuleName(); $this->initModuleDescription(); $this->initModulePartnerInfo(); $this->initModuleGroupRights(); } |
Тут все довольно тривиально, за исключением версии модуля. Номер версии модуля должен храниться в файле /install/version.php в виде массива. Плюс к этому нужно в классе модуля записывать эти значения. Зачем так сделано? Да хз… Но в связи с этим нужно налепить небольшую городушку, которая будет брать значение из этого файла и записывать его в модуль. Получаем нечто такое для какой-то … версии … :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
protected function initModuleVersionDefinition() { $versionDefinition = $this->getModuleVersionDefinition(); $this->MODULE_VERSION = $versionDefinition['VERSION']; $this->MODULE_VERSION_DATE = $versionDefinition['VERSION_DATE']; } protected function getDefaultVersionDefinition() { return [ 'VERSION' => '1.0.0', 'VERSION_DATE' => '1970-01-01 00:00:00' ]; } protected function getModuleVersionDefinition() { $arModuleVersion = []; include __DIR__ . '/version.php'; $defaultVersionDefinition = $this->getDefaultVersionDefinition(); if (!\is_array($arModuleVersion) || empty($arModuleVersion)) { return $defaultVersionDefinition; } $version = isset($arModuleVersion['VERSION']) ? $arModuleVersion['VERSION'] : $defaultVersionDefinition['VERSION']; $versionDate = isset($arModuleVersion['VERSION_DATE']) ? $arModuleVersion['VERSION_DATE'] : $defaultVersionDefinition['VERSION_DATE']; return [ 'VERSION' => $version, 'VERSION_DATE' => $versionDate ]; } |
Тут я в некоторых местах использовал венгерскую нотацию (которую, к слову, не очень то воспринимаю в условиях современного процесса разработки), но это обусловлено необходимостью работать с битриксом.
Инсталляция/деинсталляция
Для процесса инсталлирования в битриксе предусмотрен метод DoInstall(). А для деинсталлирования DoUninstall() соответственно. Кроме этого битриксом заложен заранее предустановленный набор методов, которые должны проинсталлировать файлы модуля, почтовые события и данные для БД — InstallFiles, InstallEvents и InstallDB соответственно, в автоматическом режиме. Для деинсталляции предусмотрены аналогичные методы. Пока что модуль у нас пустой, инсталлировать кроме него самого нечего, поэтому методы инсталляции файлов и почтовых событий оставлю пока пустыми, а в методе инсталляции БД пропишем регистрацию модуля в системе, а сам метод инсталляции БД добавим в DoInstall. Для остальных методов инсталляции добавим заглушки и тоже добавим их в DoInstall. Получается вот такая штука:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public function DoInstall() { $this->InstallDB(); $this->InstallEvents(); $this->InstallFiles(); } public function InstallDB() { \Bitrix\Main\ModuleManager::registerModule($this->MODULE_ID); return true; } public function InstallFiles() { return true; } public function InstallEvents() { return true; } |
Ровно то же самое получается для деинсталляции.
Система прав доступа
В битриксе есть несколько уровней доступа — права на файлы/директории, права модулей (на основе ролей или «просто права»). Права на файлы нас не интересуют, ими управляет модуль управления структурой. По условиям задачи мне потребуется только 2 роли — администратор модуля (который будет иметь права на все) и все остальные (которые не будут иметь доступа).
Я думаю, что для реализации этих возможностей можно не использовать систему ролей, чтобы не усложнять, а обойтись системой обычных прав доступа. Создам 2 права доступа пока — «[D] Доступ запрещен» и «[W] Полный доступ». Буквенные обозначения будут использоваться в коде для проверки прав. Данные буквы выбраны из соображения «привычности» для мира битрикса, но можно использовать любые, главное чтобы вы с оператором сравнения могли их нормально обрабатывать. Тут понятно, что W (Write) больше чем D (Denied), главное не напутать.
Для того, чтобы обозначить список прав доступа, нужно в классе модуля добавить метод GetModuleRightList. Этот метод должен возвращать массив описания прав доступа, доступных для вашего модуля. В моем случае получился вот такой простой метод:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public function GetModuleRightList() { $rightsReferenceIds = ['D', 'W']; $references = []; foreach ($rightsReferenceIds as $referenceId) { $references[] = "[{$referenceId}] " . \GetMessage('MAXIMASTER.COUPANDA:MODULE_RIGHTS_REFERENCE_' . $referenceId); } return [ 'reference_id' => $rightsReferenceIds, 'reference' => $references, ]; } |
Если же нужно использовать систему ролей, то необходимо вместо этого метода добавить метод GetModuleTasks, где описать массив чуть более сложной структуры, но суть его проста — добавить строковые имена всем возможным операциям и привязать эти операции к ролям и также дать буквенные обозначения. Благодаря этому администратор главного модуля сможет добавлять собственные роли к вашему модулю в разделе «Настройки > Пользователи > Уровни доступа». Но мне это не нужно, поэтому пока пропустим. Надо бы статейку про права накатать, а то инфы мало по этому поводу в интернете …
Остается только создать набор языковых файлов и вынести туда всякие тексты — названия прав доступа, название организации-разработчика, название и описание модуля, etc.
Первый пуск
Итак, что мы имеем. В /local/modules/ у нас лежит директория maximaster.coupanda, в которой есть минимально необходимая структура для работы модуля, инсталлятор модуля и языковые файлы модуля.
Идем в админку (Marketplace > Установленные решения) и смотрим, что там имеется:
Проверяем, что модуль действительно устанавливается и удаляется:
Можно сделать первый коммит 🙂
В следующей «серии» будем делать функционал по части БД и прикручивать его к инсталлятору.