DarkRiDDeR9 мин

Bitrix API. Создаём элемент инфоблока так, чтобы ошибка не исчезла

BitrixPHPПрактика

Иногда задача формулируется очень просто: «добавь товар через 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

  1. Проверяем, что вернулся положительный ID; при false сохраняем LAST_ERROR, входной внешний идентификатор и контекст операции.
  2. Читаем элемент в том же инфоблоке и убеждаемся, что поля NAME, CODE и нужные свойства действительно сохранены.
  3. Проверяем публичную выборку с теми же фильтрами, которые использует компонент каталога: активность, даты, раздел, права, цена и остатки — если они участвуют в сценарии.
  4. Только после этого включаем элемент или помечаем импортированную запись как готовую.

Чего я бы не делал

  • Не игнорировал бы результат Add в надежде, что ошибка «сама попадёт в журнал».
  • Не делал бы элемент активным до заполнения зависимых данных.
  • Не генерировал бы CODE без правила уникальности: два одинаковых названия неизбежно встретятся.
  • Не очищал бы весь кеш первым действием. Сначала нужно доказать, что проблема именно в кеше, а не в данных или фильтре.

Проверяемые источники

Итог

Сам вызов CIBlockElement::Add несложен. Сложность в том, чтобы не потерять границу между «запись появилась» и «сценарий закончен». Если хранить контракт полей рядом с кодом, проверять LAST_ERROR и делать контрольную выборку, импорт перестаёт быть магией. А дальше уже можно спокойно добавлять цены, остатки и любые проектные правила.