Files

8 lines
19 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 360,
"slug": "editorial-2018-01-practice-bitrix-elements",
"title": "CIBlockElement::Add: как не потерять ошибку и проверить результат",
"excerpt": "ID элемента ещё не означает готовую карточку. Разбираем контракт CIBlockElement::Add, ошибки LAST_ERROR, свойства, события и контрольную выборку, которая отделяет сохранённую запись от рабочего результата.",
"contentHtml": "<p>В январе 2018 года на тестовом стенде интернет-магазина разработчик запускает импорт одного товара. Скрипт получает положительный ID от Bitrix, но ссылка на карточку не открывается, а в каталоге запись не появляется. Первое предположение — виноват кеш; повторная очистка его не подтверждает.</p>\n<p><strong>Тезис:</strong> вызов <code>CIBlockElement::Add</code> завершает только операцию записи. Готовый результат требует ещё трёх проверок: ошибка не потеряна, обязательные данные сохранены, пользовательская выборка видит элемент. Ниже — учебный сценарий: его значения не выдают тестовый инфоблок за production-конфигурацию.</p>\n<h2>Что происходит при добавлении</h2>\n<p>Метод получает массив полей: <code>IBLOCK_ID</code>, <code>NAME</code>, <code>CODE</code>, активность, раздел и, при необходимости, <code>PROPERTY_VALUES</code>. Перед вставкой Bitrix вызывает обработчики <code>OnBeforeIBlockElementAdd</code>. Они могут изменить входные поля или отменить добавление. После успешной записи вызывается <code>OnAfterIBlockElementAdd</code>. Значит, одинаковый вызов в двух проектах может иметь разные результаты из-за обработчиков в <code>init.php</code>.</p>\n<figure><img src=\"/assets/editorial/2018/bitrix-catalog-workflow.png\" alt=\"Путь от входных данных к элементу инфоблока и контрольной проверке карточки\" /><figcaption>ID — промежуточный результат. Рабочую карточку подтверждает контрольная выборка.</figcaption></figure>\n<p>При успехе объект возвращает положительный ID. При ошибке возвращается <code>false</code>, а причина лежит в <code>LAST_ERROR</code>. Нельзя молча продолжать импорт после <code>false</code>: сначала сохраните сообщение, внешний ключ и контекст вызова. Иначе следующая попытка смешает исходную ошибку с последствиями повтора.</p>\n<h2>Контракт входных данных</h2>\n<p>Определите правила именно вашего инфоблока. В учебном примере обязательны название и символьный код. Число <code>12</code>, код свойства <code>EXTERNAL_ID</code> и имена входных полей условны. Они не описывают конфигурацию конкретного сайта.</p>\n<p>Внешний ключ нужен вызывающему коду. Импорт может оборваться после ответа базы или до записи в журнал. Если перед каждым повтором без проверки вызывать <code>Add</code>, система создаст несколько элементов с одним товаром. Идемпотентность — отдельное правило сервиса, а не свойство метода Bitrix.</p>\n<table><caption>Контракт добавления учебного товара</caption><thead><tr><th scope=\"col\">Участок</th><th scope=\"col\">Что передаём</th><th scope=\"col\">Как проверяем</th></tr></thead><tbody><tr><td>Идентификация</td><td><code>IBLOCK_ID</code> и внешний ключ</td><td>Инфоблок известен, внешний ключ не пустой</td></tr><tr><td>Поля</td><td><code>NAME</code>, <code>CODE</code>, <code>ACTIVE</code></td><td>Название и код прошли валидацию</td></tr><tr><td>Свойства</td><td>Обязательные значения в <code>PROPERTY_VALUES</code></td><td>Коды и типы значений совпадают со схемой</td></tr><tr><td>Повтор</td><td>Правило для известного внешнего ключа</td><td>Повтор обновляет, пропускает или останавливает операцию</td></tr></tbody></table>\n<h2>Рабочий пример: создаём черновик</h2>\n<p>Черновик остаётся неактивным, пока зависимые данные не прошли проверку. Это не даёт промежуточной записи попасть в каталог. В конкретном проекте вместо исключений можно вернуть собственный результат импорта. Смысл примера в другом: каждый переход от входа к записи и от записи к проверке виден в коде.</p>\n<pre><code>&lt;?php\nCModule::IncludeModule(\"iblock\");\n\n$iblockId = 12;\n$externalId = \"demo-2018-001\";\n$fields = array(\n \"IBLOCK_ID\" =&gt; $iblockId,\n \"NAME\" =&gt; \"Тестовый товар\",\n \"CODE\" =&gt; \"demo-2018-001\",\n \"ACTIVE\" =&gt; \"N\",\n \"PROPERTY_VALUES\" =&gt; array(\"EXTERNAL_ID\" =&gt; $externalId),\n);\n\n$duplicate = CIBlockElement::GetList(\n array(),\n array(\"IBLOCK_ID\" =&gt; $iblockId, \"PROPERTY_EXTERNAL_ID\" =&gt; $externalId),\n false,\n false,\n array(\"ID\")\n);\nif ($duplicate-&gt;Fetch()) {\n throw new RuntimeException(\"Внешний ID уже используется\");\n}\n\n$element = new CIBlockElement();\n$elementId = $element-&gt;Add($fields);\nif ($elementId === false) {\n $message = trim($element-&gt;LAST_ERROR);\n throw new RuntimeException($message !== \"\" ? $message : \"Ошибка Add\");\n}\n\n$stored = CIBlockElement::GetList(\n array(),\n array(\"IBLOCK_ID\" =&gt; $iblockId, \"ID\" =&gt; (int)$elementId),\n false,\n false,\n array(\"ID\", \"NAME\", \"ACTIVE\", \"CODE\")\n);\n$storedFields = $stored-&gt;Fetch();\n$property = CIBlockElement::GetProperty(\n $iblockId,\n $elementId,\n array(),\n array(\"CODE\" =&gt; \"EXTERNAL_ID\")\n)-&gt;Fetch();\nif (!$storedFields || !$property || (string)$property[\"VALUE\"] !== $externalId) {\n throw new RuntimeException(\"Контрольное чтение не подтвердило запись\");\n}\nreturn (int)$elementId;</code></pre>\n<p>Сначала проверяется внешний ключ, затем запись создаётся неактивной. После <code>Add</code> поля читаются через <code>GetList</code>, а значение свойства — через <code>GetProperty</code>. Так скрипт различает три ситуации: дубль до записи, ошибка сохранения и неполный результат после положительного ID.</p>\n<p>Коды <code>EXTERNAL_ID</code>, <code>IBLOCK_ID</code> и символьный <code>CODE</code> должны существовать в конкретном инфоблоке. Если свойство множественное, проверяйте все строки результата <code>GetProperty</code>, а не только первый вызов <code>Fetch</code>. В старом проекте также проверьте, что модуль информационных блоков подключён до вызова классов.</p>\n<h2>Контрольная выборка каталога</h2>\n<p>Чтение по ID доказывает наличие записи, но не её видимость для покупателя. После проверки зависимых данных элемент переводят в активное состояние и повторяют фильтр каталога. В него входят только условия пользовательского сценария; <code>CHECK_PERMISSIONS</code> нужно согласовать с тем, от чьего имени выполняется проверка.</p>\n<pre><code>&lt;?php\n$public = CIBlockElement::GetList(\n array(),\n array(\n \"IBLOCK_ID\" =&gt; $iblockId,\n \"ID\" =&gt; (int)$elementId,\n \"ACTIVE\" =&gt; \"Y\",\n \"CHECK_PERMISSIONS\" =&gt; \"Y\",\n ),\n false,\n false,\n array(\"ID\", \"NAME\", \"CODE\")\n);\nif (!$public-&gt;Fetch()) {\n throw new RuntimeException(\"Элемент не прошёл публичный фильтр\");\n}</code></pre>\n<p>Для черновика с <code>ACTIVE = N</code> отсутствие строки в этой выборке ожидаемо. Ошибкой оно становится после явного перевода записи в активное состояние и проверки остальных условий: раздела, дат, прав, цены и остатка, если они участвуют в каталоге. Это момент, когда исходный симптом возвращается в тест: проверяется не только база, но и тот же фильтр, который использует пользовательская страница.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><caption>Диагностика результата добавления</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td><code>false</code> из метода</td><td>Поле не прошло проверку или сработал обработчик</td><td>Сохранить <code>LAST_ERROR</code> и входной ключ</td><td>Исправить вход или правило; не повторять вслепую</td></tr><tr><td>ID есть, карточки нет</td><td>Элемент неактивен, не подходит фильтру или закрыт правами</td><td>Чтение по ID и повтор публичного фильтра</td><td>Разделить запись и выдачу</td></tr><tr><td>Карточка пустая</td><td>Свойство не передали или перепутали код</td><td>Прочитать поля и свойства отдельно</td><td>Сверить схему и <code>PROPERTY_VALUES</code></td></tr><tr><td>Появились дубли</td><td>Повтор не проверяет внешний ключ</td><td>Найти существующий элемент до <code>Add</code></td><td>Выбрать обновление, пропуск или остановку</td></tr><tr><td>Данные меняются после Add</td><td>Обработчик до добавления изменяет массив</td><td>Проверить зарегистрированные события</td><td>Общий инвариант оставить в обработчике</td></tr></tbody></table>\n<h2>Свойства: передать сразу или обновить отдельно</h2>\n<p>Обязательные свойства лучше передать в <code>PROPERTY_VALUES</code> во время добавления. Точечное обновление полезно позже, когда нужно изменить один статус. Метод <code>SetPropertyValuesEx</code> может принять неполный набор: неуказанные свойства сохраняются. Это не становится доказательством готовности; после важного изменения выполните контрольное чтение.</p>\n<pre><code>&lt;?php\nCIBlockElement::SetPropertyValuesEx($elementId, $iblockId, array(\"SYNC_STATUS\" =&gt; \"ready\"));\n$status = CIBlockElement::GetProperty(\n $iblockId,\n $elementId,\n array(),\n array(\"CODE\" =&gt; \"SYNC_STATUS\")\n)-&gt;Fetch();\nif (!$status || $status[\"VALUE\"] !== \"ready\") {\n throw new RuntimeException(\"Статус не подтверждён\");\n}</code></pre>\n<p>Не передавайте неполный набор свойств, если проект трактует отсутствующее значение как очистку. Для многозначных, файловых и списочных свойств формат зависит от типа. Сначала сверяйте схему инфоблока и документацию.</p>\n<h2>Граница событий</h2>\n<p><code>OnBeforeIBlockElementAdd</code> подходит для общего инварианта. Например, конкретный инфоблок не принимает пустой <code>CODE</code> из любого источника. Обработчик не должен содержать правила одной формы, сетевые вызовы и тяжёлую бизнес-логику. Иначе консольный импорт начнёт зависеть от скрытого поведения веб-приложения.</p>\n<p>Сервис отвечает за намерение операции: подготовить поля, проверить вход, обработать повтор и перевести <code>LAST_ERROR</code> в результат. Событие страхует общий барьер. Обработчик после добавления запускает побочные действия, но не должен создавать второй элемент при ошибке уведомления или индексации.</p>\n<h2>Порядок действий</h2>\n<ol><li>Назовите инфоблок, внешний ключ и обязательные поля. Запишите, что считается готовым элементом.</li><li>Проверьте повтор по внешнему ключу до вызова <code>Add</code>.</li><li>Подготовьте массив полей с явной активностью и свойствами.</li><li>Вызовите <code>Add</code>. При <code>false</code> сохраните <code>LAST_ERROR</code> и остановите операцию.</li><li>Прочитайте элемент по ID без публичных фильтров. Проверьте поля, раздел и свойства.</li><li>Повторите выборку с фильтрами каталога: активность, даты, раздел, права, цена и остаток, если они входят в сценарий.</li><li>Только после успешной контрольной выборки включите элемент или поставьте статус готовности.</li><li>Если выборка не дала запись, не чистите кеш автоматически. Сравните фильтр с сохранёнными данными.</li></ol>\n<h2>Что не сработает</h2>\n<p>Проверка только ID не ловит пустые свойства и ограничения публичной выборки. Игнорирование <code>LAST_ERROR</code> превращает понятную ошибку в пропавший товар. Очистка всего кеша маскирует неверный фильтр. Автоматическая транслитерация без проверки уникальности создаёт конфликт кодов. Повторный <code>Add</code> после таймаута создаёт дубль, если внешний ключ не участвует в решении.</p>\n<h2>Ограничения и критерий готовности</h2>\n<p>Статья использует учебный инфоблок, условный ID и условные коды свойств. Здесь нет измерений конкретного сайта и утверждений о его конфигурации. Реальный проект может добавлять документооборот, поиск, цены, остатки, права, кеш и обработчики. Каждый слой требует отдельной проверки. Документация Bitrix также содержит версионные оговорки для товарных возможностей <code>GetList</code>; при старой установке сверяйте поведение с версией модуля.</p>\n<p>Операция готова, когда другой инженер может повторить её на тестовом инфоблоке и получить четыре доказательства: положительный ID, сохранённые обязательные поля и свойства, отсутствие неожиданного дубля по внешнему ключу, запись в той же публичной выборке, которую использует пользовательский сценарий. При отрицательном результате готовность не объявляется.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/add.php?print=Y\" target=\"_blank\" rel=\"noopener\">CIBlockElement::Add</a> — параметры, <code>PROPERTY_VALUES</code>, события, ID и <code>LAST_ERROR</code>.</li><li><a href=\"https://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/getlist.php\" target=\"_blank\" rel=\"noopener\">CIBlockElement::GetList</a> — контрольная выборка, активность и фильтры прав.</li><li><a href=\"https://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/getproperty.php\" target=\"_blank\" rel=\"noopener\">CIBlockElement::GetProperty</a> — чтение фактических значений свойств.</li><li><a href=\"https://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/setpropertyvaluesex.php\" target=\"_blank\" rel=\"noopener\">CIBlockElement::SetPropertyValuesEx</a> — точечное изменение свойств.</li></ul>"
}