diff --git a/editorial/agent-rewrites/359.json b/editorial/agent-rewrites/359.json index 712980c..b42df18 100644 --- a/editorial/agent-rewrites/359.json +++ b/editorial/agent-rewrites/359.json @@ -3,5 +3,5 @@ "slug": "editorial-2018-01-mechanism-bitrix-elements", "title": "CIBlockElement::Add в Bitrix: где проходит граница между событием и сервисом", "excerpt": "Разбираем путь создания элемента инфоблока: обработчики до и после Add, общий инвариант, диагностика LAST_ERROR и проверка результата без скрытой магии init.php.", - "contentHtml": "

Скрипт вызывает CIBlockElement::Add, получает ID и завершает работу. Через несколько минут элемент находится в админке, но каталог не показывает его, импорт не объясняет отказ, а повторный запуск создаёт дубль. Разработчик ищет проблему в шаблоне или кеше. На самом деле часть правил уже выполнил обработчик события, часть не выполнил никто.

\\n

Цена такой ошибки — не только одна неверная запись. Команда теряет исходные данные, повторяет операцию вслепую и рискует отправить наружу неполный товар. Если событие после добавления упало, ID уже существует. Если событие до добавления изменило поля, вызывающий код мог не знать об этом. Поэтому одного факта «метод вернул число» недостаточно.

\\n

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

\\n

Механизм: что происходит вокруг Add

\\n

Вызов начинается с массива полей. В нём обычно есть IBLOCK_ID, NAME, CODE, статус, раздел и PROPERTY_VALUES. Bitrix передаёт эти данные в обработчики события до добавления. Обработчик может изменить массив или отменить операцию с сообщением об ошибке.

\\n

Если проверка проходит, Bitrix записывает элемент и свойства. При успехе объект CIBlockElement возвращает ID. При ошибке он возвращает false, а причина доступна в LAST_ERROR. После записи вызывается событие OnAfterIBlockElementAdd. Это уже другой этап: элемент существует, даже если последующая реакция не завершилась.

\\n

Из этого следуют три разных результата. Первый — запись отклонена до сохранения. Второй — элемент сохранён, но зависимые действия ещё не завершены. Третий — элемент прошёл публичную выборку и готов для пользователя. Нельзя называть эти состояния одним словом «успех».

\\n
\"Путь
Схема разделяет запись элемента, общую защиту и проверку пользовательского результата. Учебная иллюстрация не показывает конкретную production-конфигурацию.
\\n

Где должно жить правило

\\n

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

\\n

OnBeforeIBlockElementAdd видит каждый подходящий вызов. Это хорошее место для инварианта, который нельзя нарушить ни из админки, ни из CLI, ни из endpoint. Например, для одного инфоблока всегда нужен непустой CODE. Но обработчик не знает, нужен ли конкретному импорту файл, цена или внешний HTTP-запрос. Эти правила относятся к вызывающей операции.

\\n

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

\\n
Распределение ответственности в жизненном цикле
УчастокЧто проверяетЧего не делает
СервисВход, намерение, конфигурацию, внешний ключ и понятный результатНе подменяет общий инвариант глобальным событием
OnBeforeIBlockElementAddОбязательное правило для данного инфоблокаНе выполняет тяжёлые HTTP-вызовы и правила одного экрана
AddСохраняет элемент и переданные свойстваНе обещает цену, остаток, URL и публичную видимость
OnAfterIBlockElementAddЗапускает реакцию после записи и фиксирует IDНе отменяет уже сохранённую запись чистым возвратом
Контрольная выборкаПроверяет поля, свойства и фильтры потребителяНе исправляет исходные данные автоматически
\\n

Минимальный предохранитель до записи

\\n

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

\\n
<?php\\n\\nconst PRODUCT_IBLOCK_ID = 12;\\n\\nAddEventHandler(\\n    \"iblock\",\\n    \"OnBeforeIBlockElementAdd\",\\n    [CatalogElementGuard::class, \"beforeAdd\"]\\n);\\n\\nfinal class CatalogElementGuard\\n{\\n    public static function beforeAdd(array &$fields): bool\\n    {\\n        if ((int)($fields[\"IBLOCK_ID\"] ?? 0) !== PRODUCT_IBLOCK_ID) {\\n            return true;\\n        }\\n\\n        if (trim((string)($fields[\"CODE\"] ?? \"\")) === \"\") {\\n            global $APPLICATION;\\n            $APPLICATION->ThrowException(\\n                \"Для элемента каталога нужен символьный код\"\\n            );\\n            return false;\\n        }\\n\\n        return true;\\n    }\\n}
\\n

Возврат false здесь означает отказ до записи. Вызывающий код всё равно обязан проверить результат Add и прочитать LAST_ERROR. Нельзя считать, что сообщение из обработчика автоматически попадёт в нужный журнал, ответ API или интерфейс администратора.

\\n

Сервис сохраняет контекст ошибки

\\n

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

\\n
<?php\\n\\nfunction addCatalogElement(array $input): int\\n{\\n    if (!\\Bitrix\\Main\\Loader::includeModule(\"iblock\")) {\\n        throw new RuntimeException(\"Модуль iblock не подключён\");\\n    }\\n\\n    $name = trim((string)($input[\"name\"] ?? \"\"));\\n    $code = trim((string)($input[\"code\"] ?? \"\"));\\n    $externalId = trim((string)($input[\"externalId\"] ?? \"\"));\\n\\n    if ($name === \"\" || $code === \"\" || $externalId === \"\") {\\n        throw new InvalidArgumentException(\\n            \"Нужны name, code и externalId\"\\n        );\\n    }\\n\\n    // Учебный пример: ID инфоблока и свойства заданы явно.\\n    $fields = [\\n        \"IBLOCK_ID\" => PRODUCT_IBLOCK_ID,\\n        \"NAME\" => $name,\\n        \"CODE\" => $code,\\n        \"ACTIVE\" => \"N\",\\n        \"PROPERTY_VALUES\" => [\\n            \"EXTERNAL_ID\" => $externalId,\\n        ],\\n    ];\\n\\n    $element = new CIBlockElement();\\n    $id = $element->Add($fields);\\n\\n    if ($id === false) {\\n        throw new RuntimeException(\\n            \"Не удалось добавить элемент \" . $externalId . \": \" .\\n            ($element->LAST_ERROR ?: \"причина не указана\")\\n        );\\n    }\\n\\n    return (int)$id;\\n}
\\n

Пример учебный. В нём нет транзакции внешней системы, блокировки от конкурентного импорта и политики повторов. В production эти решения зависят от хранилища и контракта источника. Код показывает только границу: ошибка Add становится явным исключением, а ID не выдаётся без проверки.

\\n

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

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

Свойства и отрицательный путь

\\n

PROPERTY_VALUES удобно передавать вместе с обязательными полями при создании. Но массив должен соответствовать реальным кодам и типам свойств. Множественное свойство, список, файл и связь с элементом имеют разные форматы. Учебный массив со строкой не доказывает, что такой же формат подходит вашему инфоблоку.

\\n

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

\\n

Отрицательный путь начинается там, где правило не выполняется. Нет внешнего ключа — операция останавливается до Bitrix. Нет обязательного CODE — сервис возвращает понятный отказ, а общий обработчик остаётся последней защитой. Add вернул false — запись не считается созданной. ID есть, но после-обработчик упал — запись считается созданной, а реакция получает отдельный статус. Не превращайте эти случаи в один повтор.

\\n

Порядок проверки

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

Ограничения

\\n

События Bitrix глобальны в пределах приложения. Один обработчик может влиять на админку, импорт и консольный скрипт. Поэтому широкое условие в init.php опаснее локальной проверки сервиса. Ограничивайте обработчик инфоблоком и фиксируйте его контракт рядом с регистрацией.

\\n

Успешный Add не проверяет весь каталог. Цена, остаток, торговое предложение, права, кеш, поиск и URL могут жить в других слоях. Не приписывайте методу эффект, которого он не обещает. Контрольная выборка должна повторять реальные фильтры компонента.

\\n

Проверка на тестовом инфоблоке не доказывает production-результат. Она подтверждает только выбранный сценарий и формат данных. Конкурентные импорты, права пользователя, версия Bitrix и конфигурация обработчиков требуют отдельных проверок. Если среда не позволяет их проверить, назовите это ограничением.

\\n

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

\\n

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

\\n

Если есть только ID из ответа, готовности нет. Если админка показывает элемент, но контрольный запрос каталога его исключает, готовности нет. Если событие после записи запускает побочный процесс, но его ошибка теряется, готовности нет. Эти отрицательные результаты не требуют очистки кеша или повторного Add; они указывают на следующий слой проверки.

\\n

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

\\n" -} \ No newline at end of file + "contentHtml": "

Скрипт вызывает CIBlockElement::Add, получает ID и завершает работу. Через несколько минут элемент находится в админке, но каталог его не показывает, импорт не объясняет отказ, а повторный запуск создаёт дубль. Причина часто не в шаблоне или кеше: часть правил скрыта в обработчиках событий, а часть не принадлежит этому вызову вовсе.

\n

Цена ошибки — не только одна неверная запись. Команда теряет исходные данные, повторяет операцию вслепую и рискует отправить наружу неполный товар. Если Add вернул ID, запись уже создана; ошибка последующей реакции не делает безопасным второй вызов. Если обработчик до записи изменил поля, вызывающий код мог не заметить это изменение. Поэтому число в результате — только одно доказательство.

\n

Граница проходит так: Add сохраняет элемент, сервис формирует намерение и диагностирует результат, обработчик до записи защищает общий инвариант, а обработчик после попытки передаёт результат отдельной реакции. Доступность в пользовательском сценарии подтверждается ещё одной выборкой с его реальными фильтрами.

\n

Механизм: что происходит вокруг Add

\n

Вызов начинается с массива полей. В нём обычно есть IBLOCK_ID, NAME, CODE, статус, раздел и PROPERTY_VALUES. Bitrix передаёт массив в обработчики до добавления по ссылке. Такой обработчик может изменить поля или отменить вставку, установив ошибку и вернув false.

\n

Если предварительная проверка не остановила операцию, метод добавляет элемент информационного блока. Свойства передаются через PROPERTY_VALUES; их ключом может быть числовой или символьный код, а формат значения зависит от типа свойства. При успехе CIBlockElement::Add возвращает ID, при ошибке — false, а текст доступен в LAST_ERROR.

\n

Здесь есть важная тонкость. OnAfterIBlockElementAdd вызывается после попытки добавления, в том числе когда обработчик OnBeforeIBlockElementAdd уже отменил вставку. В массиве обработчика нужно проверить RESULT: положительный ID означает созданный элемент, false — отказ; подробность ошибки передаётся в RESULT_MESSAGE. Только после этой проверки можно запускать реакцию для существующей записи.

\n
Путь создания элемента Bitrix: сервис, событие до попытки вставки, Add, проверка RESULT в событии после попытки и контрольная публичная выборка
Схема разделяет подготовку входа, попытку записи, диагностический обработчик и проверку пользовательского результата. Учебная иллюстрация не описывает конкретную production-конфигурацию.
\n

Где должно жить правило

\n

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

\n

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

\n

OnAfterIBlockElementAdd сначала должен разобрать результат попытки. При положительном RESULT он может записать ID в журнал, поставить фоновую задачу или обновить поиск. Если реакция после уже успешного Add не завершилась, повторяют реакцию по сохранённому ID, а не создают элемент заново. При отрицательном результате событие полезно для диагностики, но не должно изображать созданную запись.

\n
Распределение ответственности в жизненном цикле
УчастокЧто проверяетЧего не делает
СервисВход, намерение, конфигурацию, внешний ключ и понятный результатНе подменяет общий инвариант глобальным событием
OnBeforeIBlockElementAddОбязательное правило для данного инфоблокаНе выполняет правила одного импорта и тяжёлые HTTP-вызовы
AddПытается сохранить элемент и переданные свойстваНе обещает цену, остаток, URL и публичную видимость
OnAfterIBlockElementAddРазбирает RESULT и запускает реакцию только при созданной записиНе превращает неудачную попытку в успешную запись
Контрольная выборкаПроверяет поля, свойства и фильтры потребителяНе исправляет исходные данные автоматически
\n

Минимальный предохранитель до записи

\n

Ниже — учебный пример для одного инфоблока. Идентификатор, регистрация события и текст ошибки должны соответствовать вашей конфигурации. Предохранитель проверяет только общий инвариант: он не создаёт CODE из названия и не делает сетевой запрос.

\n
<?php\n\nconst PRODUCT_IBLOCK_ID = 12;\n\nAddEventHandler(\n    'iblock',\n    'OnBeforeIBlockElementAdd',\n    array('CatalogElementGuard', 'beforeAdd')\n);\n\nfinal class CatalogElementGuard\n{\n    public static function beforeAdd(array &$fields)\n    {\n        if ((int)($fields['IBLOCK_ID'] ?? 0) !== PRODUCT_IBLOCK_ID) {\n            return true;\n        }\n\n        if (trim((string)($fields['CODE'] ?? '')) === '') {\n            global $APPLICATION;\n            $APPLICATION->ThrowException(\n                'Для элемента каталога нужен символьный код'\n            );\n            return false;\n        }\n\n        return true;\n    }\n}
\n

Возврат false здесь означает отказ до вставки только вместе с исключением через ThrowException. Вызывающий код всё равно обязан проверить результат Add и прочитать LAST_ERROR. Сообщение обработчика не обязано автоматически попасть в нужный журнал, ответ API или интерфейс администратора.

\n

Сервис сохраняет контекст ошибки

\n

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

\n
<?php\n\nfunction addCatalogElement(array $input): int\n{\n    if (!\\Bitrix\\Main\\Loader::includeModule('iblock')) {\n        throw new RuntimeException('Модуль iblock не подключён');\n    }\n\n    $name = trim((string)($input['name'] ?? ''));\n    $code = trim((string)($input['code'] ?? ''));\n    $externalId = trim((string)($input['externalId'] ?? ''));\n\n    if ($name === '' || $code === '' || $externalId === '') {\n        throw new InvalidArgumentException(\n            'Нужны name, code и externalId'\n        );\n    }\n\n    // Учебный пример: ID инфоблока и свойства заданы явно.\n    $fields = array(\n        'IBLOCK_ID' => PRODUCT_IBLOCK_ID,\n        'NAME' => $name,\n        'CODE' => $code,\n        'ACTIVE' => 'N',\n        'PROPERTY_VALUES' => array(\n            'EXTERNAL_ID' => $externalId,\n        ),\n    );\n\n    $element = new CIBlockElement();\n    $id = $element->Add($fields);\n\n    if ($id === false) {\n        throw new RuntimeException(\n            'Не удалось добавить элемент ' . $externalId . ': ' .\n            ($element->LAST_ERROR ?: 'причина не указана')\n        );\n    }\n\n    return (int)$id;\n}
\n

Пример учебный. В нём нет транзакции внешней системы, блокировки от конкурентного импорта и политики повторов. Эти решения зависят от хранилища и контракта источника. Код показывает границу: ошибка Add становится явным исключением, а ID не выдаётся без проверки.

\n

После попытки Add: результат важнее названия события

\n

Имя OnAfterIBlockElementAdd легко прочитать как «после успешной записи», но это неточно. Обработчик вызывается после попытки и получает служебные поля результата. Поэтому его первая операция должна отделить два случая: RESULT содержит ID или равен false. В первом случае элемент можно читать и передавать в реакцию; во втором нужно работать с RESULT_MESSAGE, а не с несуществующей записью.

\n

Когда Add уже вернул ID, граница хранения пройдена. Сбой индексатора, журнала или фонового задания меняет статус реакции, но не превращает созданный элемент в новую задачу вставки. Для повторов сохраняйте внешний ключ, ID и состояние реакции. Иначе очередной запуск не отличит «запись уже есть, продолжить обработку» от «записи нет, создать».

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

Свойства и отрицательный путь

\n

PROPERTY_VALUES удобно передавать вместе с обязательными полями при создании. Но массив должен соответствовать реальным кодам и типам свойств. Множественное свойство, список, файл и связь с элементом имеют разные форматы. Строка для EXTERNAL_ID в учебном примере не доказывает, что такой формат подходит вашему инфоблоку.

\n

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

\n

Отрицательный путь начинается там, где правило не выполняется. Нет внешнего ключа — операция останавливается до Bitrix. Нет обязательного CODE — сервис возвращает понятный отказ, а общий обработчик остаётся последней защитой. Add вернул false — запись не считается созданной. RESULT содержит ID, но реакция упала — запись считается созданной, а реакция получает отдельный статус.

\n

Порядок проверки

\n
  1. Опишите источник операции: форма, импорт, консольный скрипт или конечная точка. Запишите внешний ключ и ожидаемое состояние элемента.
  2. Проверьте обязательные поля и свойства до вызова Bitrix. Отдельно зафиксируйте значения, которые может изменить обработчик.
  3. Найдите все обработчики OnBeforeIBlockElementAdd и OnAfterIBlockElementAdd для этого инфоблока. Для каждого запишите изменение, отказ и побочный эффект.
  4. Вызовите Add с минимальным массивом, который соответствует контракту инфоблока. Не делайте элемент активным до заполнения зависимых данных.
  5. Проверьте результат строго: положительный ID — успех записи, false — отказ. В обработчике после попытки дополнительно проверьте RESULT и RESULT_MESSAGE.
  6. Прочитайте элемент по ID без публичных ограничений. Проверьте поля, раздел и каждое обязательное свойство.
  7. Повторите запрос с фильтрами потребителя: активность, даты, права, раздел и товарные данные, если они участвуют в каталоге.
  8. Проверьте реакцию после записи по тому же ID. Если она не завершилась, поставьте отдельный статус и повторяйте реакцию, а не создание.
  9. Только после всех проверок переведите элемент в состояние, в котором его должен видеть пользователь.
\n

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

\n

События Bitrix глобальны в пределах приложения. Один обработчик может влиять на админку, импорт и консольный скрипт. Поэтому широкое условие в init.php опаснее локальной проверки сервиса. Ограничивайте обработчик инфоблоком и фиксируйте его контракт рядом с регистрацией.

\n

Успешный Add не проверяет весь каталог. Цена, остаток, торговое предложение, права, кеш, поиск и URL могут жить в других слоях. Не приписывайте методу эффект, которого он не обещает. Контрольная выборка должна повторять реальный фильтр компонента, иначе она докажет только чтение из другого сценария.

\n

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

\n

Если есть только ID из ответа, готовности нет. Если админка показывает элемент, а контрольный запрос каталога его исключает, готовности нет. Если событие вызвано после неудачной попытки, но код обработал его как успешную запись, готовности нет. Эти результаты указывают на следующий слой проверки, а не на необходимость повторного Add.

\n

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

\n" +}