Создание, настройка и перенос Highload-блоков
Highload-блок создают до добавления записей: сначала задают имя блока и таблицы, затем добавляют пользовательские поля. Для описания блока используйте Bitrix\Highloadblock\HighloadBlockTable, а для полей — класс классического API CUserTypeEntity.
Изменение структуры затрагивает метаданные и хранилище записей. Перед переименованием таблицы, удалением поля или блока создайте резервную копию и остановите процессы, которые работают с этим Highload-блоком.
Операции чтения и изменения записей приведены в статье Работа с записями. Связь описания блока, полей и динамического ORM-класса разобрана в статье Архитектура и основные объекты.
Примеры создают часть полей из общей тестовой схемы. Полный набор кодов и типов приведен в разделе Схема данных в примерах.
Подготовить имена и подключить модуль
Перед созданием блока задайте значения NAME и TABLE_NAME. Они нужны для разных задач:
-
NAME— имя ORM-объекта. Значение начинается с заглавной латинской буквы, содержит только латинские буквы и цифры и не превышает 100 символов. ОкончаниеTableи имяCollectionбез учета регистра использовать нельзя. -
TABLE_NAME— имя таблицы записей. Значение содержит только строчные латинские буквы, цифры и знак подчеркивания и не превышает 64 символов.
Оба имени должны быть уникальными. Для TABLE_NAME модуль также проверяет, что таблица с таким именем отсутствует в базе данных.
Перед обращением к API подключите модуль highloadblock:
use Bitrix\Main\Loader;
if (!Loader::includeModule('highloadblock'))
{
throw new \RuntimeException('Не удалось подключить модуль highloadblock');
}
Все следующие примеры предполагают, что модуль уже подключен.
Управлять описанием блока
Класс HighloadBlockTable создает, получает, изменяет и удаляет описание блока. Эти операции также могут менять физическую структуру хранения, поэтому проверяйте объект результата после каждого изменения.
Создать блок
Перед созданием блока проверьте, существует ли блок с таким же именем. Такая проверка делает установочный или миграционный скрипт повторно запускаемым и не создает дубликат.
Пример. Создайте блок ProductColor или получите идентификатор уже существующего блока:
use Bitrix\Highloadblock\HighloadBlockTable;
// Проверяем, существует ли блок с таким именем
$highloadBlock = HighloadBlockTable::getList([
'select' => ['ID', 'NAME', 'TABLE_NAME'],
'filter' => ['=NAME' => 'ProductColor'],
'limit' => 1,
])->fetch();
if ($highloadBlock)
{
$highloadBlockId = (int)$highloadBlock['ID'];
}
else
{
// Создаем блок, если он не найден
$result = HighloadBlockTable::add([
'NAME' => 'ProductColor',
'TABLE_NAME' => 'product_color',
]);
if (!$result->isSuccess())
{
throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}
$highloadBlockId = (int)$result->getId();
}
Метод add() возвращает объект Bitrix\Main\ORM\Data\AddResult с результатом добавления. По нему можно проверить успешность операции, получить ошибки и идентификатор созданного блока. После успешного сохранения описания модуль создает таблицу записей с системным полем ID. Если таблицу создать не удалось, модуль удаляет добавленное описание и записывает ошибку в объект результата.
Получить один блок или список
Если известен идентификатор, используйте getById(). Метод fetch() возвращает массив описания или false:
$highloadBlock = HighloadBlockTable::getById($highloadBlockId)->fetch();
if (!$highloadBlock)
{
throw new \RuntimeException('Highload-блок не найден');
}
Для поиска по имени, таблице или другим полям используйте getList(). В следующем примере результат содержит основные поля и локализованное название для текущего языка:
$highloadBlocks = HighloadBlockTable::getList([
'select' => [
'ID',
'NAME',
'TABLE_NAME',
'FIELDS_COUNT',
'LANG_NAME' => 'LANG.NAME',
],
'order' => ['NAME' => 'ASC'],
])->fetchAll();
Поле FIELDS_COUNT содержит вычисляемое количество пользовательских полей блока. Поле LANG_NAME содержит название для текущего языка, если оно настроено.
Изменить имя блока или таблицы
Метод update() принимает идентификатор блока и изменяемые поля. Изменение NAME влияет на имя динамического ORM-класса при следующей компиляции. Изменение TABLE_NAME переименовывает таблицу записей и дополнительные хранилища множественных полей.
Пример. Переименуйте ORM-объект и таблицу:
$result = HighloadBlockTable::update($highloadBlockId, [
'NAME' => 'CatalogColor',
'TABLE_NAME' => 'catalog_color',
]);
if (!$result->isSuccess())
{
throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}
После переименования завершите текущий сценарий изменения структуры. В следующем запросе получите описание блока заново и вызовите compileEntity() без второго аргумента. Такой порядок исключает одновременную работу с классами, которые созданы по старому и новому описанию. Правила для разных структурных изменений приведены в разделе Обновить ORM-объект после изменения структуры.
Не переименовывайте один блок одновременно из нескольких процессов. Метод изменяет описание блока и таблицы поэтапно. Если один шаг завершится с ошибкой, часть изменений может сохраниться.
Удалить блок
Метод delete() удаляет описание, записи, пользовательские поля, связанные файлы, локализованные названия, правила доступа и таблицы хранения.
$result = HighloadBlockTable::delete($highloadBlockId);
if (!$result->isSuccess())
{
throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}
Операция необратима. До удаления сохраните структуру, записи и файлы, если они понадобятся для восстановления. Не продолжайте зависимые шаги миграции после ошибки или исключения.
Настроить локализованные названия
Класс Bitrix\Highloadblock\HighloadBlockLangTable хранит название блока для каждого языка. Запись определяется составным ключом: идентификатором блока ID и идентификатором языка LID. Поле LID содержит не более двух символов, а NAME — не более 100 символов.
Пример. Добавьте или обновите русское и английское названия:
use Bitrix\Highloadblock\HighloadBlockLangTable;
$localizedNames = [
'ru' => 'Цвета товаров',
'en' => 'Product colors',
];
foreach ($localizedNames as $languageId => $name)
{
$primary = [
'ID' => $highloadBlockId,
'LID' => $languageId,
];
$localizedName = HighloadBlockLangTable::getByPrimary($primary)->fetch();
if ($localizedName)
{
$result = HighloadBlockLangTable::update($primary, [
'NAME' => $name,
]);
}
else
{
$result = HighloadBlockLangTable::add($primary + [
'NAME' => $name,
]);
}
if (!$result->isSuccess())
{
throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}
}
Локализованное название используется в административном интерфейсе. Значение NAME в описании блока при этом не меняется.
Управлять пользовательскими полями
Пользовательские поля задают структуру записи. Для блока с идентификатором 7 передавайте в ENTITY_ID значение HLBLOCK_7. Метод HighloadBlockTable::compileEntityId() автоматически формирует этот идентификатор.
Основные параметры поля:
-
ENTITY_ID— идентификатор Highload-блока для подсистемы пользовательских полей. -
FIELD_NAME— код поля видаUF_*. Окончание_REFзарезервировано для ORM-ссылок. -
USER_TYPE_ID— тип пользовательского поля, напримерstring,integerилиfile. -
MULTIPLE— признак множественного поля:YилиN. По умолчанию используетсяN. -
MANDATORY— признак обязательного значения:YилиN. По умолчанию используетсяN. -
SORT— порядок поля. По умолчанию используется100. -
SETTINGS— настройки, состав которых зависит отUSER_TYPE_ID. -
EDIT_FORM_LABEL,LIST_COLUMN_LABELиLIST_FILTER_LABEL— подписи для форм, списка и фильтра по языкам.
Полный список типов и общие настройки приведены в статье Пользовательские поля.
Создать одиночное обязательное поле
Метод CUserTypeEntity::Add() возвращает идентификатор поля или false. При ошибке класс записывает исключение в объект приложения.
Пример. Создайте обязательное строковое поле UF_NAME:
global $APPLICATION;
$userTypeEntity = new \CUserTypeEntity();
$entityId = HighloadBlockTable::compileEntityId($highloadBlockId);
$userField = \CUserTypeEntity::GetList([], [
'ENTITY_ID' => $entityId,
'FIELD_NAME' => 'UF_NAME',
])->Fetch();
if ($userField)
{
$userFieldId = (int)$userField['ID'];
}
else
{
$userFieldId = $userTypeEntity->Add([
'ENTITY_ID' => $entityId,
'FIELD_NAME' => 'UF_NAME',
'USER_TYPE_ID' => 'string',
'XML_ID' => 'PRODUCT_COLOR_NAME',
'SORT' => 100,
'MULTIPLE' => 'N',
'MANDATORY' => 'Y',
'SHOW_FILTER' => 'S',
'SHOW_IN_LIST' => 'Y',
'EDIT_IN_LIST' => 'Y',
'IS_SEARCHABLE' => 'N',
'SETTINGS' => [
'DEFAULT_VALUE' => '',
],
'EDIT_FORM_LABEL' => [
'ru' => 'Название',
'en' => 'Name',
],
'LIST_COLUMN_LABEL' => [
'ru' => 'Название',
'en' => 'Name',
],
'LIST_FILTER_LABEL' => [
'ru' => 'Название',
'en' => 'Name',
],
]);
if (!$userFieldId)
{
$exception = $APPLICATION->GetException();
$message = $exception
? $exception->GetString()
: 'Не удалось создать пользовательское поле';
throw new \RuntimeException($message);
}
}
Значение SHOW_FILTER управляет фильтром в административном списке:
-
N— скрыть поле, -
I— искать точное совпадение, -
E— искать по маске, -
S— искать по подстроке.
После добавления поля модуль изменяет схему таблицы блока. Продолжите работу с записями в новом запросе. Если сценарий должен использовать новое поле сразу, примените порядок из раздела Обновить ORM-объект после изменения структуры.
Создать множественное файловое поле
Для множественного поля передайте MULTIPLE со значением Y. Модуль создаст дополнительное хранилище значений и добавит поле-массив в динамическую ORM-карту.
Пример. Добавьте множественное поле UF_FILES типа Файл:
$fileFieldId = $userTypeEntity->Add([
'ENTITY_ID' => $entityId,
'FIELD_NAME' => 'UF_FILES',
'USER_TYPE_ID' => 'file',
'SORT' => 200,
'MULTIPLE' => 'Y',
'MANDATORY' => 'N',
'EDIT_FORM_LABEL' => [
'ru' => 'Файлы',
'en' => 'Files',
],
]);
if (!$fileFieldId)
{
$exception = $APPLICATION->GetException();
$message = $exception
? $exception->GetString()
: 'Не удалось создать файловое поле';
throw new \RuntimeException($message);
}
Если нужно запустить создание повторно, сначала найдите поле по ENTITY_ID и FIELD_NAME, как в примере с UF_NAME. Вызывайте Add() только при отсутствии поля, иначе метод вернет ошибку.
Удаление файлового поля удаляет связанные файлы. Удаление всего блока также удаляет файлы из его файловых полей.
Изменить настройки поля
Метод Update() изменяет обязательность, сортировку, подписи и настройки типа. Метод не изменяет ENTITY_ID, FIELD_NAME, USER_TYPE_ID и MULTIPLE. Класс исключает эти параметры из обновления.
Если обновляете подписи, передайте все нужные виды подписей для всех языков. Метод заменяет сохраненные языковые подписи на значения из текущего вызова.
$updated = $userTypeEntity->Update($userFieldId, [
'SORT' => 50,
'MANDATORY' => 'N',
'EDIT_FORM_LABEL' => [
'ru' => 'Название цвета',
'en' => 'Color name',
],
'LIST_COLUMN_LABEL' => [
'ru' => 'Название цвета',
'en' => 'Color name',
],
'LIST_FILTER_LABEL' => [
'ru' => 'Название цвета',
'en' => 'Color name',
],
]);
if (!$updated)
{
$exception = $APPLICATION->GetException();
$message = $exception
? $exception->GetString()
: 'Не удалось изменить пользовательское поле';
throw new \RuntimeException($message);
}
Чтобы изменить код, тип или множественность, создайте новое поле, перенесите значения, переключите код приложения и только затем удалите прежнее поле. Перенос значений выполняйте через динамический класс данных по правилам из статьи Работа с записями.
Удалить поле
Перед удалением убедитесь, что поле не используется в фильтрах, компонентах, обработчиках и связях. Метод Delete() удаляет метаданные и хранилище значений поля:
$deleted = $userTypeEntity->Delete($userFieldId);
if (!$deleted)
{
$exception = $APPLICATION->GetException();
$message = $exception
? $exception->GetString()
: 'Не удалось удалить пользовательское поле';
throw new \RuntimeException($message);
}
Удаление поля необратимо. Для множественного поля модуль удаляет дополнительное хранилище, а для файлового — связанные файлы.
Перенести структуру и данные
Для переноса структуры и данных в XML используйте административные страницы Контент > Highload-блоки > Экспорт / импорт. Страницы доступны только администратору. Импорт и экспорт работают с XML-файлами.
Экспортировать блок
На странице экспорта:
-
Выберите Highload-блок.
-
Укажите XML-файл для результата. Имя должно иметь расширение
.xmlи содержать только латинские буквы, цифры, знак подчеркивания, точку и косую черту. -
Включите экспорт структуры, данных или обоих вариантов.
-
Запустите экспорт и скачайте результат.
Экспорт структуры сохраняет описание блока, локализованные названия и пользовательские поля. Экспорт данных сохраняет записи. Файлы из пользовательских полей экспортируются в отдельный каталог рядом с XML-файлом, поэтому переносите XML и этот каталог вместе.
После скачивания удалите экспортированные файлы с сервера, если они больше не нужны.
Импортировать блок
На странице импорта можно создать новый блок из XML или выбрать существующий. Для существующего блока настройте режим до запуска:
-
Поле внешнего ключа — найти существующую запись по
IDили выбранному строковому либо целочисленному полю. Найденная запись обновляется, отсутствующая добавляется. -
Импортировать структуру — добавить отсутствующие пользовательские поля из XML. Поля с уже существующими кодами повторно не создаются.
-
Импортировать данные — добавить или обновить записи.
-
Сохранять связи со сторонними объектами — оставить значения привязок к сотрудникам, Highload-блокам, CRM, разделам и элементам информационных блоков.
Если поле внешнего ключа не выбрано, импорт добавляет каждую запись как новую, даже когда такая же запись уже существует. Для повторяемого импорта используйте стабильное уникальное поле и заранее проверьте уникальность его значений.
Перед импортом:
-
Создайте резервную копию целевого блока.
-
Проверьте, что на целевой системе доступны типы пользовательских полей и связанные объекты.
-
Перенесите каталог файлов вместе с XML, если блок содержит файловые поля.
-
Сначала выполните импорт на тестовой системе.
-
После завершения проверьте структуру, количество записей, значения внешнего ключа, файлы и связи.
Импорт останавливается при неизвестном поле или ошибке сохранения записи. После завершения удалите загруженные файлы импорта с сервера.
Подготовить повторяемую миграцию
Для установки модуля и доставки изменений между окружениями оформите создание блока и полей в одном управляемом сценарии. Последовательность должна учитывать зависимости:
-
Подключите модуль
highloadblock. -
Найдите блок по стабильному
NAMEи создайте его только при отсутствии. -
Получите идентификатор
ENTITY_IDчерезcompileEntityId(). -
Найдите каждое пользовательское поле по
ENTITY_IDиFIELD_NAME, затем добавьте отсутствующие поля или обновите разрешенные настройки. -
Перенесите значения до удаления или замены поля.
-
После структурных изменений завершите миграцию и продолжите работу с записями в новом запросе.
-
Проверьте результат каждого вызова и остановите зависимые шаги после первой ошибки.
API управления структурой изменяет несколько объектов хранения поэтапно. Если один шаг завершится с ошибкой, уже выполненные изменения могут сохраниться. Не запускайте этот сценарий одновременно в нескольких процессах и предусмотрите восстановление из резервной копии.
Проверить результат
После создания или переноса убедитесь, что структура готова к работе:
-
HighloadBlockTable::getById()возвращает описание с ожидаемымиNAMEиTABLE_NAME. -
CUserTypeEntity::GetList()с фильтром поENTITY_IDвозвращает ожидаемые коды, типы, обязательность и множественность полей. -
HighloadBlockTable::compileEntity()возвращает ORM-объект, а его класс данных содержит системное полеIDи добавленные поляUF_*. -
Тестовое добавление и чтение записи проходит через динамический класс данных.
-
После импорта совпадают количество записей, значения внешнего ключа, файлы и связи.