{ "index": 360, "slug": "editorial-2018-01-practice-bitrix-elements", "title": "CIBlockElement::Add: как не потерять ошибку и проверить результат", "excerpt": "ID элемента ещё не означает готовую карточку. Разбираем контракт CIBlockElement::Add, ошибки LAST_ERROR, свойства, события и контрольную выборку, которая отделяет сохранённую запись от рабочего результата.", "contentHtml": "

В январе 2018 года на тестовом стенде интернет-магазина разработчик запускает импорт одного товара. Скрипт получает положительный ID от Bitrix, но ссылка на карточку не открывается, а в каталоге запись не появляется. Первое предположение — виноват кеш; повторная очистка его не подтверждает.

\n

Тезис: вызов CIBlockElement::Add завершает только операцию записи. Готовый результат требует ещё трёх проверок: ошибка не потеряна, обязательные данные сохранены, пользовательская выборка видит элемент. Ниже — учебный сценарий: его значения не выдают тестовый инфоблок за production-конфигурацию.

\n

Что происходит при добавлении

\n

Метод получает массив полей: IBLOCK_ID, NAME, CODE, активность, раздел и, при необходимости, PROPERTY_VALUES. Перед вставкой Bitrix вызывает обработчики OnBeforeIBlockElementAdd. Они могут изменить входные поля или отменить добавление. После успешной записи вызывается OnAfterIBlockElementAdd. Значит, одинаковый вызов в двух проектах может иметь разные результаты из-за обработчиков в init.php.

\n
\"Путь
ID — промежуточный результат. Рабочую карточку подтверждает контрольная выборка.
\n

При успехе объект возвращает положительный ID. При ошибке возвращается false, а причина лежит в LAST_ERROR. Нельзя молча продолжать импорт после false: сначала сохраните сообщение, внешний ключ и контекст вызова. Иначе следующая попытка смешает исходную ошибку с последствиями повтора.

\n

Контракт входных данных

\n

Определите правила именно вашего инфоблока. В учебном примере обязательны название и символьный код. Число 12, код свойства EXTERNAL_ID и имена входных полей условны. Они не описывают конфигурацию конкретного сайта.

\n

Внешний ключ нужен вызывающему коду. Импорт может оборваться после ответа базы или до записи в журнал. Если перед каждым повтором без проверки вызывать Add, система создаст несколько элементов с одним товаром. Идемпотентность — отдельное правило сервиса, а не свойство метода Bitrix.

\n
Контракт добавления учебного товара
УчастокЧто передаёмКак проверяем
ИдентификацияIBLOCK_ID и внешний ключИнфоблок известен, внешний ключ не пустой
ПоляNAME, CODE, ACTIVEНазвание и код прошли валидацию
СвойстваОбязательные значения в PROPERTY_VALUESКоды и типы значений совпадают со схемой
ПовторПравило для известного внешнего ключаПовтор обновляет, пропускает или останавливает операцию
\n

Рабочий пример: создаём черновик

\n

Черновик остаётся неактивным, пока зависимые данные не прошли проверку. Это не даёт промежуточной записи попасть в каталог. В конкретном проекте вместо исключений можно вернуть собственный результат импорта. Смысл примера в другом: каждый переход от входа к записи и от записи к проверке виден в коде.

\n
<?php\nCModule::IncludeModule(\"iblock\");\n\n$iblockId = 12;\n$externalId = \"demo-2018-001\";\n$fields = array(\n    \"IBLOCK_ID\" => $iblockId,\n    \"NAME\" => \"Тестовый товар\",\n    \"CODE\" => \"demo-2018-001\",\n    \"ACTIVE\" => \"N\",\n    \"PROPERTY_VALUES\" => array(\"EXTERNAL_ID\" => $externalId),\n);\n\n$duplicate = CIBlockElement::GetList(\n    array(),\n    array(\"IBLOCK_ID\" => $iblockId, \"PROPERTY_EXTERNAL_ID\" => $externalId),\n    false,\n    false,\n    array(\"ID\")\n);\nif ($duplicate->Fetch()) {\n    throw new RuntimeException(\"Внешний ID уже используется\");\n}\n\n$element = new CIBlockElement();\n$elementId = $element->Add($fields);\nif ($elementId === false) {\n    $message = trim($element->LAST_ERROR);\n    throw new RuntimeException($message !== \"\" ? $message : \"Ошибка Add\");\n}\n\n$stored = CIBlockElement::GetList(\n    array(),\n    array(\"IBLOCK_ID\" => $iblockId, \"ID\" => (int)$elementId),\n    false,\n    false,\n    array(\"ID\", \"NAME\", \"ACTIVE\", \"CODE\")\n);\n$storedFields = $stored->Fetch();\n$property = CIBlockElement::GetProperty(\n    $iblockId,\n    $elementId,\n    array(),\n    array(\"CODE\" => \"EXTERNAL_ID\")\n)->Fetch();\nif (!$storedFields || !$property || (string)$property[\"VALUE\"] !== $externalId) {\n    throw new RuntimeException(\"Контрольное чтение не подтвердило запись\");\n}\nreturn (int)$elementId;
\n

Сначала проверяется внешний ключ, затем запись создаётся неактивной. После Add поля читаются через GetList, а значение свойства — через GetProperty. Так скрипт различает три ситуации: дубль до записи, ошибка сохранения и неполный результат после положительного ID.

\n

Коды EXTERNAL_ID, IBLOCK_ID и символьный CODE должны существовать в конкретном инфоблоке. Если свойство множественное, проверяйте все строки результата GetProperty, а не только первый вызов Fetch. В старом проекте также проверьте, что модуль информационных блоков подключён до вызова классов.

\n

Контрольная выборка каталога

\n

Чтение по ID доказывает наличие записи, но не её видимость для покупателя. После проверки зависимых данных элемент переводят в активное состояние и повторяют фильтр каталога. В него входят только условия пользовательского сценария; CHECK_PERMISSIONS нужно согласовать с тем, от чьего имени выполняется проверка.

\n
<?php\n$public = CIBlockElement::GetList(\n    array(),\n    array(\n        \"IBLOCK_ID\" => $iblockId,\n        \"ID\" => (int)$elementId,\n        \"ACTIVE\" => \"Y\",\n        \"CHECK_PERMISSIONS\" => \"Y\",\n    ),\n    false,\n    false,\n    array(\"ID\", \"NAME\", \"CODE\")\n);\nif (!$public->Fetch()) {\n    throw new RuntimeException(\"Элемент не прошёл публичный фильтр\");\n}
\n

Для черновика с ACTIVE = N отсутствие строки в этой выборке ожидаемо. Ошибкой оно становится после явного перевода записи в активное состояние и проверки остальных условий: раздела, дат, прав, цены и остатка, если они участвуют в каталоге. Это момент, когда исходный симптом возвращается в тест: проверяется не только база, но и тот же фильтр, который использует пользовательская страница.

\n

Симптом → причина → проверка → действие

\n
Диагностика результата добавления
СимптомПричинаПроверкаДействие
false из методаПоле не прошло проверку или сработал обработчикСохранить LAST_ERROR и входной ключИсправить вход или правило; не повторять вслепую
ID есть, карточки нетЭлемент неактивен, не подходит фильтру или закрыт правамиЧтение по ID и повтор публичного фильтраРазделить запись и выдачу
Карточка пустаяСвойство не передали или перепутали кодПрочитать поля и свойства отдельноСверить схему и PROPERTY_VALUES
Появились дублиПовтор не проверяет внешний ключНайти существующий элемент до AddВыбрать обновление, пропуск или остановку
Данные меняются после AddОбработчик до добавления изменяет массивПроверить зарегистрированные событияОбщий инвариант оставить в обработчике
\n

Свойства: передать сразу или обновить отдельно

\n

Обязательные свойства лучше передать в PROPERTY_VALUES во время добавления. Точечное обновление полезно позже, когда нужно изменить один статус. Метод SetPropertyValuesEx может принять неполный набор: неуказанные свойства сохраняются. Это не становится доказательством готовности; после важного изменения выполните контрольное чтение.

\n
<?php\nCIBlockElement::SetPropertyValuesEx($elementId, $iblockId, array(\"SYNC_STATUS\" => \"ready\"));\n$status = CIBlockElement::GetProperty(\n    $iblockId,\n    $elementId,\n    array(),\n    array(\"CODE\" => \"SYNC_STATUS\")\n)->Fetch();\nif (!$status || $status[\"VALUE\"] !== \"ready\") {\n    throw new RuntimeException(\"Статус не подтверждён\");\n}
\n

Не передавайте неполный набор свойств, если проект трактует отсутствующее значение как очистку. Для многозначных, файловых и списочных свойств формат зависит от типа. Сначала сверяйте схему инфоблока и документацию.

\n

Граница событий

\n

OnBeforeIBlockElementAdd подходит для общего инварианта. Например, конкретный инфоблок не принимает пустой CODE из любого источника. Обработчик не должен содержать правила одной формы, сетевые вызовы и тяжёлую бизнес-логику. Иначе консольный импорт начнёт зависеть от скрытого поведения веб-приложения.

\n

Сервис отвечает за намерение операции: подготовить поля, проверить вход, обработать повтор и перевести LAST_ERROR в результат. Событие страхует общий барьер. Обработчик после добавления запускает побочные действия, но не должен создавать второй элемент при ошибке уведомления или индексации.

\n

Порядок действий

\n
  1. Назовите инфоблок, внешний ключ и обязательные поля. Запишите, что считается готовым элементом.
  2. Проверьте повтор по внешнему ключу до вызова Add.
  3. Подготовьте массив полей с явной активностью и свойствами.
  4. Вызовите Add. При false сохраните LAST_ERROR и остановите операцию.
  5. Прочитайте элемент по ID без публичных фильтров. Проверьте поля, раздел и свойства.
  6. Повторите выборку с фильтрами каталога: активность, даты, раздел, права, цена и остаток, если они входят в сценарий.
  7. Только после успешной контрольной выборки включите элемент или поставьте статус готовности.
  8. Если выборка не дала запись, не чистите кеш автоматически. Сравните фильтр с сохранёнными данными.
\n

Что не сработает

\n

Проверка только ID не ловит пустые свойства и ограничения публичной выборки. Игнорирование LAST_ERROR превращает понятную ошибку в пропавший товар. Очистка всего кеша маскирует неверный фильтр. Автоматическая транслитерация без проверки уникальности создаёт конфликт кодов. Повторный Add после таймаута создаёт дубль, если внешний ключ не участвует в решении.

\n

Ограничения и критерий готовности

\n

Статья использует учебный инфоблок, условный ID и условные коды свойств. Здесь нет измерений конкретного сайта и утверждений о его конфигурации. Реальный проект может добавлять документооборот, поиск, цены, остатки, права, кеш и обработчики. Каждый слой требует отдельной проверки. Документация Bitrix также содержит версионные оговорки для товарных возможностей GetList; при старой установке сверяйте поведение с версией модуля.

\n

Операция готова, когда другой инженер может повторить её на тестовом инфоблоке и получить четыре доказательства: положительный ID, сохранённые обязательные поля и свойства, отсутствие неожиданного дубля по внешнему ключу, запись в той же публичной выборке, которую использует пользовательский сценарий. При отрицательном результате готовность не объявляется.

\n

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

\n" }