DarkRiDDeR11 мин

Bitrix API. Карточка открывает не тот товар: проверяем конфликт CODE

BitrixPHPДиагностика

Есть неприятная ошибка, которую легко принять за кеш: открываешь карточку товара, а видишь другой товар с похожим названием. Особенно странно это выглядит после импорта — обе записи есть в админке, у обеих нормальные картинки, а URL одной вдруг показывает соседнюю. Здесь не нужно начинать с очистки кеша. Главный вопрос: как доказать конфликт CODE или широкий фильтр до того, как менять данные? Цена ошибки — исправить правильную запись и получить новый конфликт.

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

Не путать симптом и причину

Похожее название не доказывает конфликт. В одном каталоге может быть несколько позиций «Classic 250 г» в разных разделах, и тогда адрес обязан содержать достаточный контекст. Наоборот, разные названия могут получить одинаковый код после нормализации. Диагностику начинаю с конкретного сломанного адреса и ID товара, который ожидали увидеть. Только потом читаю список элементов по фактическому ELEMENT_CODE.

Дерево диагностики неправильной карточки: путь, переменная ELEMENT_CODE, число совпадений GetList и дальнейшие действия
Сначала считаем набор совпадений. Кеш проверяем только после пути и данных.

Какие данные собрать до исправления

ФактЗачем он нуженКак зафиксировать
Исходный URLПоказывает, что реально запросил браузерСохранить полный путь из адресной строки или access-лога
Ожидаемый IDНе даёт спорить о том, какая запись считается правильнойВзять ID из админки или из результата импорта
ELEMENT_CODEСвязывает путь с данными компонентаВывести переменную после разбора ЧПУ на тестовом стенде
Все записи по CODEОтличает один результат от конфликтаСделать ограниченный GetList в том же инфоблоке
Фильтр деталиОбъясняет, почему часть записей исключена или выбранаСверить с параметрами и кодом конкретного компонента

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

Документация CIBlockElement::GetList позволяет явно задать сортировку, фильтр, ограничение и набор полей. Для диагностики беру только те поля, которые помогают отличить записи: ID, имя, CODE, основной раздел и шаблон детального URL. Запрос не должен случайно тянуть свойства всего каталога: его задача — показать размер набора и порядок элементов.

<?php

function findActiveElementsByCode($iblockId, $code)
{
    $result = CIBlockElement::GetList(
        array("ID" => "ASC"),
        array(
            "IBLOCK_ID" => (int)$iblockId,
            "=CODE" => $code,
            "ACTIVE" => "Y",
        ),
        false,
        array("nTopCount" => 20),
        array("ID", "NAME", "CODE", "IBLOCK_SECTION_ID", "DETAIL_PAGE_URL")
    );

    $items = array();
    while ($item = $result->GetNext()) {
        $items[] = $item;
    }

    return $items;
}

$items = findActiveElementsByCode(12, "classic-250-g");
if (count($items) !== 1) {
    throw new RuntimeException("Нужно разобрать " . count($items) . " совпадений");
}

Сортировка по ID в этом примере нужна не для выбора «правильного» товара, а для повторяемого вывода. Если там два элемента, проблема уже доказана: детальный компонент не должен случайно решать, что меньший ID важнее. Дальше либо сужаем фильтр контекстом раздела, либо исправляем один из кодов по заранее выбранному правилу.

Как отличить три разных случая

Результат проверкиЧто это значитСледующий шаг
0 совпаденийURL разобран, но элемент не проходит базовый фильтрПроверить значение переменной, активность, инфоблок и шаблон ссылки
1 совпадение, ID правильныйДанные и базовый фильтр совпалиСравнить дополнительные условия компонента и только затем кеш
1 совпадение, ID другойПеременная из URL не соответствует ожидаемому товаруПроверить генерацию URL, шаблон и исходный CODE элемента
2 и более совпаденийФильтр недостаточно точный или коды конфликтуютРешить, нужен ли контекст раздела, затем изменить конфликтующие данные

Проверяем, что компонент получил из URL

Не нужно угадать имя переменной по шаблону. Комплексный компонент разбирает путь через CComponentEngine::ParseComponentPath и возвращает переменные, восстановленные из маркеров. Для проблемной ссылки полезно на тестовой копии вывести $page и $arVariables. Так видно, не потерялся ли раздел и действительно ли ELEMENT_CODE равен строке из адреса.

<?php

$templates = array(
    "detail" => "#SECTION_CODE#/#ELEMENT_CODE#/",
);
$variables = array();
$page = CComponentEngine::ParseComponentPath(
    "/catalog/",
    $templates,
    $variables,
    "/catalog/kofe/classic-250-g/"
);

if ($page !== "detail") {
    throw new RuntimeException("Не найден шаблон detail");
}

error_log(print_r($variables, true));

Если в массиве нет SECTION_CODE, а детальный запрос должен учитывать раздел, коды элементов могут быть вполне корректны. Ошибка будет в URL-шаблоне или в логике компонента, который не применяет восстановленную переменную. И наоборот: если переменные верны, а GetList возвращает несколько записей, искать надо в данных и условиях выборки, не в роутинге.

Исправление без потери истории

Когда конфликт подтверждён, сначала выбираю правило для нового адреса: суффикс, артикул или раздел. Затем сохраняю старый URL и список мест, которые на него ссылаются. Смена CODE меняет адрес, поэтому публикацию лучше выполнять отдельным шагом с проверкой ссылок. Метод CIBlockElement::Update возвращает результат изменения; при ошибке не пропускаем LAST_ERROR.

<?php

$element = new CIBlockElement();
$updated = $element->Update($duplicateId, array(
    "CODE" => "classic-250-g-2",
));

if (!$updated) {
    throw new RuntimeException($element->LAST_ERROR);
}

// После изменения снова выполняем findActiveElementsByCode().

Порядок работы в продовой задаче

  1. Зафиксировать URL, ожидаемый ID и время, когда ошибка наблюдалась.
  2. На тестовой копии получить переменные, восстановленные из того же пути.
  3. Сделать выборку по фактическому CODE в нужном IBLOCK_ID и посчитать результаты.
  4. Сравнить полученные ID с тем, что показывает детальный компонент после его дополнительных фильтров.
  5. Если есть конфликт, выбрать новое стабильное правило кода и проверить все старые ссылки, которые важны для проекта.
  6. После изменения повторить URL-проверку. Кеш и индекс обновлять только по принятому в проекте порядку, когда данные и маршрут уже доказаны.

Ограничения

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

Также не стоит менять десятки кодов одной SQL-командой. У Bitrix есть API изменения элемента, обработчики событий и проектные зависимости от адресов. Сначала правим один доказанный конфликт на тестовых данных, проверяем маршрут и только потом составляем отдельный план для массовой миграции.

Итог

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

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