Иногда задача формулируется очень просто: «добавь товар через API». Первая версия обычно занимает десять строк — создаём CIBlockElement, вызываем Add, получаем ID. А через день приходит сообщение: товар есть в админке, но карточка пустая, ссылка ведёт не туда или импорт тихо пропустил половину ошибок. Давайте сразу сделаем операцию так, чтобы её можно было проверить, повторить и поддерживать.
Ситуация: ID — это ещё не готовый результат
Элемент инфоблока — лишь одна часть пользовательского сценария. Для каталога могут быть важны символьный код, раздел, обязательные свойства, активность, картинка, цена и остаток. Метод CIBlockElement::Add действительно возвращает ID при успехе и false при ошибке, а текст причины лежит в LAST_ERROR. Поэтому нормальный критерий готовности состоит из двух вопросов: запись создана и потребитель этой записи видит ожидаемые данные.

Сначала формулируем контракт операции
Перед вызовом API полезно выписать, какие поля обязательны именно для нашего инфоблока. Это выглядит занудно до первой ошибки импорта, а после неё экономит часы. Не нужно создавать универсальный валидатор Bitrix: достаточно проверить входные данные, зафиксировать системные значения и вернуть диагностируемую ошибку вызывающему коду.
Ещё один пункт контракта — повторный запуск. Импорт может оборваться после ответа базы или до записи в журнал. Поэтому внешний идентификатор должен позволять отличить новую операцию от повтора. В проекте это обычно означает отдельную проверку существующей записи по внешнему ключу и явное правило: обновляем черновик, пропускаем готовую запись или останавливаемся с конфликтом. Сам Add за это правило не отвечает.
| Участок | Что фиксируем | Чем доказываем |
|---|---|---|
| Вход | name, внешний ID, категория, файлы | Валидация до вызова Bitrix и понятная ошибка для вызывающего кода |
| Элемент | IBLOCK_ID, NAME, CODE, ACTIVE | Массив $fields можно залогировать без секретов |
| Свойства | Какие свойства обязательны при первом сохранении | Они передаются в PROPERTY_VALUES или проверяются отдельно |
| Результат | ID, URL, видимость в нужной выборке | Контрольный запрос и тест пользовательского сценария |
Рабочий пример
Ниже пример для черновика товара. Я намеренно сохраняю элемент неактивным: пока импорт не завершил все обязательные действия, пользователю незачем видеть полуготовую карточку. Конкретные коды свойств и ID инфоблока должны быть вынесены в конфигурацию проекта, а не спрятаны в середине функции.
<?php
use Bitrix\Main\Loader;
const PRODUCT_IBLOCK_ID = 12;
function createProductDraft(array $input): int
{
if (!Loader::includeModule("iblock")) {
throw new RuntimeException("Модуль iblock не подключён");
}
$name = trim((string)($input["name"] ?? ""));
$code = trim((string)($input["code"] ?? ""));
if ($name === "" || $code === "") {
throw new InvalidArgumentException("Нужны NAME и CODE");
}
$element = new CIBlockElement();
$id = $element->Add([
"IBLOCK_ID" => PRODUCT_IBLOCK_ID,
"NAME" => $name,
"CODE" => $code,
"ACTIVE" => "N",
"PROPERTY_VALUES" => [
"EXTERNAL_ID" => (string)($input["externalId"] ?? ""),
"BRAND" => (int)($input["brandId"] ?? 0),
],
]);
if ($id === false) {
throw new RuntimeException($element->LAST_ERROR ?: "Не удалось создать элемент");
}
return (int)$id;
}
Почему свойства лучше не «доклеивать» вслепую
Для обязательных свойств, без которых объект не имеет смысла, удобнее передавать PROPERTY_VALUES в том же вызове Add. Метод SetPropertyValuesEx полезен, когда нужно сознательно обновить небольшую часть свойств: он не требует передавать полный набор и экономнее по запросам. Но он возвращает null, поэтому его нельзя использовать как удобный индикатор успеха. Если частичное обновление критично, его надо окружить собственным журналированием и контрольным чтением.
<?php
// Осознанное точечное изменение, а не «попробуем и забудем».
CIBlockElement::SetPropertyValuesEx(
$elementId,
PRODUCT_IBLOCK_ID,
["SYNC_STATUS" => "ready"]
);
// После важного изменения читаем нужное свойство в контрольном сценарии.
Четыре проверки после Add
- Проверяем, что вернулся положительный ID; при
falseсохраняемLAST_ERROR, входной внешний идентификатор и контекст операции. - Читаем элемент в том же инфоблоке и убеждаемся, что поля
NAME,CODEи нужные свойства действительно сохранены. - Проверяем публичную выборку с теми же фильтрами, которые использует компонент каталога: активность, даты, раздел, права, цена и остатки — если они участвуют в сценарии.
- Только после этого включаем элемент или помечаем импортированную запись как готовую.
Чего я бы не делал
- Не игнорировал бы результат
Addв надежде, что ошибка «сама попадёт в журнал». - Не делал бы элемент активным до заполнения зависимых данных.
- Не генерировал бы
CODEбез правила уникальности: два одинаковых названия неизбежно встретятся. - Не очищал бы весь кеш первым действием. Сначала нужно доказать, что проблема именно в кеше, а не в данных или фильтре.
Проверяемые источники
- CIBlockElement::Add — контракт метода, обработчики до и после записи, ID и LAST_ERROR
- CIBlockElement::SetPropertyValuesEx — точечное сохранение свойств и особенности пустых значений
- CIBlockElement::GetList — фильтры ACTIVE, ACTIVE_DATE и выборка полей элемента
Итог
Сам вызов CIBlockElement::Add несложен. Сложность в том, чтобы не потерять границу между «запись появилась» и «сценарий закончен». Если хранить контракт полей рядом с кодом, проверять LAST_ERROR и делать контрольную выборку, импорт перестаёт быть магией. А дальше уже можно спокойно добавлять цены, остатки и любые проектные правила.