8 lines
19 KiB
JSON
8 lines
19 KiB
JSON
{
|
||
"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><?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;</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><?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}</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><?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}</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>"
|
||
}
|