Свойства инфоблока в Битрикс: что это и зачем их вообще трогать
Когда к нам приходит проект с каталогом на 30 000 SKU, первое, что мы разбираем — где проходит граница между полями элемента и свойствами инфоблока. Ошибка на этом этапе стоит дорого: её потом не исправить миграцией за один вечер, потому что от структуры хранения зависят фильтры, шаблоны, интеграция с 1С и модуль поиска. Поля элемента — это жёсткий каркас, который задан ядром: NAME, CODE, PREVIEW_TEXT, DETAIL_TEXT, ACTIVE, SORT, IBLOCK_SECTION_ID. Их нельзя расширить, не трогая ядро, и нельзя завести «поле бренд» — такого поля в Битрикс просто нет.
А вот бренд, размер, цвет, габариты, поставщик, серия, коллекция — всё это свойства. Каталог на 30k SKU без свойств не живёт: контент-менеджеру нужно фильтровать по бренду, менеджеру — выгружать в маркетплейс по поставщику, а покупателю — подбирать кроссовки по размеру. Каждое такое измерение — отдельное свойство со своим типом и настройками. Именно свойства дают то, чего не дают поля: расширяемость без правки кода и участие в умном фильтре. Ниже разберём, чем технически отличаются два способа хранить данные и как мы обычно делим их в проектах Paradigma.
| Критерий | Поле элемента | Свойство инфоблока |
|---|---|---|
| Где хранится | Таблица b_iblock_element |
Таблица b_iblock_element_property |
| Как фильтруется | Прямо в GetList по ключу |
Через PROPERTY_* или отдельный запрос |
| Добавить без правки кода | Нет — только миграция ядра | Да — через админку или API |
| Умный фильтр | Не участвует | Участвует штатно |
| Множественность | Нет | Да — флаг MULTIPLE |
В проектах Paradigma мы держим простое правило: то, что ищет пользователь и что приходит из 1С как атрибут товара, — свойство; то, что описывает сам элемент как запись, — поле. Название, символьный код, краткое и детальное описание, активность, привязка к разделу — это поля, их трогает только контент-менеджер. Бренд, размерная сетка, цвет, вес, страна производства, артикул поставщика — свойства, потому что по ним фильтруют и их передаёт обмен.
Граница не всегда очевидна. Цену мы не храним в свойстве — для неё есть CATALOG_PRICE и типы цен, иначе теряем скидки и пересчёт. Остатки — тоже не свойство, а CATALOG_QUANTITY. А вот «гарантия производителя» или «материал подошвы» — уже свойство: по ним может быть фильтр, и они не участвуют в торговых расчётах. Если сомневаетесь — спросите, будет ли по этому атрибуту фильтрация или выгрузка. Если да — свойство. Если это служебная метка для логики — возможно, хватит поля или отдельной таблицы, но это уже редкость.
Отдельно про множественность: у свойства можно включить MULTIPLE = Y, и тогда у товара будет несколько значений — например, несколько тегов или несколько совместимых моделей. У поля такой возможности нет. Это ещё один аргумент в пользу свойств там, где данные по природе своей множественные. Об этом ниже — в разделе про типы свойств, где разберём, какой тип под какую задачу брать.
Типы свойств инфоблока: какой выбрать под конкретную задачу
Типичный сценарий: контент-менеджер просит «сделать фильтр по цвету и материалу», вы открываете настройки свойства и видите выпадающий список из двух десятков типов. Выбираешь наугад — через месяц выясняется, что умный фильтр не подхватывает значения, а переделать на живой базе уже нельзя.
У нас в практике выбор типа — это решение на этапе проектирования инфоблока, а не «потом поправим». Смена типа не конвертирует данные автоматически: значения остаются в старой таблице, а новое свойство читается как пустое. Поэтому мы всегда сначала смотрим, какие задачи будут у свойства: только хранение и вывод, участие в умном фильтре, связь с другими сущностями или загрузка файлов.
Дальше — матрица типов и три группы задач, под которые они подбираются.
| Тип свойства | Под какую задачу | Где хранится значение | В умном фильтре | Особенности |
|---|---|---|---|---|
| Строка | Артикул, краткий текст | b_iblock_element_property |
Да (по подстроке) | Лимит длины, нет сортировки по алфавиту |
| Число | Вес, размер, цена-дубль | b_iblock_element_property |
Да (диапазон) | Хранится как строка, но фильтруется численно |
| Список | Цвет, материал, сезон | b_iblock_element_property + b_iblock_property_enum |
Да (фасетный) | Значения предзаданы, ID привязаны к свойству |
| Привязка к элементам | Похожие товары, аксессуары | b_iblock_element_property (ID элементов) |
Да | Тянет поля связанного элемента, риск рекурсии |
| Привязка к разделам | Доп. категории, теги-разделы | b_iblock_element_property (ID разделов) |
Да | Работает с любым инфоблоком, а не только своим |
| Файл | PDF-инструкции, чертежи | b_iblock_element_property (ID файла) |
Нет | Множественное — галерея, одиночное — один файл |
| Привязка к HL-блоку | Справочник брендов, регионов | b_iblock_element_property (ID записи HL) |
Ограниченно | Нужен отдельный HL-инфоблок и свойство-ключ |
| Справочник | Крупные классификаторы (МКТУ, ОКЕИ) | b_iblock_element_property |
Да | Требует модуля catalog, значения из HL |
Строка, число и список: базовые типы для каталога
Девяносто процентов свойств каталога закрываются тремя типами. Строка — для всего, что не участвует в числовых или фасетных операциях: артикул поставщика, краткое описание, код маркировки. Важно помнить: строка не сортируется по алфавиту в компонентах без явной настройки и плохо дружит с регистром — «Красный» и «красный» будут разными значениями.
Число берём везде, где нужен диапазонный фильтр: вес, габариты, мощность, гарантийный срок. Хранится оно строкой, но умный фильтр и сортировка приводят его к числу — поэтому в фильтре «от 5 до 10» работает корректно, а вот «5 кг» уже нет: суффикс сломает приведение. Единицы измерения держим отдельным свойством-списком.
Список — основной тип для фасетного фильтра: цвет, материал, сезон, пол. У него есть отдельная таблица значений b_iblock_property_enum, и именно по ней строятся счётчики «сколько товаров в каждой категории». Если пункты могут добавляться контент-менеджером на лету — берите список, а не строку, иначе фильтр превратится в мусор из опечаток.
Привязка к элементам и разделам: когда нужна вместо списка
Привязка к элементам нужна, когда значение свойства — это другой товар, а не абстрактная метка. Классика: «похожие товары», «аксессуары», «комплект», «аналоги». Список тут не подходит — он не умеет тянуть цену, картинку и ссылку связанного элемента. В шаблоне вы получаете PROPERTY_LINKED.ELEMENT с полным набором полей, и это экономит отдельный запрос.
Привязка к разделам решает другую задачу: когда товар нужно показать в нескольких категориях, но дублировать элемент не хочется. Например, «подарки до 3000» — это раздел, а не свойство-список. Компонент catalog.section умеет фильтровать по привязке к разделам, если включить её в настройках. Минус — при большом числе связей растёт нагрузка на выборку: у нас в проектах с активным импортом такие свойства мы индексируем отдельно.
Обе привязки требуют аккуратности при импорте: 1С присылает либо XML_ID, либо название, и маппинг делается вручную — об этом подробнее в секции про интеграцию.
Файл, привязка к HL-блоку и «Справочник»: сложные случаи
Файл — очевидный выбор для PDF-инструкций, сертификатов, чертежей и 3D-моделей. Работает и в одиночном, и в множественном режиме. Учтите: в умном фильтре файловые свойства не участвуют, а при импорте из 1С файлы часто приходят битыми ссылками — проверяйте PROPERTY_VALUE_ID перед выводом.
Привязка к HL-блоку — это когда справочник живёт отдельно от каталога и переиспользуется несколькими инфоблоками. Бренды, регионы доставки, поставщики. Свойство хранит ID записи HL, а саму сущность вы подтягиваете через HighloadBlockTable. Такой подход даёт единую точку правки: поменяли название бренда в HL — обновилось везде.
«Справочник» — отдельный тип, доступный при установленном модуле catalog. Он похож на привязку к HL, но заточен под большие классификаторы (МКТУ, ОКЕИ, коды ТН ВЭД) и умеет подсказки при вводе. В умном фильтре работает, но требует настройки фасетного индекса. Если классификатор небольшой и статичный — берите обычный список, «Справочник» здесь избыточен.
Смена типа свойства после запуска не конвертирует значения. Старые данные остаются в старой таблице в старом формате, а новое свойство читается как пустое. Умный фильтр при этом молча теряет часть товаров — внешне всё работает, но в выдаче пропадают позиции. Если тип всё-таки нужно поменять, заранее готовьте скрипт миграции значений и пересборку фасетного индекса.
Настройка свойств инфоблока через админку: пошагово
Контент-менеджеры чаще всего заводят свойства сами — когда нужно добавить новую характеристику в карточку товара, не дожидаясь разработчика. У нас в практике это регулярная ситуация: маркетинг просит «показывать на карточке страну производителя», и ждать спринт разработки никто не хочет. Хорошая новость в том, что 80% свойств в каталоге — это стандартные поля, которые можно создать за пять минут прямо в админке, не трогая код.
Из всей формы добавления свойства реально важны пять полей: код, название, тип, флаги поведения и — для списочных типов — сами значения списка. Всё остальное (сортировка, подсказки, привязка к разделам) настраивается позже и по потребности. Ниже — порядок действий, который мы показываем новым контент-менеджерам на онбординге.
- Открыть в админке
Контент → Инфоблоки, выбрать нужный инфоблок каталога, перейти на вкладку «Свойства» и нажать кнопку «Добавить свойство». - Заполнить код (только латиница,
snake_case, напримерcountry_of_origin), название (человеческое, для формы) и выбрать тип из списка. Код после создания менять нельзя — на нём завязаны шаблоны, фильтры и маппинг из 1С. - Выставить флаги поведения: множественное, обязательное, участвует в умном фильтре, показывать в списке. Каждый флаг потом можно переключить, но продумать заранее дешевле.
- Для типа «Список» — заполнить значения списка: каждому задать сортировку и символьный код (он же XML_ID, по нему значения матчатся при импорте из 1С).
- Сохранить и проверить результат: открыть любой товар этого инфоблока и убедиться, что новое поле появилось в форме редактирования.
В каталогах на 20k+ SKU у нас сложился свой дефолтный набор флагов. «Показывать в списке» включаем почти всегда — контент-менеджеру удобнее видеть характеристику в общем списке товаров, чем открывать карточку. «Участвует в умном фильтре» — только для свойств, по которым реально будет фильтрация: каждое такое свойство добавляет нагрузку на индекс фильтра, и десяток лишних флагов заметно замедляет страницы каталога.
А вот «обязательное» мы стараемся не ставить на свойства, которые приходят из импорта. Логика простая: если 1С по какой-то причине не передала значение, а свойство помечено обязательным, элемент не сохранится или импорт упадёт с ошибкой валидации. На каталоге в десятки тысяч позиций это превращается в массовый сбой обмена. Обязательность имеет смысл только для полей, которые заполняет человек руками — например, SEO-заголовок или ручная метка «Хит продаж».
Множественность тоже включаем осознанно: она удобна для тегов, материалов, комплектации, но ломает простые шаблоны вывода, если разработчик не ожидал массив вместо строки. Сомневаетесь — заводите одиночное свойство, а множественное добавляйте отдельным, когда задача действительно потребует.
Настройка свойств через API: CIBlockProperty::Add и Update
Распространённая ошибка — заводить свойства руками, когда их уже больше десятка. Представим типовую ситуацию: каталог разворачивается на новом окружении, шесть инфоблоков, в каждом по 6–8 свойств — цвет, материал, размерная сетка, сезонность, габариты, страна производства. Через админку это час кликов, и почти гарантированно где-то забудется флаг «Множественное» или привязка к разделу. А если между dev и prod нужно синхронизировать конфигурацию — руками это вообще не воспроизводимо.
Мы в таких проектах описываем свойства кодом и запускаем миграцию при развёртывании. CIBlockProperty::Add() и CIBlockProperty::Update() — старый, но живой API модуля iblock, который решает задачу за секунды и оставляет след в репозитории. Комбинируется с CIBlockPropertyEnum::Add() для списочных свойств и с проверкой по CODE, чтобы миграция была идемпотентной — можно гонять её повторно без дублей.
Как добавить свойство через CIBlockProperty::Add
Метод принимает один ассоциативный массив $arFields и возвращает ID созданного свойства или false с записью ошибки в $APPLICATION->GetException(). Обязательных полей немного: IBLOCK_ID, NAME, CODE, PROPERTY_TYPE. Остальное — SORT, MULTIPLE, IS_REQUIRED, USER_TYPE — имеет дефолты, но по опыту их лучше задавать явно: SORT по умолчанию 500, и если не проставить, свойства выстроятся в непредсказуемом порядке в форме редактирования.
Для списочного свойства (PROPERTY_TYPE = 'L') значения хранятся отдельно — их добавляют через CIBlockPropertyEnum::Add(), передавая полученный PROPERTY_ID. Порядок важен: сначала создаём свойство, потом наполняем список. Если поменяете порядок — получите свойство-сироту без значений.
<?php
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
$iblockId = 17; // замените на ваш IBLOCK_ID
$arFields = [
'IBLOCK_ID' => $iblockId,
'NAME' => 'Материал',
'CODE' => 'MATERIAL',
'PROPERTY_TYPE' => 'L', // список
'SORT' => 100,
'MULTIPLE' => 'N',
'IS_REQUIRED' => 'N',
'FILTRABLE' => 'Y',
'ACTIVE' => 'Y',
];
$ibp = new CIBlockProperty();
$propId = $ibp->Add($arFields);
if (!$propId) {
global $APPLICATION;
throw new \RuntimeException('Не удалось создать свойство: ' . $APPLICATION->GetException()->GetString());
}
foreach (['Хлопок' => 100, 'Лён' => 200, 'Шерсть' => 300] as $value => $sort) {
CIBlockPropertyEnum::Add([
'PROPERTY_ID' => $propId,
'VALUE' => $value,
'SORT' => $sort,
'DEF' => 'N',
]);
}Как обновить свойство, не потеряв значения
Обновление опаснее создания: CIBlockProperty::Update($ID, $arFields) перезаписывает переданные поля, а если случайно передать пустой PROPERTY_TYPE — тип сбросится. Значения элементов при этом не удаляются, но для списочных свойств есть нюанс: обновление PROPERTY_TYPE или USER_TYPE может «осиротить» привязки.
Мы всегда идём от CODE, а не от ID — ID на dev и prod разные, а CODE мы задаём сами и он стабилен. Схема: ищем существующее свойство через CIBlockProperty::GetList() с фильтром по IBLOCK_ID и CODE. Если нашли — Update по его ID, если нет — Add. Такой upsert безопасно запускать повторно.
<?php
$iblockId = 17;
$code = 'MATERIAL';
$existing = CIBlockProperty::GetList(
['SORT' => 'ASC'],
['IBLOCK_ID' => $iblockId, 'CODE' => $code]
)->Fetch();
$arFields = [
'NAME' => 'Материал (основной)',
'SORT' => 150,
'IS_REQUIRED' => 'Y',
'FILTRABLE' => 'Y',
];
$ibp = new CIBlockProperty();
if ($existing) {
// не трогаем PROPERTY_TYPE и CODE — только метаданные
$ok = $ibp->Update($existing['ID'], $arFields);
$propId = $existing['ID'];
} else {
$arFields['IBLOCK_ID'] = $iblockId;
$arFields['CODE'] = $code;
$arFields['PROPERTY_TYPE'] = 'L';
$propId = $ibp->Add($arFields);
}
if (!$ok && !$propId) {
throw new \RuntimeException('Ошибка синхронизации свойства ' . $code);
}Что ещё стоит держать в голове при настройке свойств инфоблока:
- Кеш инфоблока. После
AddилиUpdateсбрасывайте теги черезCIBlock::clearIBlockTagCache($iblockId)— иначе компонент отдаст старую конфигурацию. - Нет транзакции. Если упадёт добавление значений через
CIBlockPropertyEnum::Add, свойство останется созданным, но пустым — обрабатывайте откат вручную. - SORT по умолчанию 500. Все свойства без явного
SORTполучат одинаковый вес и выстроятся в произвольном порядке. - IBLOCK_ID обязателен. Без него
Addвернётfalse, аGetListвыдаст свойства со всех инфоблоков — сначала проверьте, что фильтр не пустой. - Компонент не увидит свойство без сброса кеша страницы и кеша компонента — на проде это неочевидный симптом «код отработал, а на сайте пусто».
Вывод свойств инфоблока в шаблоне компонента
Самая частая причина вопроса «почему в шаблоне не видно свойство» — оно туда просто не попало. Если кода свойства в этом списке нет, в шаблоне его тоже нет — сколько бы раз вы ни писали $arResult['PROPERTIES']['MY_CODE'] .
Второй нюанс: даже когда свойство выбрано, у него разная форма значения. Одиночное — строка или число в ['VALUE'], множественное — массив. Свойство типа «Список» хранит в ['VALUE'] ID варианта, а не текст. Ниже — рабочие паттерны для шаблона и для случаев, когда компонент использовать не хочется.
<?php
// Шаблон компонента bitrix:catalog.element.
// Свойства должны быть указаны в PROPERTY_CODE настроек компонента,
// иначе в $arResult['PROPERTIES'] их не будет.
$props = $arResult['PROPERTIES'];
// Одиночное свойство, например артикул производителя
if (!empty($props['ARTNUMBER']['VALUE'])) {
echo '<span class="artnumber">'
. htmlspecialcharsbx($props['ARTNUMBER']['VALUE'])
. '</span>';
}
// Множественное свойство, например галерея дополнительных изображений
if (!empty($props['GALLERY']['VALUE'])) {
echo '<ul class="gallery-extra">';
foreach ($props['GALLERY']['VALUE'] as $fileId) {
$img = CFile::ResizeImageGet(
$fileId,
['width' => 600, 'height' => 600],
BX_RESIZE_IMAGE_PROPORTIONAL,
true
);
echo '<li><img src="' . $img['src'] . '" alt=""></li>';
}
echo '</ul>';
}Как вывести значение списка, а не его ID
Свойство типа «Список» в ['VALUE'] отдаёт числовой ID варианта — например, 12 вместо «Красный». Компонент уже подставляет расшифровку в ['VALUE_ENUM'], если свойство попало в выборку. Для одиночного списка этого достаточно, для множественного — VALUE_ENUM тоже массив, и порядок совпадает с VALUE.
Если VALUE_ENUM пуст (бывает у свойств, подтянутых вручную, без флага GET_ENUM), берём значения через CIBlockPropertyEnum::GetList и строим карту ID → значение. Такой подход удобен, когда выводите сразу несколько списковых свойств и не хотите дёргать БД на каждый ID.
<?php
// 1. Быстрый путь: компонент уже отдал расшифровку
$colorEnum = $arResult['PROPERTIES']['COLOR']['VALUE_ENUM'] ?? null;
if ($colorEnum !== null) {
echo htmlspecialcharsbx($colorEnum);
}
// 2. Универсальный путь: собираем карту значений списка
$enumMap = [];
$rsEnum = CIBlockPropertyEnum::GetList(
['SORT' => 'ASC'],
['IBLOCK_ID' => $arResult['IBLOCK_ID'], 'CODE' => 'COLOR']
);
while ($enum = $rsEnum->Fetch()) {
$enumMap[$enum['ID']] = $enum['VALUE'];
}
// Множественное списковое свойство
if (!empty($arResult['PROPERTIES']['COLOR']['VALUE'])) {
$names = [];
foreach ((array)$arResult['PROPERTIES']['COLOR']['VALUE'] as $id) {
if (isset($enumMap[$id])) {
$names[] = $enumMap[$id];
}
}
echo implode(', ', array_map('htmlspecialcharsbx', $names));
}Как вывести свойства через CIBlockElement::GetProperty вне компонента
Когда свойства нужны не в шаблоне, а в своём скрипте — в ajax.php, в письме, в REST-ручке — компонент не нужен. Хватает CIBlockElement::GetProperty: передаёте ID инфоблока, ID элемента и фильтр по коду свойства. Для множественных свойств результат приходит несколькими строками, и их нужно аккуратно склеить.
<?php
$elementId = 12345;
$iblockId = 7; // замените на ваш IBLOCK_ID
$rsProp = CIBlockElement::GetProperty(
$iblockId,
$elementId,
['SORT' => 'ASC'],
['CODE' => 'MATERIAL'] // фильтр по коду свойства
);
$materials = [];
while ($prop = $rsProp->Fetch()) {
if ($prop['VALUE'] === '' || $prop['VALUE'] === false) {
continue;
}
// Для списков берём текст, для строк — значение
$materials[] = $prop['VALUE_ENUM'] ?: $prop['VALUE'];
}
if ($materials) {
echo 'Материалы: ' . implode(', ', array_map('htmlspecialcharsbx', $materials));
}print_r показывает свойства, а в проде пусто. Почти всегда виноват кеш компонента: правки в шаблоне или в настройках PROPERTY_CODE не подхватились, потому что кеш ещё жив. Во время разработки снимите галочку кеширования в настройках компонента или добавьте $arParams['CACHE_TYPE'] = 'N' в вызов. На проде — либо ждите истечения TTL, либо сбрасывайте кеш точечно через CBitrixComponent::clearComponentCache() или админку. И помните: если свойство не в списке PROPERTY_CODE, никакой сброс кеша его не покажет — сначала добавьте код, потом чистите.
Свойства инфоблока и интеграция с 1С: маппинг и подводные камни
В обмене с 1С свойства товара приходят не по символьному коду и не по названию, а по XML_ID — это уникальный идентификатор, который 1С хранит у себя в справочнике характеристик. Bitrix при импорте ищет свойство в инфоблоке именно по этому XML_ID, и если совпадения нет — значение просто не записывается. Никакой ошибки в логе, никакого предупреждения: товар обновился, а характеристика осталась пустой или сохранила старое значение.
В проектах с активным обменом мы регулярно видим эту картину. Контент-менеджеры жалуются, что «цвет пропал у половины каталога», а на деле разошёлся маппинг между справочником 1С и свойствами инфоблока. Проблема коварна тем, что проявляется не сразу: пока XML_ID совпадают, всё работает, и никто не задумывается о связи. Ломается она в момент, когда в 1С переименовали характеристику, добавили новую или пересобрали конфигурацию — и обмен молча теряет данные. Дальше разберём, как этот маппинг устроен, где его проверять и как подстелить соломки, чтобы импорт не затирал значения втихую.
Как сопоставить свойства 1С и Битрикс по XML_ID
Связь между свойством 1С и свойством инфоблока держится на одном поле — XML_ID в таблице b_iblock_property. Когда 1С присылает в XML узел характеристики, CommerceML-обработчик Битрикса ищет свойство с таким же XML_ID и пишет в него значение. Если не нашёл — создаёт новое (при включённой опции) либо игнорирует. Поэтому первое, что мы делаем при разборе «пропавших» характеристик, — выгружаем список свойств инфоблока с их XML_ID и сверяем с тем, что реально приходит в XML от 1С.
Сверить можно двумя способами: глазами через админку (в списке свойств XML_ID не виден — нужно открывать каждое) или скриптом. Второй вариант быстрее и не врёт. Типовой диагностический скрипт выгружает свойства инфоблока и сохраняет карту «XML_ID → CODE», чтобы потом сравнить её с выгрузкой из 1С.
Дальше — контроль на лету. Регистрируем обработчик на OnBeforeIBlockElementUpdate, который перед записью элемента проверяет: все ли XML_ID из пришедших свойств существуют в инфоблоке. Если нет — пишем в лог, а не молчим.
<?php
// init.php — контроль маппинга свойств при импорте из 1С
use Bitrix\Main\EventManager;
use Bitrix\Main\Diag\Debug;
EventManager::getInstance()->addEventHandler(
'iblock',
'OnBeforeIBlockElementUpdate',
'checkPropertyMapping'
);
function checkPropertyMapping(&$arFields)
{
$iblockId = (int)$arFields['IBLOCK_ID'];
// замените на ваш IBLOCK_ID каталога
if ($iblockId !== CATALOG_IBLOCK_ID) {
return true;
}
// карта XML_ID -> CODE для свойств инфоблока
$knownXmlIds = [];
$rs = \CIBlockProperty::GetList(
['ID' => 'ASC'],
['IBLOCK_ID' => $iblockId]
);
while ($prop = $rs->Fetch()) {
if ($prop['XML_ID'] !== '') {
$knownXmlIds[$prop['XML_ID']] = $prop['CODE'];
}
}
// свойства, пришедшие в обмене (упрощённо: из PROPERTY_VALUES)
$incoming = $arFields['PROPERTY_VALUES'] ?? [];
foreach ($incoming as $propId => $values) {
$rsProp = \CIBlockProperty::GetByID($propId);
$prop = $rsProp->Fetch();
if (!$prop) {
continue;
}
$xmlId = $prop['XML_ID'];
if ($xmlId !== '' && !isset($knownXmlIds[$xmlId])) {
Debug::writeToFile(
['PROPERTY_ID' => $propId, 'XML_ID' => $xmlId, 'ELEMENT' => $arFields['ID']],
'1c-mapping-mismatch',
'/local/logs/1c-mapping.log'
);
}
}
return true;
}Что делать, если 1С присылает свойство, которого нет в инфоблоке
Ситуация типовая: поставщик добавил в справочник новую характеристику, обмен пошёл, а свойства с таким XML_ID в инфоблоке нет. По умолчанию Битрикс либо создаст его автоматически (если в настройках обмена разрешено создание свойств), либо просто пропустит значение. Первый вариант опасен тем, что свойство создастся с случайным кодом и типом, и потом его придётся переделывать руками. Второй — тихой потерей данных.
Мы придерживаемся такого подхода: новые свойства из 1С не создавать автоматически, а складывать в отдельный лог и разбирать пачкой раз в неделю. Разработчик или контент-менеджер заводит свойство в инфоблоке вручную, задаёт корректный код, тип и XML_ID, и следующий обмен уже пишет в него значения. Так каталог не зарастает мусорными свойствами вроде «Характеристика_1С_12345».
Если поток новых характеристик большой и ручной разбор не тянет — делаем полуавтомат: обработчик читает XML, сравнивает с инфоблоком и присылает на почту отчёт «вот эти XML_ID не замаплены». Дальше решение всё равно за человеком.
| Свойство в 1С | Свойство в Битрикс | Симптом | Решение |
|---|---|---|---|
| Новая характеристика | Отсутствует | Значение не записалось | Завести свойство с тем же XML_ID |
| XML_ID изменён | Старый XML_ID | Значения затираются пустым | Обновить XML_ID свойства в инфоблоке |
| Тип «Справочник» | Тип «Строка» | Значение пишется как ID, не как текст | Сменить тип свойства на список |
| Множественное свойство | Одиночное | Сохраняется только первое значение | Включить «Множественное» |
| Свойство удалено в 1С | Свойство живо в Битрикс | Старые значения висят мёртвым грузом | Деактивировать или удалить свойство |
Свойства, которые 1С не должна трогать: SEO-тексты, теги, ручные метки
Самая частая жалоба от контент-менеджеров в проектах с активным обменом: «вчера всё заполнила, сегодня прихожу — описания пустые». Открываем лог импорта, смотрим, что происходило ночью. Ночной обмен из 1С прошёл, и вместе с ценами и остатками он перезаписал SEO_DESCRIPTION, SEO_TITLE, SEO_KEYWORDS и пару свойств, которые менеджеры заполняли руками неделями. В каталоге на 20 000+ SKU это превращается в регулярный откат работы целого отдела.
Причина всегда одна и та же: обмен не отличает «1С не передала значение» от «1С передала пустое значение». В XML-выгрузке тег отсутствует — модуль импорта трактует это как «очистить поле» и затирает то, что было. То же самое происходит с ручными метками вроде «Хит», «Новинка», «Снято с производства» — если эти статусы живут в свойствах инфоблока, а не в отдельном справочнике.
У нас на проектах это решается на уровне события OnBeforeIBlockElementUpdate: перехватываем обновление элемента, сверяем пришедшие значения с резервной таблицей и возвращаем защищённые свойства, если обмен прислал пустоту.
Ниже — рабочий обработчик и последовательность внедрения.
<?php
use Bitrix\Main\EventManager;
use Bitrix\Main\Application;
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
// Список инфоблоков и защищённых свойств
const PROTECTED_MAP = [
15 => ['SEO_TITLE', 'SEO_DESCRIPTION', 'SEO_KEYWORDS', 'MANUAL_TAG'],
16 => ['SEO_TITLE', 'SEO_DESCRIPTION', 'MANUAL_TAG'],
];
EventManager::getInstance()->addEventHandler(
'iblock',
'OnBeforeIBlockElementUpdate',
['ProtectedPropsHandler', 'onBeforeUpdate']
);
class ProtectedPropsHandler
{
public static function onBeforeUpdate(&$arFields): bool
{
$iblockId = (int)($arFields['IBLOCK_ID'] ?? 0);
if (!isset(PROTECTED_MAP[$iblockId]) || empty($arFields['ID'])) {
return true;
}
$elementId = (int)$arFields['ID'];
$protected = PROTECTED_MAP[$iblockId];
$connection = Application::getConnection();
$sqlHelper = $connection->getSqlHelper();
// Значения, которые пришли из импорта (могут быть пустыми)
$incoming = $arFields['PROPERTY_VALUES'] ?? [];
// Резервная таблица: element_id, property_code, value
$rows = $connection->query(
"SELECT PROPERTY_CODE, VALUE FROM b_protected_props
WHERE ELEMENT_ID = {$elementId}"
)->fetchAll();
$backup = [];
foreach ($rows as $row) {
$backup[$row['PROPERTY_CODE']] = $row['VALUE'];
}
foreach ($protected as $code) {
$newValue = $incoming[$code] ?? null;
$isEmpty = ($newValue === null || $newValue === '' || $newValue === false);
if ($isEmpty && isset($backup[$code])) {
// Возвращаем сохранённое значение
$arFields['PROPERTY_VALUES'][$code] = $backup[$code];
} elseif (!$isEmpty && $newValue !== ($backup[$code] ?? null)) {
// Значение изменилось не через импорт — обновляем бэкап
$connection->queryExecute(
"REPLACE INTO b_protected_props (ELEMENT_ID, PROPERTY_CODE, VALUE)
VALUES ({$elementId}, '{$sqlHelper->forSql($code)}',
'{$sqlHelper->forSql($newValue)}')"
);
}
}
return true;
}
}Порядок внедрения в проекте выглядит так:
- Определить список защищённых свойств — только те, что реально заполняются вручную, а не приходят из 1С.
- Завести таблицу бэкапа
b_protected_propsс полямиELEMENT_ID,PROPERTY_CODE,VALUEи индексом по элементу. - Повесить обработчик на
OnBeforeIBlockElementUpdateчерезEventManagerвinit.phpили в своём модуле. - Логировать каждое расхождение — когда импорт прислал пусто, а мы вернули значение из бэкапа, писать строку в
/local/logs/protected_props.log.
Самые частые ошибки при работе со свойствами инфоблока
Распространённая ошибка: считать, что свойства инфоблока — это «просто поля, которые заполняются в карточке». На деле это отдельный слой со своей типизацией, кешем, поведением при обмене и своей логикой в шаблоне. Мы собрали грабли, которые повторяются в проектах чаще всего — от смены типа свойства без конвертации до правки ядра вместо обработчика. Почти все они выглядят как «странный баг Битрикса», хотя это следствие одного неверного шага, сделанного раньше. Дальше — короткая сводка, а потом разберём, какие ошибки ловятся на этапе разработки, а какие всплывают только в проде через недели.
| Грабля | Причина | Как чинить |
|---|---|---|
| Смена типа без конвертации | Значения в БД остались в старом формате | Перезаписать значения через API или импорт |
Нет PROPERTY_CODE в компоненте |
Свойство не попало в выборку $arResult |
Добавить код в параметры catalog.section |
Чтение VALUE вместо VALUE_ENUM |
Для списков VALUE — это ID, а не текст |
Использовать VALUE_ENUM или GetEnumList |
| Правка ядра вместо обработчика | Хочется «быстро поправить» вывод свойства | Событие OnAfterIBlockElementUpdate / шаблон |
| Нет сброса кеша инфоблока | Изменения в БД не видны на фронте | CIBlock::clearIBlockTagCache() или ClearCache |
Часть ошибок вылезает сразу. Нет PROPERTY_CODE в параметрах компонента — свойство просто не появляется в $arResult['PROPERTIES'], и это видно на первой же отладке. То же с чтением VALUE вместо VALUE_ENUM для списочных свойств: вместо текста выводится числовой ID. Такие вещи отсеиваются на этапе разработки, если разработчик хоть раз открывает var_dump или xDebug.
А вот смена типа свойства и отсутствие сброса кеша коварнее. Смена типа без конвертации может не проявиться неделями: пока контент-менеджер не откроет карточку и не увидит пустое поле вместо значения. Данные в БД формально на месте, но в новом формате они нечитаемы, и «пропадают» ровно в тот момент, когда кто-то идёт их править.
Кеш — та же история: ClearCache при обновлении элемента через API не всегда срабатывает автоматически. Правка, сделанная скриптом, появится на фронте только после ручного сброса или истечения TTL. В проде это выглядит как «данные не сохраняются», хотя в админке всё на месте.
Отдельная категория — правка ядра. Мы регулярно видим проекты, где логика вывода свойства вшита прямо в /bitrix/modules/iblock/: это работает до первого обновления платформы. Правильный путь — обработчик события или кастомный шаблон, как мы разбирали в секции про вывод свойств.
Читайте также: Инфоблоки Битрикс: создание, вывод элементов, разделы и свойства, Как ускорить Битрикс: композит, кэширование и настройка сервера.
Подробнее об услуге: разработка корпоративных сайтов на Битрикс.
Частые вопросы
Можно ли задать свойству значение по умолчанию, которое подставится при создании элемента?
Да, у свойства есть поле DEFAULT_VALUE, но оно работает только для типов S (строка), N (число) и L (список) и подставляется лишь в форме редактирования элемента в админке. При добавлении через CIBlockElement::Add значение по умолчанию не применяется — его нужно передавать в массиве PROPERTY_VALUES явно.
Чем отличается свойство типа L (список) от свойства типа E (привязка к элементам)?
L хранит собственные значения в таблице b_iblock_property_enum и не зависит от других инфоблоков, а E ссылается на ID элементов другого инфоблока через LINK_IBLOCK_ID. Для E при удалении связанного элемента значение свойства остаётся «висячим», поэтому связи нужно чистить вручную или через агент.
Что делать, если после обновления через CIBlockProperty::Update значения свойства у элементов пропали?
Скорее всего вы сменили PROPERTY_TYPE или USER_TYPE — при этом старые значения в b_iblock_element_property становятся несовместимыми и перестают читаться. Перед сменой типа сделайте дамп таблицы и перенесите данные вручную, либо создайте новое свойство и скопируйте значения через CIBlockElement::SetPropertyValuesEx.
А если у меня свойство с множественностью (MULTIPLE=Y), как правильно вывести его в шаблоне без дублей?
В $arResult["PROPERTIES"]["CODE"]["VALUE"] будет массив, а не строка, поэтому используйте foreach и не забудьте про ключ ~VALUE для необработанного значения. Если нужно вывести через компонент, проверяйте, что в параметрах указан нужный код в PROPERTY_CODE, иначе массив будет пустым.
Можно ли запретить 1С перезаписывать конкретное свойство при обмене?
Да, в настройках свойства снимите галку «Участвует в обмене с 1С» или в XML-выгрузке не передавайте тег <Свойство> с этим кодом. Дополнительно можно повесить обработчик на событие OnBeforeIBlockElementUpdate и восстанавливать значение из резервной копии, если оно изменилось.
Чем отличается хранение значений в b_iblock_element_property от b_iblock_element_prop_sN?
Первая таблица используется для множественных свойств и свойств с типом F/E/G, вторая — для одиночных значений, где N — это ID инфоблока. Прямые запросы к prop_sN быстрее, но ломаются при смене ID инфоблока, поэтому в коде лучше работать через CIBlockElement::GetProperty.