1С Битрикс · 7 мин чтения

CIBlockElement Add и Update: создание и правка элементов инфоблока из кода

За создание элементов инфоблока в 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 ещё не подключен, и вызов упадёт с фатальной ошибкой о неизвестном классе. Подключение нужно в каждом отдельном скрипте или агенте, где вызывается метод, даже если модуль уже включен в панели администратора.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно