За создание элементов инфоблока в 1С-Битрикс из PHP-кода отвечает метод CIBlockElement Add, если говорить точным вызовом - CIBlockElement::Add, а за редактирование уже существующих элементов - CIBlockElement::Update. Работаю с этой связкой в проектах на Битрикс регулярно: разовый импорт каталога, синхронизация остатков с 1С, обновление пунктов выдачи в инфоблоке доставки. API старое, из ветки d5, но именно оно чаще всего встречается в обработчиках обменов, консольных скриптах и агентах, потому что ведёт себя предсказуемо и не требует разбираться в ORM Bitrix Framework.
Что делает CIBlockElement::Add при создании элемента
Перед вызовом метода подключаю модуль инфоблоков - без этого класс CIBlockElement ещё не объявлен, и скрипт упадёт с фатальной ошибкой о неизвестном классе. Дальше создаётся объект, собирается массив полей и передаётся в Add. Метод возвращает ID нового элемента или false, если что-то пошло не так, и оба варианта нужно обрабатывать: при false в свойстве LAST_ERROR лежит текст причины, чаще всего это незаполненное обязательное поле или значение свойства, которое не проходит валидацию по типу.
<?php
CModule::IncludeModule("iblock");
$el = new CIBlockElement;
$fields = array(
"IBLOCK_ID" => 21,
"NAME" => "Тестовый товар",
"ACTIVE" => "Y",
"CODE" => "test-product",
"PREVIEW_TEXT" => "Короткое описание",
"DETAIL_TEXT" => "Полное описание товара",
"PROPERTY_VALUES" => array(
"ARTICLE" => "SKU-001",
"PRICE" => 1990,
),
);
$elementId = $el->Add($fields);
if ($elementId) {
echo "Создан элемент #" . $elementId;
} else {
echo "Ошибка добавления: " . $el->LAST_ERROR;
}
Если CODE не передать, а в настройках инфоблока включена транслитерация названия в код, Битрикс сформирует его сам из NAME. На проектах с обменом 1С всё равно передаю CODE и XML_ID явно - потом по ним проще найти элемент при повторном запуске импорта, не полагаясь на автогенерацию.
Обязательные и часто используемые поля при добавлении
Формально обязательны только IBLOCK_ID и NAME, остальные поля подставляются значениями по умолчанию. На практике для нормального каталога или справочника нужен более широкий набор.
| Поле | Что содержит |
| IBLOCK_ID | ID инфоблока, куда добавляется элемент, обязательное поле |
| NAME | Название элемента, обязательное поле |
| ACTIVE | Y или N, активность показа, по умолчанию Y |
| CODE | Символьный код для ЧПУ, при отсутствии формируется автоматически по настройкам инфоблока |
| IBLOCK_SECTION_ID | ID раздела или массив ID для привязки к нескольким разделам сразу |
| PREVIEW_TEXT, DETAIL_TEXT | краткое и полное описание, строкой или массивом с ключами TYPE и TEXT для html-формата |
| DATE_ACTIVE_FROM, DATE_ACTIVE_TO | период показа элемента на сайте |
| SORT | вес сортировки, по умолчанию 500 |
| XML_ID | внешний идентификатор, критичен для обменов и повторного импорта |
На XML_ID у меня держится вся логика upsert-импорта: если элемент с таким внешним ID уже есть, вызывается Update, если нет - Add. Без этого поля каждый повторный запуск импорта плодит дубли, и через пару недель регулярной синхронизации в инфоблоке накапливается мусор, который потом приходится вычищать вручную.
Свойства элемента через PROPERTY_VALUES
Ключом массива PROPERTY_VALUES может быть как числовой ID свойства, так и его символьный код. Код предпочтительнее: ID свойства зависит от порядка создания в конкретной базе и на деве, тесте и проде почти никогда не совпадает, а код свойства переносится вместе со структурой инфоблока и не ломается при выгрузке и загрузке между окружениями.
$fields["PROPERTY_VALUES"] = array(
"ARTICLE" => "SKU-002",
"COLOR" => array("Красный", "Синий"),
"MANUFACTURER" => array(
"VALUE" => "Производитель ООО Ромашка",
"DESCRIPTION" => "уточнено менеджером",
),
);
Для свойства с множественными значениями, например списка с флагом «множественное», значение передаётся простым массивом - как COLOR в примере выше. Для справочных и текстовых свойств, где нужно ещё и описание значения, используется вложенный массив с ключами VALUE и DESCRIPTION.
Бесплатный материал
🎁 Полезный скрипт в подарок
Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.
Без спама. Отписка в 1 клик.
CIBlockElement::Update - как редактировать существующий элемент
Update принимает ID элемента первым параметром и массив полей вторым. Из основных полей меняются только те, что перечислены в массиве, остальные остаются как были. А вот с PROPERTY_VALUES правило обратное, документация формулирует его прямо: «Если массив PROPERTY_VALUES задан, то он должен содержать полный набор значений свойств для данного элемента, т.е. если в нем будет отсутствовать одно из свойств, то все его значения для данного элемента будут удалены». Неполный PROPERTY_VALUES не дописывает одно свойство, а обнуляет все остальные. Ключа PROPERTY_VALUES в массиве нет, значит свойства Update не трогает вовсе.
<?php
CModule::IncludeModule("iblock");
$el = new CIBlockElement;
$fields = array(
"NAME" => "Товар после переименования",
);
$result = $el->Update(1348, $fields);
if ($result) {
CIBlockElement::SetPropertyValuesEx(1348, false, array("PRICE" => 2190));
echo "Элемент обновлён";
} else {
echo "Ошибка обновления: " . $el->LAST_ERROR;
}
Поле IBLOCK_ID в Update лучше вообще не передавать - метод не предназначен для переноса элемента в другой инфоблок. Если нужно переместить товар из одного каталога в другой, пересоздаю его через Add в целевом инфоблоке, переношу свойства и привязку к разделам, а исходный элемент удаляю через Delete.
Точечное обновление одного свойства без потери остальных
Когда нужно поменять всего одно свойство у тысяч элементов, например флаг наличия на складе после синхронизации с 1С, гонять через Update весь набор полей избыточно: метод пересохраняет элемент целиком, включая поисковый индекс и кеш-теги. Для точечных правок использую статические методы CIBlockElement::SetPropertyValueCode и SetPropertyValuesEx.
<?php
CModule::IncludeModule("iblock");
CIBlockElement::SetPropertyValueCode(1348, "IN_STOCK", "Y");
SetPropertyValueCode меняет одно свойство по символьному коду и не требует знать текущие значения остальных. SetPropertyValuesEx принимает сразу массив свойств для одного элемента и удобен, когда после разбора вебхука или строки из выгрузки 1С нужно обновить сразу несколько полей, но не трогать название и описание элемента.
Массовый импорт и обновление: на что смотрю в первую очередь
Пакетные скрипты запускаю через консоль или агент Битрикса, а не через браузер - там нет лимита времени выполнения PHP, заданного в настройках веб-сервера. Второе правило: не плодить дубли, перед Add всегда проверяю, нет ли уже элемента с таким XML_ID.
<?php
CModule::IncludeModule("iblock");
$externalId = "PVZ-77-001";
$existing = CIBlockElement::GetList(
array(),
array("IBLOCK_ID" => 34, "XML_ID" => $externalId),
false,
false,
array("ID")
)->Fetch();
$fields = array(
"IBLOCK_ID" => 34,
"NAME" => "Пункт выдачи, Красноярск, ул. Ленина, 10",
"XML_ID" => $externalId,
"ACTIVE" => "Y",
"PROPERTY_VALUES" => array(
"ADDRESS" => "г. Красноярск, ул. Ленина, 10",
),
);
$el = new CIBlockElement;
if ($existing) {
$updateFields = $fields;
unset($updateFields["IBLOCK_ID"], $updateFields["PROPERTY_VALUES"]);
$el->Update($existing["ID"], $updateFields);
CIBlockElement::SetPropertyValuesEx($existing["ID"], 34, $fields["PROPERTY_VALUES"]);
} else {
$el->Add($fields);
}
Такой upsert я использовал на проекте, где раз в сутки нужно было обновлять инфоблок пунктов выдачи: часть точек переезжает, часть закрывается, часть добавляется, и без сверки по XML_ID таблица за пару недель зарастала бы дублями. У Add есть ещё третий параметр bUpdateSearch: на большом импорте, тысячи элементов за один проход, временно отключаю его, чтобы не пересчитывать полнотекстовый индекс на каждой вставке, а переиндексирую каталог одним проходом уже после того, как импорт закончится.
Если нужно поднять с нуля не разовый скрипт, а весь каталог интернет-магазина на Битрикс с регулярной синхронизацией, обычно быстрее сразу заказать разработку интернет-магазина под ключ (у меня такие проекты стартуют от 80 000 ₽), чем растягивать точечные доработки на месяцы.
Типичные ошибки при работе с Add и Update
- Забыли CModule::IncludeModule(“iblock”) перед вызовом - класс CIBlockElement ещё не подключен, скрипт падает с фатальной ошибкой.
- Не проверили возвращаемое значение и LAST_ERROR - при false элемент просто не создался, а в логе импорта по этому поводу тишина.
- Передали в PROPERTY_VALUES ключ-ID свойства, снятый на другом окружении - на проде под этим ID может оказаться совсем другое свойство.
- Ждали, что Update с новым IBLOCK_ID перенесёт элемент в другой инфоблок - метод так не работает.
- Передали дату не в том формате, что настроен на сайте - дата «уезжает» на день из-за различий в разборе строки.
- Запустили импорт по XML_ID из двух процессов одновременно, например ручной запуск поверх работающего агента - оба не находят существующий элемент и создают дубль.
1С-Битрикс регулярно обновляется, поэтому перед внедрением я сверяюсь с актуальной документацией.
Частые вопросы
Чем CIBlockElement::Add отличается от Iblock\ElementTable в D7?
D7-API даёт ORM с событиями и удобной работой через сущности, а CIBlockElement - старое API из ядра d5, где методы возвращают ID или false и требуют ручной проверки LAST_ERROR. Для разовых скриптов обменов и агентов старое API проще и остаётся рабочим на всех текущих версиях, поэтому использую его там, где не нужна сложная бизнес-логика уровня ORM.
Можно ли перенести элемент в другой инфоблок через Update?
Надёжно через Update это не сделать - поле IBLOCK_ID в этом методе не предназначено для переноса. Для смены инфоблока элемент пересоздаю в целевом инфоблоке через Add и удаляю исходный через Delete, перенося свойства и привязки к разделам вручную.
Как обновить только одно свойство, не трогая остальные?
Использую CIBlockElement::SetPropertyValueCode с ID элемента, кодом свойства и новым значением. Метод не требует знать текущие значения остальных свойств и не пересохраняет название и описание элемента, что заметно быстрее на больших объёмах.
Обязательно ли подключать модуль iblock перед вызовом этих методов?
Да, без CModule::IncludeModule(“iblock”) класс CIBlockElement ещё не подключен, и вызов упадёт с фатальной ошибкой о неизвестном классе. Подключение нужно в каждом отдельном скрипте или агенте, где вызывается метод, даже если модуль уже включен в панели администратора.