Перейти к содержимому

Готовое решение изнутри - файлы, следы, вмешательство

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

Механика

Решение Маркетплейса приезжает в проект одним архивом полной сборки. Внутри архива лежит каталог модуля с составным именем: до точки код партнёра, после - код решения. Это же имя дальше служит идентификатором в настройках, правах и подписках на события.

Каталог решения ложится в системную папку модулей, рядом со штатными модулями платформы. Собственный код проекта принято держать в каталоге проекта, а чужое решение приходит именно в системную папку. Отличить одно от другого проще всего по точке в имени: у модулей платформы её нет.

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

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

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

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

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

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

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

Тиражное решение кладёт в проект ещё и мастер настройки сайта. Его файлы лежат в каталоге мастеров под пространством имён партнёра, а публичная часть разворачивается уже при запуске мастера. Реальные пути и идентификаторы инфоблоков подставляются на этом шаге вместо макросов.

Шаги

  1. Открыть каталог решения в системной папке модулей и прочитать его файл установки целиком.
  2. Выписать из метода установки всё, что решение делает помимо копирования собственных файлов.
  3. Найти копии поставки снаружи: административные скрипты, компоненты, темы оформления и шаблоны.
  4. Собрать следы решения в базе: подписки на события, свои таблицы, настройки модуля и агенты.
  5. Сверить собранный список с методом удаления и отметить то, что снятие модуля не уберёт.

Код

Смотрим состав поставки решения:

/bitrix/modules/vendor.module/
├── install/index.php # класс установки: точка входа при установке и удалении
├── install/version.php # версия и её дата, по ним собираются обновления
├── install/mysql/install.sql # свои таблицы решения, отдельный файл на тип базы
├── install/admin/ # вызывающие скрипты, уедут в /bitrix/admin/
├── install/components/ # компоненты решения, уедут в каталог компонентов
├── lib/ # классы решения, находятся автозагрузкой
├── lang/ru/ # весь русский текст решения
└── options.php # страница настроек решения в административной части

Каталог поставки читается как список обещаний решения. Всё, что лежит внутри папки установки, при установке уедет наружу, а остальное работает прямо на месте.

Читаем метод установки решения:

public function DoInstall()
{
$this->InstallDB(); // выполняет install/<тип базы>/install.sql
$this->InstallFiles(); // копирует содержимое install/ в публичные каталоги
RegisterModuleDependences('sale', 'OnSaleOrderSaved', // модуль-источник
$this->MODULE_ID, '\\Vendor\\Module\\Handlers', 'onOrderSaved');
CAgent::AddAgent('\\Vendor\\Module\\Sync::run();', // фоновая работа решения
$this->MODULE_ID, 'N', 3600); // раз в час после запуска
RegisterModule($this->MODULE_ID); // без этой строки модуля для платформы нет
}

Метод установки - самый честный документ о решении. Он перечисляет ровно то, что решение делает с проектом, и читается быстрее описания в карточке Маркетплейса.

Ищем копии поставки вне каталога модуля:

Окно терминала
ls /home/bitrix/www/bitrix/admin/vendor_module_*.php # вызывающие админ-скрипты
ls -d /home/bitrix/www/bitrix/components/vendor/ # компоненты решения
ls -d /home/bitrix/www/bitrix/js/vendor.module/ # клиентские расширения
ls -dlt /home/bitrix/www/local/templates/*/ | head -5 # шаблон сайта из поставки

Копии живут своей жизнью и переживают удаление каталога модуля. Правка компонента снаружи не видна внутри поставки, а повторная установка возвращает файл в исходное состояние без предупреждения.

Собираем следы решения в базе:

-- подписки решения на чужие события: SORT задаёт очередь обработчиков
SELECT FROM_MODULE_ID, MESSAGE_ID, TO_CLASS, TO_METHOD, SORT
FROM b_module_to_module WHERE TO_MODULE_ID = 'vendor.module';
-- фоновая работа решения: ACTIVE = N означает выключенный агент
SELECT NAME, ACTIVE, AGENT_INTERVAL, NEXT_EXEC
FROM b_agent WHERE MODULE_ID = 'vendor.module';

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

Ищем таблицы и настройки решения:

SHOW TABLES LIKE 'b_vendor%';
SELECT NAME, SITE_ID, VALUE FROM b_option WHERE MODULE_ID = 'vendor.module';
-- пустой SITE_ID означает глобальную настройку, заполненный - привязку к сайту

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

Отличаем компоненты решения от штатных:

Окно терминала
# у штатных компонентов пространство имён bitrix, у решения - код партнёра
grep -rn "'vendor:" /home/bitrix/www/local/templates/ | head -10
grep -rn "'vendor:" /home/bitrix/www/*/index.php | head -10
# свои шаблоны штатных компонентов решение кладёт рядом со штатными
ls /home/bitrix/www/bitrix/components/bitrix/catalog.section/templates/

Пространство имён в вызове компонента сразу показывает его происхождение. Шаблон решения для штатного компонента виден иначе: по свежей папке среди шаблонов этого компонента.

Читаем метод удаления решения:

public function DoUninstall()
{
UnRegisterModuleDependences('sale', 'OnSaleOrderSaved',
$this->MODULE_ID, '\\Vendor\\Module\\Handlers', 'onOrderSaved');
CAgent::RemoveModuleAgents($this->MODULE_ID); // снимает агенты решения
$this->UnInstallFiles(); // удаляет копии по тем же адресам
$this->UnInstallDB(); // отдельная галка сохраняет данные
UnRegisterModule($this->MODULE_ID); // модуль исчезает из списка
}

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

Смотрим, чем обновление решения отличается от обновления платформы:

// updater.php: выполняется один раз до копирования файлов обновления
$updater->CopyFiles('install/components', 'components/vendor');
$errorMessage = 'обновление требует более новой версии продукта'; // остановка
// API самого этого обновления на текущем хите ещё недоступно

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

Ограничения

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

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

Конвертация кодировки при установке применяется только к файлам в папках языка. Русский текст, зашитый прямо в код решения, на сайте в другой кодировке превращается в набор символов.

Собственные права решение объявляет свойством класса установки и списком уровней доступа. Если автор этого не сделал, доступ к его страницам определяет только его собственный код, а штатные права модуля тут ни при чём.

Обновления приходят на все модули лицензии, включая неустановленные, а скрипт обновления выполняется до копирования файлов. Обращение к новому API прямо в этом скрипте даёт ошибку о неизвестном классе.

Типичные проблемы

Каталог решения на месте, а его функций на сайте нет.

Модуль скопировали файлами, но регистрацию в системе никто не выполнял. Платформа считает установленным только зарегистрированный модуль, а не найденную на диске папку.

Правки в файлах решения исчезли после его обновления.

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

Шаблон из решения открывается по отдельному адресу, а на главной старый дизайн.

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

Русский текст решения после установки стал набором символов.

Текст был зашит в код, а не вынесен в языковые файлы решения. Конвертация кодировки при установке применяется только к файлам внутри папок языка.

После удаления решения в базе остались его таблицы.

Метод удаления чистит ровно то, что предусмотрел автор решения. Часть данных сохраняется намеренно, отдельной галкой на шаге удаления модуля.

Ошибка о неизвестном классе появилась во время установки обновления.

Скрипт обновления обратился к API того же самого обновления, ещё не доступного на этом хите. Такой скрипт пишут независимым от нового кода и безопасным для повторного запуска.

Частые вопросы

Куда ставится решение из маркетплейса?

В системную папку модулей, отдельным каталогом с именем вида «партнёр.решение». Часть поставки оттуда копируется наружу: админ-скрипты, компоненты, темы и изображения.

Как понять, что именно решение изменило на сайте?

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

Чем компоненты решения отличаются от штатных?

Пространством имён: у платформы это bitrix, у решения - код партнёра. По нему компоненты решения находятся поиском в шаблонах и на страницах.

Где хранятся настройки решения?

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

Обновление решения и обновление платформы - это одно и то же?

Нет, это разные потоки обновлений с разными сроками и разными последствиями. Обновление решения содержит только его изменённые части и ставится по номерам версий.

Смежное

Первоисточник