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

Пользовательские поля (UF) в 1С-Битрикс

Пользовательские поля - универсальный способ добавить к существующей сущности свои данные, не меняя схему базы. Так расширяют пользователей, разделы инфоблоков, сделки CRM, highload-блоки и собственные объекты. Разберём, как их создавать, читать и писать, и чем они отличаются от свойств инфоблока.

Как это работает

Поле живёт отдельно от сущности. Значения хранятся в служебных таблицах ядра, а не в таблице самого объекта, поэтому добавление поля не требует изменения схемы базы. Количество полей для одного объекта не ограничено.

Префикс UF_ обязателен. Код поля всегда начинается с UF_ и пишется заглавными латинскими буквами: UF_PHONE, UF_BIRTHDATE, UF_RATING. Именно по префиксу ядро отличает пользовательское поле от штатного. Задать код можно только при создании - позже он неизменяем.

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

Сущность задаётся кодом ENTITY_ID. Системные примеры: USER для пользователя, IBLOCK_N для инфоблока с идентификатором N, IBLOCK_N_SECTION для его разделов, HLBLOCK_N для highload-блока, а в коробочном Битрикс24 - TASKS_TASK, CRM_LEAD, CRM_DEAL и другие. Если модуль штатно поля не поддерживает, их можно зарегистрировать на произвольном коде сущности.

Два уровня API, и их нельзя смешивать. Метаданные поля - его тип, множественность, подписи - создаёт и меняет класс CUserTypeEntity. Значения у конкретной записи читает и пишет менеджер полей $USER_FIELD_MANAGER (класс CUserTypeManager). В D7 для чтения метаданных есть UserFieldTable, но для изменения определений замены CUserTypeEntity пока нет.

Интеграция с ORM держится на одном методе. Если у ORM-сущности определён статический getUfId(), возвращающий код сущности, то поля UF_* работают в выборке как обычные: их можно выбирать, фильтровать и сортировать - ORM сам добавит нужные JOIN, а у объекта появятся геттеры и сеттеры.

Примеры

1. Создание поля

$userTypeEntity = new CUserTypeEntity();
$userFieldId = $userTypeEntity->Add([
'ENTITY_ID' => 'IBLOCK_3_SECTION', // к чему привязано поле
'FIELD_NAME' => 'UF_DEV2DAY_FIELD', // код, всегда с UF_
'USER_TYPE_ID' => 'string', // тип поля
'XML_ID' => 'XML_ID_DEV2DAY_FIELD',
'SORT' => 500,
'MULTIPLE' => 'N',
'MANDATORY' => 'N',
'SHOW_FILTER' => 'N', // N - не в фильтре, I - точное, E - маска, S - подстрока
'IS_SEARCHABLE'=> 'N',
'SETTINGS' => [
'DEFAULT_VALUE' => '', 'SIZE' => '20', 'ROWS' => '1',
'MIN_LENGTH' => '0', 'MAX_LENGTH' => '0', 'REGEXP' => '',
],
'EDIT_FORM_LABEL' => ['ru' => 'Пользовательское свойство', 'en' => 'User field'],
'LIST_COLUMN_LABEL' => ['ru' => 'Пользовательское свойство', 'en' => 'User field'],
'LIST_FILTER_LABEL' => ['ru' => 'Пользовательское свойство', 'en' => 'User field'],
'ERROR_MESSAGE' => ['ru' => 'Ошибка при заполнении', 'en' => 'An error'],
]);
if ($userFieldId === false) {
// регистрация не прошла - например, поле с таким кодом уже есть
}

Подписи задаются массивом по кодам языков, а состав SETTINGS зависит от типа поля. Метод возвращает идентификатор нового поля или false - результат нужно проверять.

Обновление метаданных выглядит просто, но с важным ограничением:

$userTypeEntity->Update($userFieldId, ['MANDATORY' => 'Y']);

Через Update() нельзя менять USER_TYPE_ID, ENTITY_ID, FIELD_NAME и MULTIPLE - эти свойства задаются один раз. Если нужно изменить одно из них, создают новое поле, переносят значения и удаляют старое.

2. Значения для поля-списка

$enumField = new CUserFieldEnum();
$addEnum = [];
$addEnum['n' . $i] = [ // ключ ОБЯЗАН начинаться с n
'XML_ID' => $key,
'VALUE' => $value,
'DEF' => 'N', // значение по умолчанию
'SORT' => $i * 10,
];
$enumField->SetEnumValues($fieldId, $addEnum);

Варианты списка хранятся отдельно от определения поля. Ключ массива, начинающийся с буквы n, - жёсткое требование фреймворка для новых элементов; при другом ключе значения просто не добавятся.

3. Чтение и запись значений

/** @var \CUserTypeManager $manager */
$manager = \Bitrix\Main\UserField\Internal\UserFieldHelper::getInstance()->getManager();
$entityId = 'BLOG_RATING';
$itemId = 123;
// запись: код сущности, идентификатор записи, массив «поле - значение»
$manager->Update($entityId, $itemId, ['UF_RATING' => 50]);
// чтение одного поля и всех полей сущности
$value = $manager->GetUserFieldValue($entityId, 'UF_RATING', $itemId);
$allFields = $manager->GetUserFields($entityId, $itemId);

Это же доступно через глобальный $USER_FIELD_MANAGER - объект тот же самый. Такой подход работает с любым кодом сущности, даже если объект штатно поля не поддерживает. У него есть цена: списочные выборки по такому объекту невозможны - базе не к чему присоединять таблицы значений.

4. Поля UF в выборке ORM

final class OrderTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName(): string
{
return 'b_example_order';
}
public static function getUfId(): string
{
return 'EXAMPLE_ORDER'; // код сущности для пользовательских полей
}
public static function getMap(): array
{
return [ /* обычные поля */ ];
}
}
$rows = OrderTable::query()
->setSelect(['ID', 'UF_MANAGER_COMMENT'])
->where('UF_RATING', 5)
->addOrder('UF_RATING', 'DESC')
->fetchAll();
// в объектном стиле
$order = OrderTable::getByPrimary($id)->fetchObject();
echo $order->getUfManagerComment();

Один метод getUfId() включает всё: выборку, фильтрацию, сортировку и сгенерированные геттеры. Без него поля UF_* в запросе просто не появятся.

5. Согласия пользователей

Отдельный механизм для сбора согласий на обработку персональных данных, cookie и аналитику. Текст соглашения создают в админке, а запрос выводят компонентом:

$APPLICATION->IncludeComponent('bitrix:main.userconsent.request', '', [
'ID' => $agreementId,
'AUTO_SAVE' => 'N',
]);
// при AUTO_SAVE = N согласие сохраняют сами
\Bitrix\Main\UserConsent\Consent::addByContext($agreementId);

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

Справочник API

МетодНазначениеОсобенности
CUserTypeEntity::Add()создание полявозвращает ID или false; код обязан начинаться с UF_
CUserTypeEntity::Update()изменение метаданныхнельзя менять тип, сущность, код и множественность
CUserTypeEntity::Delete()удаление поляудаляет и все значения
UserFieldTableчтение метаданных в D7только чтение; изменения - через CUserTypeEntity
$USER_FIELD_MANAGER / CUserTypeManagerзначения полейUpdate, GetUserFieldValue, GetUserFields
UserFieldHelper::getInstance()->getManager()тот же менеджер в D7-стиле
CUserFieldEnum::SetEnumValues()варианты поля-спискаключи новых элементов начинаются с n
getUfId() в классе ORM-таблицывключает поля UF в выборкебез него UF_* не работают в select, filter, order
OnUserTypeBuildListрегистрация своего типа полякогда системных типов не хватает
UserConsent\Consent::addByContext()сохранение согласиянужен при AUTO_SAVE = N
bitrix:main.userconsent.requestкомпонент запроса согласияв своих формах подключают вручную

Типы полей: string, integer, double, boolean, date, datetime, enumeration, file, url, money, iblock_element, iblock_section, hlblock и другие; часть типов доступна только в коробочном Битрикс24.

Частые ошибки

Поле не распознаётся как пользовательское. Симптом: создали, а его нигде нет. Забыт префикс UF_ или код записан не заглавными латинскими буквами.

Попытка сменить тип поля. Симптом: после Update() связи значений разъехались. Тип, сущность, код и множественность неизменяемы после создания. Правильный путь - новое поле, перенос значений, удаление старого.

Значения списка не добавляются. Ключи новых элементов в SetEnumValues() обязаны начинаться с буквы n.

Ждут, что UserFieldTable запишет значение. Это класс для чтения метаданных. Определения меняет CUserTypeEntity, значения пишет менеджер полей.

UF_* не появляются в выборке ORM. В классе таблицы не определён getUfId().

Списочная выборка по «неподдерживаемому» объекту. Ручной подход через менеджер даёт чтение и запись значений, но не даёт списков - для них нужна ORM-сущность с getUfId().

Результат Add() не проверяют. Метод возвращает false при ошибке - например при дубликате кода, - и дальнейший код работает с несуществующим полем.

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

Чем пользовательские поля отличаются от свойств инфоблока?

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

Как отфильтровать выборку по полю UF_?

Определите в классе ORM-таблицы статический метод getUfId(), возвращающий код сущности. После этого поля UF_* ведут себя как обычные: их можно указывать в выборке, фильтре и сортировке, а ORM сам добавит соединения с таблицами значений. Без этого метода поля в запросе не появятся.

Можно ли повесить поле на объект, который их не поддерживает?

Да, поле регистрируется на произвольном коде сущности, а значения читаются и пишутся через менеджер полей. Ограничение существенное: списочные выборки по такому объекту не работают, потому что нет ORM-таблицы, к которой можно присоединить значения. Если нужны списки - заводите ORM-сущность с getUfId().

Как правильно изменить тип уже созданного поля?

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

Связанные темы

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