Пользовательские поля (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().
Как правильно изменить тип уже созданного поля?
Никак напрямую - тип, код, сущность и множественность фиксируются при создании. Безопасная последовательность: создать новое поле с нужным типом, перенести в него значения скриптом, убедиться, что старое поле нигде не используется, и только потом удалить его. Удаление определения удаляет и все значения.
Связанные темы
- Инфоблоки - свойства элементов инфоблока (не разделов)
- D7 ORM - выборки, в которых участвуют поля UF
- Highload-блоки - куда выносить пользовательские поля при больших объёмах
- Свойства инфоблоков - чем свойства отличаются от пользовательских полей
- Пользовательские поля на практике - решения по работе с полями из кода
- Пользовательское поле: заведение, вывод, типы - к чему крепится и как читается
- Раздел Инфоблоки и данные